sync: update from private repo (edc775f2)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-07-06 01:04:12 +00:00
parent 747377bef9
commit b1292e34b2
322 changed files with 28001 additions and 4686 deletions
+8 -1
View File
@@ -6,14 +6,21 @@ MAESTRO を最短で動かす方法が Docker Compose です。`git clone` か
## まずはこれだけ
Linux でも、Windows の WSL2Docker Desktop の WSL 統合)でも動きます。
```bash
cp .env.example .env # OLLAMA_BASE_URL / OLLAMA_MODEL を自分の LLM に向ける
docker compose up -d # 初回はイメージをビルドしてから起動
# http://localhost:9876 を開く
```
`.env``config.yaml` は用意しなくても起動します。未設定のまま起動すると UI にセットアップウィザードが開き、そこから LLM の接続先を設定できます。先に指定しておきたい場合は `cp .env.example .env` して `OLLAMA_BASE_URL` / `OLLAMA_MODEL` を設定します([LLM エンドポイント](#llm-エンドポイント)参照)。
Compose は UI を `127.0.0.1:9876` だけに公開するので、初期状態では LAN から到達できません。外部公開は[ローカル以外へ出す](#ローカル以外へ出す)を参照。
### WindowsWSL2
Docker Desktop の WSL 統合を有効にした WSL2 ディストロ内で、同じコマンドを実行するだけです。ヘッド付きブラウザ一式はコンテナ内で完結するため、Windows 側に X サーバーや WSLg は不要です。またリポジトリの `.gitattributes` がビルド用のシェルスクリプトを LF に固定するので、Windows でチェックアウトしても(本来 CRLF になってしまう場合でも)ビルドが壊れません。
## コンテナがやること・やらないこと
- MAESTRO 本体(Web UI・ワーカー・ツール・任意の Gateway)を動かす。
+15 -1
View File
@@ -8,15 +8,29 @@ people up (Linux networking, the LLM endpoint, data persistence, the sandbox).
## TL;DR
Works on Linux and on Windows via WSL2 (Docker Desktop's WSL integration).
```bash
cp .env.example .env # point OLLAMA_BASE_URL/OLLAMA_MODEL at your LLM
docker compose up -d # builds the image on first run, then starts
# open http://localhost:9876
```
No `.env` or `config.yaml` is required to start: a fresh `docker compose up`
opens a setup wizard in the UI where you point MAESTRO at your LLM. To preset the
endpoint instead, `cp .env.example .env` and set `OLLAMA_BASE_URL`/`OLLAMA_MODEL`
(see [The LLM endpoint](#the-llm-endpoint)).
Compose publishes the UI on `127.0.0.1:9876` only, so a fresh instance is not
reachable from your LAN. See [Going beyond localhost](#going-beyond-localhost).
### Windows (WSL2)
Run the same command inside a WSL2 distro with Docker Desktop's WSL integration
enabled — nothing else is needed. The headed-browser stack runs entirely inside
the container (no host X server or WSLg), and the repository's `.gitattributes`
keeps the build's shell scripts LF-only, so a Windows checkout (which would
otherwise convert them to CRLF) still builds cleanly.
## What the container is (and is not)
- It runs the **MAESTRO app** (web UI, workers, tools, optional gateway).
@@ -0,0 +1,128 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AIエージェント設計20原則 × MAESTRO — コード実測による再評価</title>
<style>
:root { --green:#16a34a; --blue:#2563eb; --amber:#d97706; --red:#dc2626; --ink:#1f2937; --muted:#6b7280; --line:#e5e7eb; --bg:#f9fafb; }
* { box-sizing: border-box; }
body { font-family: -apple-system, "Hiragino Kaku Gothic ProN", "Noto Sans JP", sans-serif; color: var(--ink); line-height: 1.7; max-width: 1000px; margin: 0 auto; padding: 24px; background: #fff; }
h1 { font-size: 1.6rem; margin: .2em 0; }
h2 { font-size: 1.25rem; margin-top: 2em; border-bottom: 2px solid var(--line); padding-bottom: .3em; }
h3 { font-size: 1.05rem; margin-top: 1.4em; }
.badge { display: inline-block; padding: 3px 12px; border-radius: 999px; font-size: .8rem; font-weight: 700; color: #fff; background: var(--green); }
.meta { color: var(--muted); font-size: .85rem; margin: .5em 0 1.5em; }
table { border-collapse: collapse; width: 100%; margin: 1em 0; font-size: .9rem; }
th, td { border: 1px solid var(--line); padding: 7px 10px; text-align: left; vertical-align: top; }
th { background: var(--bg); font-weight: 700; }
code { background: var(--bg); padding: 1px 5px; border-radius: 4px; font-size: .85em; font-family: ui-monospace, "SF Mono", Menlo, monospace; }
.ok { color: var(--green); font-weight: 700; }
.part { color: var(--amber); font-weight: 700; }
.no { color: var(--red); font-weight: 700; }
.wrong { background: #fef2f2; }
.summary-box { background: var(--bg); border-left: 4px solid var(--blue); padding: 12px 18px; border-radius: 0 8px 8px 0; margin: 1em 0; }
.tally { display: flex; gap: 16px; flex-wrap: wrap; margin: 1em 0; }
.tally div { flex: 1; min-width: 120px; text-align: center; border: 1px solid var(--line); border-radius: 8px; padding: 12px; }
.tally .num { font-size: 1.8rem; font-weight: 800; }
.note { color: var(--muted); font-size: .85rem; }
.pri { font-weight: 700; padding: 2px 8px; border-radius: 4px; color:#fff; }
.p1 { background: var(--red); } .p2 { background: var(--amber); } .p3 { background: var(--muted); }
</style>
</head>
<body>
<span class="badge">完成 / 検証済み</span>
<h1>Anthropic「AIエージェント設計20の原則」× MAESTRO — コード実測による再評価</h1>
<p class="meta">
評価日: 2026-07-01 対象: <code>origin/main</code> commit <code>be4e98d7</code>(全ソースを実測)<br>
基にした資料: Zenn記事「Anthropic公式に学ぶAIエージェント設計20の原則」(ながたく, 2026/06/23)の MAESTRO 評価表<br>
手法: 3チームでコード横断監査(各原則→該当 <code>file:line</code>→適合判定)。推測(記事の「⚠️不明」)を実コードで置換。
</p>
<div class="summary-box">
<strong>結論。</strong> 20の原則そのものは妥当(Anthropicの実指針)。しかし元記事の<strong>MAESTRO評価は精度が低い</strong> — 表の大半が「⚠️不明/確認必要」で、コードを読まずに書かれている。コード実測の結果、MAESTROは20原則に<strong>強く整合</strong>しており(適合17/部分適合3/非適合0)、記事が「改善余地大」とした項目のうち<strong>7件は実装済み機能を見落とした事実誤認</strong>だった。実質的な改善余地は<strong>3件</strong>に絞られる。
</div>
<div class="tally">
<div><div class="num ok">17</div>適合</div>
<div><div class="num part">3</div>部分適合</div>
<div><div class="num no">0</div>非適合</div>
<div><div class="num">7</div>記事の事実誤認を反証</div>
</div>
<h2>1. 記事の主張 vs 実コード(是正一覧)</h2>
<p>元記事が否定的・不明とした主要項目のうち、実コードと矛盾するものを列挙する。</p>
<table>
<tr><th>原則</th><th>記事の主張</th><th>実測判定</th><th>根拠(file:line</th></tr>
<tr class="wrong"><td>1 注意予算/トークン計測</td><td>「トークン計測<strong>未実装</strong></td><td class="ok">誤り→適合</td><td><code>context-manager.ts:68-83</code><code>update(usage.prompt_tokens)</code>+比率閾値 0.7/0.85/0.95)/配線 <code>context-control.ts:64-116</code></td></tr>
<tr class="wrong"><td>4 サブエージェント</td><td><strong>並列化に偏り</strong>、汚染防止を文書化できていない」</td><td class="ok">誤り→適合</td><td><code>delegate-runner.ts:25-29</code>「isolation invariant」独立Conversation<code>orchestration.ts:7-11</code>「中間作業は文脈に残らない」。隔離型<code>delegate</code>と並列型<code>SpawnSubTask</code>が別実装</td></tr>
<tr class="wrong"><td>5 長タスク3戦略</td><td>「仕組みがあるか<strong>不明</strong>・改善余地大」</td><td class="ok">半分誤り→適合</td><td>圧縮=<code>history-compactor.ts</code><code>prompt-guard.ts</code>、検索=<code>task-conversation.ts</code>Search/ReadTaskConversation)+transcript replay、ノート=<code>mission.ts</code>MissionBrief(今回 user_constraints/decisions/current_focus 拡張)。3戦略とも実在</td></tr>
<tr class="wrong"><td>7 相対パス問題</td><td>「相対パス問題を避けるため<strong>絶対パス必須化</strong></td><td class="no">推奨が有害</td><td>逆。MAESTROは<strong>ワークスペース相対</strong>を強制しjail外の絶対パスを拒否(<code>core.ts:457/494/729</code>, <code>browser.ts:118</code>)。絶対パス必須化はサンドボックスを壊す</td></tr>
<tr class="wrong"><td>14 マルチエージェント</td><td>「コスト対効果の<strong>基準不明</strong></td><td class="ok">誤り→適合</td><td><code>MAX_DELEGATION_DEPTH=2</code><code>agent-loop.ts:179</code>)+直列契約+<code>research.yaml</code> dig に opt-in判断基準(複数独立対象/overflowリスク時のみ)</td></tr>
<tr class="wrong"><td>17-18 CLAUDE.md</td><td>「フックの有無不明/200行超で肥大化」</td><td class="part">カテゴリ違い</td><td>記事はリポジトリの<code>CLAUDE.md</code>Claude-Code向け手引き, 277行)とMAESTRO製品の実行時プロンプトを混同。製品側は<code>allowed_tools</code>機械ゲート+<code>MISSION_TOTAL_CHAR_BUDGET=3200</code>で境界を機械強制(<code>movement-setup.ts:36</code>, <code>prompt.ts:102-108</code></td></tr>
<tr class="wrong"><td>20 訂正回数</td><td>「訂正回数の<strong>管理不明</strong></td><td class="ok">誤り→適合</td><td>3層のループ検出: Progressive Pressure<code>prompt.ts:315</code>)/連続再訪ABORT<code>piece-runner.ts:1115</code>)/toolLoopTracker<code>loop-safety.ts</code>, 同一バッチ5回で中断)</td></tr>
</table>
<h2>2. 全20原則 — 証拠ベース判定</h2>
<h3>コンテキストエンジニアリング(1〜6</h3>
<table>
<tr><th>#</th><th>原則</th><th>判定</th><th>根拠</th></tr>
<tr><td>1</td><td>コンテキストは有限の注意予算</td><td class="ok">適合</td><td><code>ContextManager</code>が実usageからトークン追跡+閾値アクション(context-manager.ts / context-control.ts</td></tr>
<tr><td>2</td><td>文脈はジャストインタイムで引く</td><td class="ok">適合</td><td><code>ReadToolDoc</code>遅延ロード(docs.ts:110)+1行ツールカタログ自動注入(prompt.ts:290)+skill indexRead offset/limit</td></tr>
<tr><td>3</td><td>コード実行で中間データ処理</td><td class="ok">適合</td><td>Bashcore.ts:807, cwdWS・パス検証)+スクリプト作業をprompt自動推奨(prompt.ts:157)+RunUserScriptMCP</td></tr>
<tr><td>4</td><td>サブエージェント=文脈汚染の隔離</td><td class="ok">適合</td><td>隔離型<code>delegate</code>(独立Conversation, 結果のみ返却)と並列型<code>SpawnSubTask</code>が別々に実装</td></tr>
<tr><td>5</td><td>長タスクは3戦略を使い分け</td><td class="ok">適合</td><td>圧縮/検索(task-conversation)/構造化ノート(MissionBrief)すべて実在</td></tr>
<tr><td>6</td><td>長文は文書冒頭・クエリ末尾</td><td class="ok">適合</td><td><code>Conversation.seed</code>: index0=system(preamble+guidance)、index1=user(タスク本文)。参照材料が先・クエリが後</td></tr>
</table>
<h3>ツール設計・ACI7〜12</h3>
<table>
<tr><th>#</th><th>原則</th><th>判定</th><th>根拠</th></tr>
<tr><td>7</td><td>ツール設計は一次作業</td><td class="ok">適合</td><td>1文description<code>docs/tools/*.md</code>39本の二層設計。CLAUDE.md/maintenance-checklistに明文規律。※記事の絶対パス推奨は不採用(有害)</td></tr>
<tr><td>8</td><td>タスクの自然な単位</td><td class="ok">適合</td><td>SplitExcelSheets/delegate/BrowseWeb(batched actions)/CreateChecklist 等、薄いラッパーでなくワークフロー単位。今回tool-dispatcher分割で内部の関心分離も改善(#702)</td></tr>
<tr><td>9</td><td>人間が即答できる粒度</td><td class="part">部分適合<br>(記事が正しい)</td><td><strong>89ツール</strong>と多い。重複クラスタ: delegate/SpawnSubTask/WaitSubTask、WebFetch/BrowseWeb/InteractiveBrowse/BrowseWithSession、ssh/ssh-console、Read系。allowed_tools/META_TOOLSで提示は絞るが棚卸し価値あり</td></tr>
<tr><td>10</td><td>応答識別子は自然言語名</td><td class="ok">適合</td><td>WS相対パス(savedRelPath等)、サブタスク#N、checklist item_id、browser ref(e1/f1.e3)、comment:&lt;id&gt;/transcript:&lt;index&gt;。不透明UUIDをLLMに晒さない</td></tr>
<tr><td>11</td><td>エラー応答は改善指示</td><td class="ok">適合</td><td>invalid transition→有効な遷移先列挙、Read binary→Bashでの確認手順、大出力→Grep/offset提案 等。弱点: Edit old_string not foundSSH command rejected は原因のみ</td></tr>
<tr><td>12</td><td>現実的複雑度で評価</td><td class="part">部分適合</td><td>bench 5軸100点+LLM-judge、composite-mini-reportは3ソース融合・現実的。<strong>だがbenchタスクが2本のみ</strong>。89ツールの大半は未網羅(unitテスト46本で部分補完)</td></tr>
</table>
<h3>ワークフロー選択・プロンプト・運用(13〜20)</h3>
<table>
<tr><th>#</th><th>原則</th><th>判定</th><th>根拠</th></tr>
<tr><td>13</td><td>事前特定でWF/Agentを選ぶ</td><td class="ok">適合</td><td>ハイブリッド: movementグラフは静的(rules[].next、終端値はlint拒否 piece-runner.ts:92)、movement内はReActエージェント(最大200反復)</td></tr>
<tr><td>14</td><td>並列価値がコストを上回る時だけ</td><td class="ok">適合</td><td>depth=2上限+直列delegateresearch.ymlのopt-in判断基準</td></tr>
<tr><td>15</td><td>ルール列挙より3〜5例</td><td class="part">部分適合</td><td>chat/research/ssh-opsに具体テンプレ・few-shotあり。ただし一部はまだルール列挙。例示ファーストに寄せる余地</td></tr>
<tr><td>16</td><td>指示に理由を添える</td><td class="ok">適合</td><td>ssh-ops「ローカルで回避してはいけない」「MITM疑い→自動リトライしない」等の理由付き。<code>why_no_default</code>必須フィールドで構造的にも強制</td></tr>
<tr><td>17</td><td>重要境界は機械的ブロック</td><td class="ok">適合</td><td><code>getToolDefs</code>がallowed_tools外を提示しない=フィルタで強制。editフラグ、rules終端値lint、SSH allowlist。(記事はCLAUDE.md混同のカテゴリ違い)</td></tr>
<tr><td>18</td><td>CLAUDE.mdは200行未満</td><td class="ok">適合(runtime)</td><td>製品ランタイムは<code>MISSION_TOTAL_CHAR_BUDGET=3200</code>で切詰め。リポジトリCLAUDE.md 277行はClaude-Code向けの軽微なnitで製品所見ではない</td></tr>
<tr><td>19</td><td>検証コマンドで完了定義</td><td class="ok">適合</td><td><code>vitest run</code><code>npm run bench</code><code>validate-help-docs.mjs</code>Stopフック(changelog警告)</td></tr>
<tr><td>20</td><td>同じ修正2回でリセット</td><td class="ok">適合</td><td>Progressive Pressure+連続再訪ABORT(loop_detected)toolLoopTrackercontext force_transitiontext-only上限。カウントは管理されている</td></tr>
</table>
<h2>3. 実質的な改善余地(コード実測で残った3件)</h2>
<p>記事の「優先改善トップ5」のP0(CLAUDE.md肥大化・トークン予算未実装)は前提が誤りのため除外。実測で残る改善余地は次の通り。</p>
<table>
<tr><th>優先</th><th>改善点</th><th>原則</th><th>根拠</th></tr>
<tr><td><span class="pri p1">P1</span></td><td><strong>ツール棚卸し・統合</strong>。89ツールを「人間が1秒で使い分けを言えるか」で監査し、重複クラスタ(サブエージェント3種/ブラウザ4種/SSH2系統)を整理・description明確化。CLAUDE.mdのツール表も実態と乖離(棚卸し漏れの症状)</td><td>8,9</td><td>実測89ツール、重複4クラスタ特定</td></tr>
<tr><td><span class="pri p2">P2</span></td><td><strong>bench網羅の拡充</strong>。現在2タスクのみ。主要ツール群を現実的タスクで評価する bench を追加</td><td>12</td><td>bench/tasks/ に2本のみ</td></tr>
<tr><td><span class="pri p3">P3</span></td><td>細部: (a) pieceの一部ルール列挙を例示に寄せる(15)、(b) Edit/SSHの一部エラーに次の一手を追記(11)</td><td>11,15</td><td>該当箇所を特定済み</td></tr>
</table>
<h2>4. 総評</h2>
<p>
MAESTROは、記事が「基礎はある」とした以上に、20原則の<strong>大部分を既に実装済み</strong>である — トークン予算管理、JITコンテキスト、隔離型サブエージェント、3層ループ検出、機械的ツールゲート、char-budget化された実行時プロンプトは、いずれもコードに実在する。記事の否定的評価の多くは、コードを読まずアーキテクチャ要約から推測したことによる事実誤認だった。
</p>
<p>
唯一、記事が正しく指摘し実測でも残るのは<strong>ツールカタログの肥大(原則9)と評価網羅(原則12)</strong>である。特に89ツールという規模は「人間が使い分けを即答できる」水準を超えており、重複クラスタの統合が最も費用対効果の高い改善となる。
</p>
<hr style="margin:2em 0; border:none; border-top:1px solid var(--line);">
<p class="note">
トレーサビリティ: 本評価は 2026-07-01 に <code>origin/main</code>(be4e98d7) の全ソースを3チームで横断監査した結果。各判定は該当 <code>file:line</code> を根拠とする。元記事の評価は本番コードへのアクセスなしに書かれており、本書はそれを実測で全面的に置換したもの。<br>
設計と実装の差分: 元記事の「⚠️不明」20項目中17項目が「実装済み・適合」と判明。すなわち<strong>設計意図(原則)と実装は大きく乖離しておらず、乖離していたのは記事の推測と実コードの間</strong>だった。
</p>
</body>
</html>
@@ -0,0 +1,193 @@
# Agent loop follow-up issues
## Issue 1: Fix dangling control tool calls across movement transitions
### Problem
`transition` and `complete` tool calls can be appended to the live conversation
without a matching tool result message. When the same `Conversation` is reused by
the next movement, strict OpenAI-compatible providers may reject the next request
because every assistant `tool_call` must have a corresponding `tool` message.
### Evidence
- `src/engine/agent-loop.ts`: assistant messages are recorded with all pending
tool calls, including control calls.
- `src/engine/agent-loop/terminal-control.ts`: valid `transition` returns a
`MovementResult` without a tool result message.
- `src/engine/agent-loop.ts`: valid `complete` returns immediately without a
tool result message.
- `src/engine/context/conversation.ts`: `replayableTurns()` sanitizes persisted
transcript replay, but live `conversation.messages` shared across movements is
not sanitized.
### Expected behavior
After a movement exits via `transition` or `complete`, the next model request
must not contain unresolved control `tool_call`s.
### Suggested fix
Normalize live conversation state before entering the next movement, or avoid
recording terminal/control tool calls without corresponding tool messages.
### Acceptance criteria
- Add a regression test that uses a shared `Conversation` across a real
`transition`.
- The second movement's model input contains no dangling `transition` or
`complete` tool calls.
- Existing agent-loop tests continue to pass.
---
## Issue 2: Make context overflow terminal defaults consistently abort
### Problem
Context overflow handling has two different policies:
- `buildContextOverflowResult()` converts terminal defaults (`COMPLETE`, `ASK`)
to `ABORT`.
- `applyContextManagerUpdate()` with `force_transition` uses
`movement.defaultNext ?? 'ABORT'` directly.
This means context pressure can falsely complete or ask from a movement whose
default next step is terminal, even though the safer policy is to abort on
context loss.
### Evidence
- `src/engine/agent-loop/context-control.ts`: `buildContextOverflowResult()`
normalizes `COMPLETE` and `ASK` to `ABORT`.
- `src/engine/agent-loop/context-control.ts`: `applyContextManagerUpdate()`
force transition uses `movement.defaultNext` directly.
### Expected behavior
All context-overflow forced exits should use the same terminal-default policy.
When context is compromised, terminal defaults should not be treated as
successful completion.
### Suggested fix
Share one helper for context-overflow movement results, or apply the same
terminal-default normalization in `applyContextManagerUpdate()`.
### Acceptance criteria
- Add a regression test for `force_transition` with `defaultNext: "COMPLETE"`.
- The result is an abort-style movement result, not a successful completion.
- Existing context overflow tests continue to pass.
---
## Issue 3: Enforce `why_no_default` for `needs_user_input`
### Problem
The prompt and tool description require `why_no_default` when calling
`complete({ status: "needs_user_input" })`, but runtime validation only requires
`missing_info`.
This lets the agent ask the user without documenting why it could not choose a
reasonable default, which weakens the intended "avoid unnecessary user
questions" behavior.
### Evidence
- `src/engine/agent-loop/prompt.ts`: instructs the model to provide
`why_no_default`.
- `src/engine/agent-loop/terminal-control.ts`: tool description says
`why_no_default` is required.
- `src/engine/agent-loop/terminal-control.ts`: `validateCompleteArgs()` only
checks `missing_info`.
- `src/engine/agent-loop/terminal-control.ts`: movement output ignores
`why_no_default`.
### Expected behavior
`needs_user_input` should be rejected unless both `missing_info` and
`why_no_default` are non-empty strings.
### Suggested fix
Validate `why_no_default` in `validateCompleteArgs()` and include it in the
result/debug output if useful.
### Acceptance criteria
- Add a negative test for missing `why_no_default`.
- Add a positive test for `needs_user_input` with both fields.
- Existing `complete` behavior remains unchanged for other statuses.
---
## Issue 4: Validate interactive browse waiting-human session id
### Problem
`parseInteractiveBrowseWaitingHuman()` casts `sessionId` to `string` without
checking that it is present and actually a string.
If the tool returns malformed `waiting_human` output, the movement result can
carry `browserSessionId: undefined` despite the type expecting a string.
### Evidence
- `src/engine/agent-loop/tool-dispatcher.ts`: `sessionId` is read via
`parsed["sessionId"] as string`.
- The function validates `action` and `waitReason`, but not `sessionId`.
### Expected behavior
Malformed waiting-human output should not produce a typed waiting-human movement
result with an undefined session id.
### Suggested fix
Require `typeof parsed["sessionId"] === "string"` and a non-empty value before
returning a waiting-human result.
### Acceptance criteria
- Add a test for malformed `waiting_human` output without `sessionId`.
- Add a test for valid `waiting_human` output.
- Existing interactive browse behavior remains unchanged for valid tool output.
---
## Issue 5: Split `tool-dispatcher.ts` before it becomes the next agent-loop
### Problem
`tool-dispatcher.ts` now owns several distinct responsibilities:
- cache routing
- cache hit/miss event logging
- tool execution
- memory checkpoint behavior
- waiting-human parsing
- batched result recording
This file is becoming the next high-coupling module after the agent-loop split.
### Expected behavior
Tool dispatch should be decomposed into focused modules so bugs in cache
routing, execution, and special tool outputs are easier to test independently.
### Suggested fix
Split along behavioral boundaries, for example:
- `tool-cache-routing.ts`
- `tool-execution.ts`
- `tool-result-recorder.ts`
- `interactive-browse-result.ts`
### Acceptance criteria
- No behavior change.
- Existing agent-loop and tool-loop tests pass.
- New modules expose narrow, testable functions.
@@ -0,0 +1,324 @@
# Improve task memory and conversation recall
## Summary
Long-running local tasks can lose early user instructions, constraints, and
decisions after several exchanges. The codebase already has partial mechanisms:
- `MissionUpdate` / Mission Brief pins `goal`, `done`, `open`, and
`clarifications`.
- `Conversation` persists `logs/transcript.jsonl` and can replay prior turns on
continuation.
- `buildLocalConversationContext()` injects recent task comments.
- `handoffContext` carries the previous piece result.
However, these do not give the agent a reliable way to preserve task state or
actively search older conversation history.
This issue proposes three related improvements:
1. Make Mission Brief a stronger task-state ledger.
2. Add agent-facing conversation search/read tools.
3. Update prompts so the agent proactively revisits prior conversation when
task context is ambiguous or long-running.
## Problem
In multi-turn tasks, the agent often appears to forget early conversation
details, especially:
- original user constraints
- decisions made after clarification
- "do not change X" instructions
- previous failed attempts
- which files were already inspected or changed
- why a plan was chosen
Current behavior is understandable from the implementation:
- `MissionUpdate` is optional and model-driven; it is not enforced at movement
boundaries.
- Mission Brief fields are too coarse for long implementation tasks.
- local task context only injects the recent conversation window.
- transcript replay is automatic context carry-over, not searchable retrieval.
- prompt compaction/summarization can remove or compress important details.
## Existing Mechanisms To Reuse
### Mission Brief
Source: `src/engine/tools/mission.ts`
Mission Brief is the best existing place for pinned task state because it is:
- per local task
- visible at the top of every movement prompt
- editable by the agent through `MissionUpdate`
- editable by the user in the UI
- intended to prevent long-conversation drift
This should be extended rather than replaced.
### Conversation transcript
Source: `src/engine/context/conversation.ts`
`logs/transcript.jsonl` is already written during execution and replayed on
continuation. It should remain the raw audit source. New search tools should
read from it instead of stuffing the whole transcript into prompt context.
### Local task comments
Source: `src/engine/local-context.ts`
Task comments are the user-facing conversation layer. They should be searchable
alongside transcript entries because user instructions often live in comments,
requests, and interjections.
## Proposed Design
### 1. Extend Mission Brief schema
Add fields that separate task facts from progress:
```ts
interface MissionBrief {
goal?: string;
user_constraints?: string;
decisions?: string;
done?: string;
open?: string;
current_focus?: string;
touched_files?: string;
risks?: string;
clarifications?: string;
last_updated_by_movement?: string;
}
```
Keep backwards compatibility with existing `goal`, `done`, `open`, and
`clarifications`.
### 2. Make movement-boundary updates explicit
At the end of each movement, encourage or enforce a Mission Brief update when
the movement made meaningful progress.
Suggested behavior:
- On movement start, render the current Mission Brief as today.
- During movement, the model can still call `MissionUpdate`.
- Before `transition` or `complete`, prompt guidance should require the agent to
update `done`, `open`, `current_focus`, and relevant `decisions` if stale.
- Optionally add a lightweight stale-check:
- if a movement used tools or edited files
- and no `MissionUpdate` occurred
- inject a reminder before accepting terminal control calls
Do not make this a hard blocker initially; use a reminder first to avoid
creating loops.
### 3. Add `SearchTaskConversation`
Add a META tool available to all local-task pieces.
Suggested signature:
```ts
SearchTaskConversation({
query: string;
source?: "comments" | "transcript" | "both";
author?: "user" | "agent" | "system";
kind?: "request" | "comment" | "interjection" | "result" | "ask" | "progress" | "handoff";
limit?: number;
})
```
Search sources:
- DB local task comments
- `runtimeDir/transcript.jsonl` when available
Return compact excerpts only:
```md
## Conversation Search Results
- comment:123 user/comment 2026-06-30T...
excerpt: ...
- transcript:42 user 2026-06-30T...
excerpt: ...
```
Constraints:
- cap result count
- cap excerpt length
- never return full transcript by default
- search only the current task's conversation
- respect existing task/space authorization boundaries
### 4. Add `ReadTaskConversation`
Add a companion tool to read context around a known hit.
Suggested signature:
```ts
ReadTaskConversation({
ref: "comment:123" | "transcript:42";
before?: number;
after?: number;
})
```
Return nearby entries with strict caps. This keeps search cheap and lets the
agent inspect the exact conversation around an old decision.
### 5. Wire conversation retrieval into Mission Brief refresh
Update prompt guidance for long tasks:
- If user constraints or decisions are unclear, use `SearchTaskConversation`
before asking the user.
- When updating `user_constraints` or `decisions`, prefer citing old comments or
transcript refs in the brief.
- Before asking the user a clarification, search the task conversation when the
missing information may already have been stated earlier.
- Before changing previously touched files or revising a prior decision, search
for older constraints/decisions related to the file or topic.
Example Mission Brief snippet:
```md
### User constraints
- Keep existing auth flow unchanged. Source: comment:17
- Do not rewrite unrelated UI. Source: transcript:42
### Decisions
- Extend Mission Brief instead of creating a second TaskState store. Source: comment:23
```
### 6. Add proactive recall guidance to system/movement prompts
The new tools should not be passive. Update prompt guidance so the agent
actively uses them when forgetting early conversation is likely.
Suggested rules:
- At the start of a follow-up task, review Mission Brief first.
- If Mission Brief is missing, stale, or too vague, call
`SearchTaskConversation` before proceeding with assumptions.
- If the task has multiple user turns or interjections, search conversation
history for constraints before making broad edits.
- If the agent is about to ask the user something, first search prior comments
and transcript for the answer unless the question is truly new.
- If a movement resumes after context compaction, search for relevant older
decisions before modifying files.
- When search results reveal durable constraints or decisions, update
Mission Brief immediately.
Candidate prompt copy:
```md
## Conversation recall
For long-running or follow-up tasks, do not rely only on the visible recent
context. If an earlier user constraint, decision, or clarification may affect
your next action, use SearchTaskConversation / ReadTaskConversation before
proceeding. Prefer retrieving prior context over asking the user to repeat it.
When you find durable task facts, update MissionUpdate so they remain pinned.
```
Keep this guidance concise so it does not crowd out movement-specific
instructions.
## Files Likely Involved
- `src/db/repository.ts`
- extend `MissionBrief`
- update parse/update tests
- add query helpers for task comments if needed
- `src/engine/tools/mission.ts`
- extend `MissionUpdate` schema
- clamp/render new fields
- tests for partial updates
- `src/engine/agent-loop/prompt.ts`
- render new Mission Brief fields
- update movement guidance around state refresh
- add concise proactive recall guidance for long/follow-up tasks
- `src/engine/agent-loop/watchdogs.ts`
- optionally add stale Mission Brief reminder logic
- optionally remind the agent to search conversation history when Mission Brief
is empty/stale during later iterations
- `src/engine/tools/index.ts`
- add new conversation tools to META_TOOLS
- `src/engine/tools/conversation.ts` or `src/engine/tools/task-conversation.ts`
- implement `SearchTaskConversation`
- implement `ReadTaskConversation`
- `src/engine/tools/core.ts`
- expose safe context fields needed by conversation tools, e.g. taskId,
runtimeDir, workspacePath, repo access if needed
- `src/worker.ts`
- ensure ToolContext has enough current-task context for the new tools
- `ui/src/components/detail/tabs/OverviewTab.tsx`
- expose new Mission Brief fields for user editing
- `ui/src/content/help/*.md`
- update help docs if behavior/UI changes
- `ui/src/content/help/00-changelog.md`
- add a user-facing changelog entry
## Acceptance Criteria
- Mission Brief supports new fields while preserving existing data.
- Existing tasks with old Mission Brief JSON still load correctly.
- `MissionUpdate` can update new fields independently.
- Mission Brief rendering keeps a bounded prompt size.
- Movement guidance encourages updating Mission Brief before transitions.
- Prompt guidance tells the agent to search prior conversation before asking the
user to repeat information or making assumptions in long/follow-up tasks.
- Prompt guidance tells the agent to search prior conversation before asking the
user to repeat information or making assumptions in long/follow-up tasks.
- `SearchTaskConversation` can find old user comments by keyword.
- `SearchTaskConversation` can find transcript entries when `transcript.jsonl`
exists.
- `ReadTaskConversation` can return bounded context around a search result.
- Tools are scoped to the current task and cannot read other task logs.
- Tests cover:
- old Mission Brief compatibility
- new Mission Brief fields
- conversation search over comments
- conversation search over transcript
- prompt text includes proactive recall guidance
- bounded output and excerpt truncation
- authorization/task scoping
## Non-goals
- Do not put task-specific state into `UpdateUserMemory`; that is user-wide
memory and would pollute future unrelated tasks.
- Do not inject the full transcript into every prompt.
- Do not replace `Conversation` replay; retrieval should complement it.
- Do not make Mission Brief update a hard terminal blocker until reminder-only
behavior has been observed.
## Suggested Implementation Order
1. Extend Mission Brief schema and UI rendering.
2. Extend `MissionUpdate` and prompt rendering.
3. Add reminder-only movement-boundary guidance.
4. Implement `SearchTaskConversation` for DB comments.
5. Add transcript search.
6. Add `ReadTaskConversation`.
7. Add proactive recall prompt guidance and tests.
8. Update help docs and changelog.
@@ -0,0 +1,229 @@
# Add workspace file provenance so agents can identify which task created or owns files
## Summary
Persistent workspaces make it harder to tell which task created, uploaded, or
modified a file. This is especially confusing when multiple tasks share the same
workspace tree. The agent may see a file and assume it belongs to the current
task, even though it was an input or output from a different task.
Add a file provenance ledger so both users and agents can answer:
- Which task created this file?
- Was this uploaded by the user or generated by an agent?
- Which task last modified it?
- Is it safe for the current task to edit, or should it be treated as read-only
context?
## Problem
In persistent workspace mode, files can outlive a single task. That is useful,
but it creates ambiguity:
- old input files remain visible to later tasks
- previous task outputs look like current task artifacts
- generated files and uploaded files are not clearly distinguished
- agents may edit or rely on files that belong to unrelated tasks
- users cannot easily audit why a file exists
Current workspace/runtime separation helps logs:
- `workspace_path` points to the shared files tree
- `runtime_dir` separates per-task logs/checklists/raw outputs
But shared workspace files themselves do not have provenance metadata.
## Proposed Design
Add a sidecar provenance ledger for files in persistent workspaces.
Prefer a DB-backed table for queryability, with optional JSONL export for
debugging. Do not write task tags into file contents.
### Schema
Suggested table:
```sql
CREATE TABLE workspace_file_provenance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
space_id TEXT,
workspace_path TEXT NOT NULL,
rel_path TEXT NOT NULL,
created_by_task_id INTEGER,
created_by_job_id TEXT,
created_by_piece TEXT,
created_by_movement TEXT,
source_kind TEXT NOT NULL,
first_seen_at TEXT NOT NULL,
last_modified_by_task_id INTEGER,
last_modified_by_job_id TEXT,
last_modified_at TEXT,
checksum TEXT,
note TEXT,
UNIQUE(workspace_path, rel_path)
);
```
Suggested `source_kind` values:
- `user_input`
- `agent_output`
- `agent_edit`
- `bash_generated`
- `subtask_output`
- `imported_existing`
- `unknown`
### Recording Rules
Record provenance at these boundaries:
- UI upload to `input/`
- `source_kind=user_input`
- `created_by_task_id=current task`
- `Write`
- new file: `source_kind=agent_output`
- existing file: update `last_modified_by_task_id`
- `Edit`
- update `last_modified_by_task_id`
- keep original `created_by_task_id`
- `Bash`
- compare workspace file snapshot before/after command
- new files: `source_kind=bash_generated`
- changed files: update `last_modified_by_task_id`
- subtask outputs copied/visible to parent
- `source_kind=subtask_output`
- include parent task and subtask job id
- existing files in a persistent space before this feature ships
- backfill as `source_kind=imported_existing` or `unknown`
### Agent-Facing Tools
Add META tools:
```ts
GetFileProvenance({ path: string })
```
Returns a compact provenance record for one file.
```ts
ListWorkspaceFiles({
path?: string;
sourceKind?: string;
createdByTaskId?: number;
lastModifiedByTaskId?: number;
includeUnknown?: boolean;
})
```
Returns bounded file listings with provenance summaries.
### Prompt Guidance
Update the system prompt / workspace guidance so the agent uses provenance
before editing ambiguous files.
Candidate prompt copy:
```md
## Workspace file provenance
Persistent workspaces may contain files from older tasks. Before editing a file
whose provenance shows `source_kind=user_input` or `created_by_task_id` differs
from the current task, verify that it is relevant. Prefer creating a new output
file when unsure. Use GetFileProvenance / ListWorkspaceFiles to inspect file
origin.
```
### UI
Show provenance in the file browser / preview:
- created by task id/title
- source kind
- last modified by task id/title
- timestamp
This can be a small metadata row or tooltip; avoid cluttering the file list.
## Files Likely Involved
- `src/db/schema.sql`
- add provenance table
- `src/db/migrate.ts`
- migration for existing installations
- `src/db/repository.ts`
- read/write/query provenance records
- optional backfill helpers
- `src/engine/tools/core.ts`
- hook `Write`, `Edit`, and `Bash` file changes
- expose task/job/piece/movement context needed for provenance
- `src/engine/piece-runner.ts`
- thread task id, job id, piece, movement into `ToolContext`
- `src/engine/tools/index.ts`
- register provenance tools as META tools
- `src/engine/tools/file-provenance.ts`
- implement `GetFileProvenance`
- implement `ListWorkspaceFiles`
- `src/bridge/local-tasks-api.ts` / file APIs
- record UI uploads
- return provenance metadata with file listing/detail responses
- `ui/src/components/files/*`
- display provenance metadata
- `src/engine/agent-loop/prompt.ts`
- add concise workspace provenance guidance
- `ui/src/content/help/*.md`
- document how provenance labels work
- `ui/src/content/help/00-changelog.md`
- add user-facing changelog entry
## Acceptance Criteria
- New files created by `Write` record current task provenance.
- Files changed by `Edit` preserve original creator and update last modifier.
- Files created or changed by `Bash` are detected and recorded.
- User-uploaded input files are recorded as `user_input`.
- File listing or preview can show provenance metadata.
- Agent can call `GetFileProvenance` for a file.
- Agent can list files filtered by provenance.
- Prompt guidance tells the agent to avoid editing unrelated/user-input files
without checking relevance.
- Existing persistent workspace files remain usable after migration.
- Provenance output is bounded and does not dump large file contents.
## Non-goals
- Do not embed task tags into file contents.
- Do not make files from other tasks globally read-only; the agent may still
need to use shared workspace artifacts.
- Do not block all edits to `user_input` files immediately. Start with warnings
and guidance to avoid breaking existing workflows.
- Do not use `UpdateUserMemory` for per-file task metadata.
## Suggested Implementation Order
1. Add DB schema and repository helpers.
2. Record UI uploads and `Write`/`Edit` provenance.
3. Add `Bash` before/after file change detection.
4. Implement `GetFileProvenance`.
5. Implement `ListWorkspaceFiles` with filters and caps.
6. Add prompt guidance.
7. Surface metadata in the file browser/preview.
8. Update help docs and changelog.
@@ -0,0 +1,114 @@
# Add segmented screenshots to BrowseWeb
> **Status: Implemented (2026-07-03).** 設計との主な差分は下記「Implementation notes」を参照。
## Implementation notes (設計との差分)
- **既定を分割に変更**: 当初案は `screenshotSegments: true` のオプトインだったが、実際のユーザー意図は「スクショは既定で 1 画面ぶんずつ区切る」だったため、**BrowseWeb では分割を既定挙動**にした。フルページ 1 枚が欲しい場合は `screenshotSegments: false`(基本モード)/ アクションの `segments: false` でオプトアウトする。
- **短ページは連番なし**: 1 画面に収まるページは連番を付けず `output/<name>.png` の 1 枚のまま(後方互換)。2 画面ぶん以上のときだけ `-001` / `-002` の連番になる。
- **分割単位・撮影方式**: ビューポート高さ(1 画面ぶん)を単位に、`fullPage: true` + `clip` でページを縦に切り出す。スクロールしないため、(1) `position:fixed`/`sticky` なヘッダーが各セグメント先頭に重複して本文を隠さない、(2) 撮影後にページのスクロール位置を変えないので後続アクション(ref/selector クリック等)に副作用が出ない。最後のセグメントは残り高さぶんだけ切り出し、下端に余白を作らない。
- **枚数の絶対上限**: `maxSegments` はユーザー(LLM)指定でも `ABSOLUTE_MAX_SEGMENTS`50)を超えない。巨大な `scrollHeight` を返すページや極端な指定値による撮影ループの暴走(worker 時間・ディスク枯渇)を防ぐ最終防波堤。非有限値(NaN/Infinity)は既定値にフォールバック。
- **TestWorkspaceApp は現状維持**: 共通ハンドラ(`runPageActions`)の既定を分割にしたため、`TestWorkspaceApp` の screenshot アクションには `segments: false` を明示して単一フルページ撮影を維持(本 issue の scope 指示どおり)。
- **打ち切り通知**: `maxSegments`(既定 10)で打ち切った場合は戻り値にその旨を出力(無音打ち切りにしない)。
- 実装: `src/engine/tools/browser.ts``planScreenshotSegments` / `segmentFilename` / `captureScreenshots`)、テスト: `browser.screenshot-segments.test.ts`(純関数)・`browser.screenshot-capture.test.ts`(実ブラウザ、`CONTAINER=1` ゲート)。
## Summary
Long HTML pages are hard for agents to verify from a single screenshot. A full-page
capture can become extremely tall, causing image understanding to miss details or
compress important UI states. Viewport-only screenshots avoid giant images, but they
only show the first visible area.
Add a segmented screenshot mode to `BrowseWeb` so agents can capture a page as a
sequence of viewport-sized images.
## Problem
For generated HTML verification and UI review, agents often need to inspect the
whole rendered page. Current screenshot modes are not ideal:
- viewport screenshot: readable, but only captures one screen
- full-page screenshot: complete, but can be too tall and information-dense
This makes visual verification unreliable for long reports, dashboards, and other
HTML outputs.
## Proposed Design
Keep existing screenshot behavior and add an explicit segmented mode.
### Basic BrowseWeb mode
```js
BrowseWeb({
url: "output/report.html",
screenshot: "report.png",
screenshotSegments: true
})
```
Expected output files:
```text
output/report-001.png
output/report-002.png
output/report-003.png
```
### Action mode
```js
BrowseWeb({
actions: [
{ type: "goto", url: "output/report.html" },
{ type: "screenshot", value: "report.png", segments: true, maxSegments: 8 }
]
})
```
### Behavior
- Scroll the page from top to bottom and capture one viewport-sized screenshot
per segment.
- Use stable generated names based on the requested screenshot filename:
`name-001.ext`, `name-002.ext`, etc.
- Return the saved file list in the tool output.
- Default to a bounded number of segments, for example `maxSegments: 10`, to
avoid runaway captures on infinite-scroll pages.
- Allow callers to override the cap with `maxSegments`.
- Preserve existing modes:
- default screenshot remains a single viewport image
- full-page screenshot remains available via the explicit full-page option
- Do not change `TestWorkspaceApp` iframe screenshots unless a separate need is
identified.
## Files Likely Involved
- `src/engine/tools/browser.ts`
- extend `BrowseWebAction` with `segments?: boolean` and `maxSegments?: number`
- add basic-mode inputs `screenshotSegments` and `screenshotMaxSegments`
- implement shared segmented capture helper
- `src/engine/tools/browser.runpageactions.test.ts`
- add regression coverage for segmented screenshots on a tall local HTML page
- assert filenames and image heights/counts
- `docs/tools/browseweb.md`
- document segmented screenshot usage
- `ui/src/content/help/00-changelog.md`
- add a user-facing changelog entry when implemented
- `ui/src/content/help/16-tools.md`
- mention that browser screenshots can be captured as viewport, full-page, or
segmented images
## Acceptance Criteria
- `BrowseWeb({ url, screenshot, screenshotSegments: true })` saves multiple
viewport-sized screenshots for a tall page.
- Action-mode `screenshot` supports `segments: true`.
- The tool output lists all saved segment paths.
- Segment count is capped by default and configurable.
- Existing viewport and full-page screenshot behavior continues to work.
- Tests cover viewport, full-page, and segmented capture behavior.
+81 -19
View File
@@ -54,29 +54,29 @@ grep -c "engine/tools/" src/bridge/tools-api.ts
## 2. 既存モジュールにツールを追加した場合
**対象ファイル:**
- `pieces/*.yaml` — 必要な piece の `allowed_tools` にツール名を追加
- `src/engine/tools/tool-categories.ts` — 新ツールを適切なカテゴリに割り当てる(sensitive は既定 OFF)。ツールの利用可否は workspace tool policy(設定→ツール)が決めるので、**piece 側に追加する必要はない**
- `CLAUDE.md` — 「ツールモジュール構成」テーブルの該当モジュール行に新ツール名を追加
- `docs/tools/{name}.md` — 新ツールの詳細ドキュメント(推奨)
- ツール `description` — 1 文 + 「詳細は ReadToolDoc({ name: "XXX" })」を末尾に記述
**なぜ必要か:**
`allowed_tools` に載っていないツールは LLM に提示されない。ツールを実装しても piece に追加しなければエージェントが使えない。
ツールの提示は workspace tool policy が決める(カテゴリ単位。sensitive 以外は既定 ON)。カテゴリ未割り当てだとどのワークスペースでも出ない。
CLAUDE.md のテーブルが古いと、Claude Code 自身が既存ツールを認識せずに新規実装してしまうリスクがある。
ツール description は毎 LLM 呼び出しに乗るため 1 文に絞り、詳細は ReadToolDoc 経由で必要時のみ読み込む(agent-loop が movement 開始時に description サマリを自動カタログ化する)。
**確認方法:**
新ツールが使われるべき piece を特定し、`allowed_tools` に含まれているか確認する。
`tool-categories.ts` で新ツールがカテゴリに割り当てられているか確認する。
CLAUDE.md のモジュールテーブルに新ツール名が含まれているか確認する。
**実例: TestWorkspaceApp2026-06-24:**
`src/engine/tools/app-test.ts` に追加 → `tools/index.ts``tryLoadModule` 追加 → `src/bridge/tools-api.ts` のモジュール一覧に追加 → `pieces/workspace-app.yaml` の verify movement `allowed_tools` に追加`src/engine/tools/docs.ts``TOOL_DOC_ALIASES``testworkspaceapp: 'testworkspaceapp'` を追加 → `docs/tools/testworkspaceapp.md` を新規作成。
`src/engine/tools/app-test.ts` に追加 → `tools/index.ts``tryLoadModule` 追加 → `src/bridge/tools-api.ts` のモジュール一覧に追加 → `tool-categories.ts` でカテゴリ割り当て`src/engine/tools/docs.ts``TOOL_DOC_ALIASES``testworkspaceapp: 'testworkspaceapp'` を追加 → `docs/tools/testworkspaceapp.md` を新規作成。(旧手順の「piece の allowed_tools に追加」は撤去済み)
---
## 3. ツールをリネーム・削除した場合
**対象ファイル:**
- `pieces/*.yaml` — 全 piece の `allowed_tools` から旧名を削除/リネーム
- `src/engine/tools/tool-categories.ts` — カテゴリ定義・`SENSITIVE_TOOLS` から旧名を削除/リネーム
- `src/engine/tools/raw-save.ts``RAW_SAVE_TOOLS` に旧名が残っていないか
- `ui/src/components/settings/ToolsForm.tsx` — ヘルプテキスト等にツール名の言及がないか
- `CLAUDE.md` — ツールモジュール構成テーブル
@@ -205,7 +205,7 @@ grep -A 20 'LEGACY_SECTION_REDIRECT' ui/src/components/settings/SettingsSidebar.
**なぜ必要か:**
Tool description は毎回 LLM のコンテキストに乗るため肥大化させたくない。詳細手順・ワークフロー例は `docs/tools/{name}.md` に置き、`ReadToolDoc` ツールで必要時に取得する設計。
ReadToolDoc は META_TOOLS として常時利用可能なので、piece の `allowed_tools` に追加する必要はない
ReadToolDoc は META_TOOLS として常時利用可能workspace tool policy でも常時 ON
関連ツール(CheckItem / CreateChecklist / GetChecklist 等)は1つの doc にまとめてエイリアス経由で引けるようにする。
**確認方法:**
@@ -465,23 +465,26 @@ grep -n "BEGIN\|COMMIT\|prepare(" src/ssh/*.ts | head -30
- ssh-types.ts と API レスポンス shape (`SshConnection`, `SshGrant`, `SshAuditRow`) が一致しているか
- 禁止フォントサイズ (`text-[11px]` 等) を導入していないか — 既存セクションの「UI フォントサイズスケール」参照
### 12-E. piece schema (`allowed_ssh_connections`) を変更したとき
### 12-E. SSH 接続の認可 (workspace connection scoping) を変更したとき
SSH 接続の認可は **piece ではなくワークスペース**で決まる(設定→SSH の登録接続)。
piece の `allowed_ssh_connections` は撤去済み。接続スコープは worker が解決して
`workspaceSshConnections` として piece-runner に渡す。
**対象ファイル:**
- `src/engine/piece-runner.ts``allowed_ssh_connections` の lint (validateMovement)
- `src/engine/types.ts` (or piece schema 定義箇所) — `allowed_ssh_connections?: string[]`
- `pieces/*.yaml` — SSH ツールを `allowed_tools` に含む movement は **必ず** `allowed_ssh_connections` 宣言が必要 (空配列 `[]` でも可)
- `docs/ssh.md` §"Per-piece `allowed_ssh_connections`"
- `src/worker.ts``parsedPolicy.enabledSensitive``ssh` があるとき `listBySpace(spaceId)` で接続 ID を解決(`workspaceSshConnections`
- `src/engine/piece-runner.ts``workspaceSshConnections ?? []``ctx.allowedSshConnections` / `movement.allowedSshConnections` に流す
- `src/engine/tools/ssh.ts` — preflight で `ctx.allowedSshConnections` に対して接続 ID を検証
- `docs/ssh.md` §"接続の認可"
**lint 規約:**
- `allowed_tools` に SSH ツール名が含まれる場合、`allowed_ssh_connections` の宣言が必須 (`undefined` reject)
- 値は配列、各要素は `*` または lowercase hex + ハイフン UUID (≥ 8 chars)
- 空配列 `[]` は "deny all" として明示扱い
**規約:**
- 接続が 1 つも登録されていない(空配列)→ SSH ツールはクリーンに reject
- `'*'` ワイルドカードは worker からは渡さない(常に明示 ID リスト = クロススペース分離保証)
**確認方法:**
```bash
# 既存 piece に SSH 使用宣言があるか
grep -l 'Ssh\(Exec\|Upload\|Download\)' pieces/*.yaml | xargs -I {} grep -l 'allowed_ssh_connections' {}
# worker が ssh 接続を解決している経路
grep -n 'workspaceSshConnections' src/worker.ts src/engine/piece-runner.ts
```
### 12-F. config.yaml の SSH セクション (`SshRuntimeConfig`) を変更したとき
@@ -517,7 +520,7 @@ SSH Console は SshExec/Upload/Download とは別系統の対話的 PTY 経路
## 13. Scheduler から呼ばない手動オペレーション endpoint
以下の endpoint は **UI からの手動操作専用** で、scheduler / Routine / 自動化経路から起動できない設計になっている。Routine 側の payload schema にこれらの override を追加してはいけない (scheduled task の `allowed_tools` 境界が想定外に拡大するリスクのため)。
以下の endpoint は **UI からの手動操作専用** で、scheduler / Routine / 自動化経路から起動できない設計になっている。Routine 側の payload schema にこれらの override を追加してはいけない (scheduled task のツール境界〈ワークスペースのツールポリシーで決まる〉が想定外に拡大するリスクのため)。
- `POST /api/local/tasks/:id/continue` — 別 piece で task を続ける (handoff)
- 実装: `src/bridge/local-tasks-api.ts``/continue` ハンドラ
@@ -557,8 +560,67 @@ bwrap のマウント構成を変えた場合、セキュリティ境界が変
---
## 15. A2A 認可サーバー (`src/bridge/a2a/`)
外部エージェント連携(A2A)の OAuth2 認可サーバー関連を変更するときに連動が必要な箇所。
### DB テーブル・スキーマを変更するとき
- `src/db/schema.sql``CREATE TABLE``oidc_models` / `a2a_clients`)を更新(初期スキーマ)
- `src/db/migrate.ts` に冪等 `ALTER TABLE ADD COLUMN` を追加(既存 DB 用)。**dual-path 必須**(どちらか片方だけ更新するとテストが大量に落ちる)
- `src/db/repository.a2a.ts` / `src/bridge/a2a/oidc-adapter.ts` のクエリを合わせて修正
### `config.yaml` の `a2a` セクションを変更するとき
- YAML キーはスネークケース (`client_ttl_seconds`)、コード内はキャメルケース (`clientTtlSeconds`)
- `src/config.ts``transformKeys` が自動変換するため、読み取り側では キャメルケースのみ使う
- `config.yaml.example` にコメント付きで追記する
- `A2aConfig` interface`src/config.ts`)に対応フィールドを追加
### A2A ルーターを変更するとき
- `createConsentRouter` / `createA2aClientsAdminRouter` / `mountA2aOidc` はいずれも `src/bridge/server.ts` でマウントされる。新しい router を追加した場合は `server.ts` の配線を忘れずに
- 管理者専用エンドポイントは `requireAdmin` ミドルウェアを必ず通すこと
- ユーザー向け同意エンドポイントは `requireAuth` を通し、操作対象の `sub`subject)をセッションの userId と照合する
### A2A 公開スキル(スペース許可リスト)を変更するとき
- 公開スキルは `spaces.a2a_skills`(JSON 配列カラム)に保存される。スキーマ変更時は `schema.sql``migrate.ts` の**両方**(dual-path 必須)を更新すること
- 委任は `a2a_delegations` テーブルで管理し、`grant_id` で OAuth grant`oidc_models`)にリンクする。`grant_id` の整合性は外部キーではなくアプリ層で保証しているため、grant 削除時は対応する delegation も削除すること
- 委任フィルタは **`buildVisibilityWhere` の OR 句に追加しない**。タスク/ジョブの公開スコープ(private/org/public)とは独立したフィルタとして AND 交差で適用すること(OR に混ぜると cross-space IDOR の原因になる)
- Agent Card の公開エンドポイント(`GET /.well-known/agent.json`)とスペースオーナー向け API(公開スキル設定 PATCH)は、いずれも `a2a.enabled` ゲートを通過した場合のみ有効。ゲートを外したルートを追加しないこと
### A2A スキル実行(executor)を変更するとき
- `a2a_tasks` テーブルは `schema.sql``migrate.ts` の**両方**(dual-path 必須)を更新すること
- executor は `createLocalTask``createJob` の順に呼ぶ。`createLocalTask` 単体はジョブをエンキューしない(`createJob` で初めて Worker がポーリングで拾える状態になる)
- ジョブ完了の検知はポーリング(`a2a_tasks``status` カラムを定期参照)で行い、進捗は SSE で外部エージェントに返す
- 実行完了後、`output/` 以下のファイルは A2A Artifact として返却する
- 委任スコープは executor でも `computeEffectiveScope` を通じて再強制する。トークンの委任スコープがスペースの公開スキル設定を上回ることは許容しない(fail-closed
- `buildVisibilityWhere` の OR 句に委任条件を追加しないこと(AND 交差で適用する)
### 非ブロッキング送信 + reconciler`reconciler.ts` / `task-finalize.ts`)を変更するとき
- **非ブロッキング契約**: `message/send``params.configuration.blocking: false` は SDK ネイティブ機能。executor が初期 Task`submitted`)を publish した時点で SDK が即座にレスポンスを返す(完了を待たない)。外部エージェントは後から `tasks/get``params.id`)で最新状態を取得する。executor 自体を非ブロッキング化する改造は不要(SDK 側で完結)。
- **reconciler は単一起動**: `A2aTaskReconciler``server.ts``a2a.enabled` ゲート内から 1 インスタンスだけ `start()` する(多重起動は state churn の原因)。テストでは `start()` の interval を使わず `reconcileOnce()` を明示的に呼んで決定論化すること。
- **DB が source of truth**: reconciler は `a2a_tasks`(非 terminal 行)を `listNonTerminalA2aTasks` で走査し、リンク先の MAESTRO ジョブ状態(`getJob`)と突き合わせて収束させる。live executor が切断・再起動で消えても、reconciler だけでタスクを terminal 化できる(再起動耐性)。in-memory の per-task detached promise には依存しない。
- **終端化ロジックは共有**: `enumerateOutputArtifacts` / `finalizeStatusFromJob` / `TERMINAL_JOB_STATUSES``task-finalize.ts`)を executor と reconciler の**両方**が使う。ジョブ状態 → A2A state の写像や artifact 列挙を変えるときは、この共有ヘルパだけを直せば両経路に反映される(ロジックを片方に複製しないこと)。
- **冪等な状態一致スキップ**: reconciler は `payload.status.state` が算出後の state と一致する場合は再書き込みしない(`waiting_human → input-required` の churn 防止)。`saveA2aTask` は task id の upsert なので executor と reconciler が競合しても二重書きは安全。
- **委任カラムの保持**: reconciler が `saveA2aTask` を呼ぶ際は `payload.metadata``a2aDelegationId` / `a2aGrantId` / `a2aActingUserId``a2a_tasks``delegation_id` / `grant_id` / `acting_user_id` 列へ写す(`task-store.ts` の save 規約と同一)。列を増やすときは executor の初期 Task metadata・`SqliteA2aTaskStore.save``reconciler.persistTask` の 3 箇所を揃えること。
**確認方法:**
```bash
# A2A 関連テスト一括実行
npx vitest run src/bridge/a2a/ src/db/migrate.test.ts src/db/repository.a2a.test.ts src/db/repository.a2a-deleg.test.ts
# router のマウント漏れを確認
grep -n "mountA2aOidc\|createA2aClients\|createConsent" src/bridge/server.ts
# buildVisibilityWhere に委任条件が混入していないか確認
grep -n "a2a_delegations\|a2a_skills" src/db/repository.ts
```
---
## 自動検知の可能性
- **ツールモジュール登録漏れ**: `index.ts``tools-api.ts` のモジュール一覧を比較するスクリプトで CI チェック可能
- **piece の allowed_tools 不整合**: 全 piece の `allowed_tools` に含まれるツール名が実際の `TOOL_DEFS` に存在するか検証するスクリプトで CI チェック可能
- **code-review-graph**: `importers_of` で各ツールモジュールの参照元を列挙できるが、「tools-api.ts にも登録すべき」というルールの自動適用は困難。変更時の `detect_changes` + `get_impact_radius` で影響範囲の見落としを防ぐ用途が現実的
+16 -19
View File
@@ -2,8 +2,10 @@
The orchestrator can call tools hosted on external **MCP servers** (OAuth-secured
SaaS like Canva, or self-hosted servers with static API keys). Connected MCP
tools are exposed to pieces via `mcp__<server>__<tool>` names, and can be
allowlisted with `mcp__<server>__*` wildcards in `piece.allowed_tools`.
tools surface to tasks as `mcp__<server>__<tool>` names. Availability is
governed by connecting a server to the workspace (per-space), not by any piece
allowlist — the workspace tool policy always carries the `mcp__*` pattern and
the aggregator scopes results to the job's space and a valid token.
This document is the **operator runbook** for setting up, troubleshooting, and
maintaining MCP integrations. For internal design notes, see
@@ -107,29 +109,24 @@ sections — global at top (admin only), user's own below.
4. If using a private IP, ensure `mcp.allow_private_addresses: true` is set
(see Prerequisites).
## How tools flow into pieces
## How tools flow into tasks
The orchestrator caches `tools/list` results in `mcp_server_tools`, refreshed
on registration and on explicit admin refresh (no automatic TTL today). Piece
authors expose them via `allowed_tools`:
on registration and on explicit admin refresh (no automatic TTL today). There
is no per-piece allowlist: the workspace tool policy always carries the
`mcp__*` pattern, so every tool from a server connected to the job's space is
offered automatically. The aggregator scopes results to that space and a valid
token, so a task only ever sees tools from servers connected to its own
workspace.
```yaml
movements:
- name: design
allowed_tools:
- Read
- Write
- mcp__canva__* # all tools from server `canva`
- mcp__my-tools__lint # a specific tool from `my-tools`
```
The wildcard `mcp__<server>__*` expands to all currently-cached tools for that
server.
To expose a server's tools, connect the server to the workspace (**Settings →
MCP**). To *require* one, list it in the piece's `required_mcp` frontmatter —
that only controls job parking when no connection exists (see below).
## Job parking and resume
When a piece requires an MCP server (via `required_mcp` frontmatter or
discovered from `allowed_tools`) and the user has no connection, the worker
When a piece requires an MCP server (via `required_mcp` frontmatter) and the
user has no connection, the worker
parks the job:
- `jobs.status = 'waiting_human'`
+3 -3
View File
@@ -6,13 +6,13 @@
| | Piece | Skill |
|---|-------|-------|
| 役割 | **実行テンプレート**ツール権限・遷移フローを制御 | **参照知識** — 手順・規約・ガイドを提供 |
| 役割 | **実行テンプレート**movement の手順・遷移フローを制御 | **参照知識** — 手順・規約・ガイドを提供 |
| フォーマット | YAMLMAESTRO 固有) | MarkdownClaude Code / Codex 互換) |
| 選択 | piece-classifier が自動選択 | エージェントが ReadSkill で任意に参照 |
| 実行制御 | `allowed_tools` でツールを制限 | 制御なし(エージェントの判断に委ねる) |
| 実行制御 | movement の遷移フローを定義(ツール可否はワークスペースのツールポリシー側) | 制御なし(エージェントの判断に委ねる) |
**判断基準:**
- ツールの許可・禁止や movement フローを定義したい → **Piece**
- movement の手順・遷移フローを定義したい → **Piece**
- 手順書・コーディング規約・デバッグガイドをエージェントに参照させたい → **Skill**
## スキルの構造
+24 -67
View File
@@ -141,23 +141,9 @@ Then in the UI:
3. Optionally set `remote_path_prefix` (default `/`) — restricts upload/download paths
4. Click **Test** → first call returns `host_key_first_observe` with a fingerprint
5. **Verify** in the dialog (compare fingerprint with what you expect from `ssh-keyscan <host>`)
6. Add the connection's UUID to a piece's `allowed_ssh_connections`:
6. Enable the `ssh` tool category for the workspace in **Settings → Tools**. Once enabled, every connection registered to that workspace becomes reachable from any task in it — there is no per-piece opt-in and no piece YAML to edit.
```yaml
# pieces/example.yaml
name: ssh-example
movements:
- name: deploy
allowed_tools: [SshExec, SshUpload]
allowed_ssh_connections: ["abcd1234-..."]
rules:
- condition: done
next: COMPLETE
instruction: |
Use SshExec to ...
```
7. Test the piece via the normal task UI.
7. Test via the normal task UI. The worker resolves the workspace's SSH connections once per job (`connectionRepo.listBySpace(spaceId)`) and exposes that exact set to every movement.
## `config.yaml` Reference
@@ -361,60 +347,31 @@ an allowlist. SHA1-RSA and other weak algorithms are rejected before
the key is recorded (`host_key_alg_not_allowed`). This is hard-coded
in `src/ssh/session.ts` to avoid misconfiguration.
## Per-piece `allowed_ssh_connections`
## Workspace-scoped SSH connections
A piece's movement must explicitly opt in to SSH usage. The
piece-runner enforces three invariants:
SSH reachability is a **workspace** property, not a piece property. The
per-piece `allowed_ssh_connections` field was removed in the tool-consolidation
work; if it still appears on a submitted piece it is tolerated but ignored.
1. If a movement's `allowed_tools` contains any SSH tool name
(`SshExec`/`SshUpload`/`SshDownload`), `allowed_ssh_connections`
**must be declared** on that movement (even if empty)
2. The field must be an array of strings
3. Each entry must be `*` or a lowercase hex+hyphen UUID (≥ 8 chars)
The gate now works like this:
Lint failures abort piece load.
1. The workspace's tool policy must enable the `ssh` category
(**Settings → Tools**). If it does not, the SSH tools are not offered to any
movement.
2. When `ssh` is enabled, the worker resolves the connections registered to the
job's space (`connectionRepo.listBySpace(spaceId)`) once per job and applies
that set uniformly to every movement. This space-scoped list is the **sole**
connection gate.
3. The list is always an explicit set of connection IDs — never a `*` wildcard —
so a job can only ever reach connections registered in its own workspace.
Cross-space isolation is guaranteed.
4. The [access grant check](#access-grants) still applies on top: a user without
a grant for a given connection cannot use it even when the workspace exposes
it.
### Forms
```yaml
# Explicit allowlist (most common)
allowed_ssh_connections: ["abcd1234-...", "ef567890-..."]
# Wildcard (admin-style — use sparingly)
allowed_ssh_connections: ["*"]
# Deny-all (still allows SSH tool in allowed_tools but refuses every UUID)
allowed_ssh_connections: []
```
The `*` form skips the per-piece check but **does not** skip the
[access grant check](#access-grants). A user without a grant for a
given connection still cannot use it even when the piece says `*`.
### Example
```yaml
name: backup-rotation
description: Daily backup rotation on prod servers
movements:
- name: list
allowed_tools: [SshExec]
allowed_ssh_connections: ["abcd1234-...", "ef567890-..."]
instruction: |
List the existing backup files on each server.
rules:
- condition: ready to rotate
next: rotate
- name: rotate
allowed_tools: [SshExec, SshUpload]
allowed_ssh_connections: ["abcd1234-...", "ef567890-..."]
instruction: |
Rotate the oldest backup ...
rules:
- condition: done
next: COMPLETE
```
If SSH is enabled but the SSH subsystem is not initialised (e.g. a test
environment without the bridge), the resolved list is empty and every SSH call
is rejected (fail-closed).
## Access Grants
@@ -870,7 +827,7 @@ curl -X DELETE 'http://localhost:3000/api/ssh/admin/audit?older_than_days=90'
| `access denied (no_grant)` | User lacks grant for connection | Admin creates a grant, or user uses an owned connection |
| `access denied (disabled)` | Admin disabled the connection | Admin re-enables, or use different connection |
| `access denied (abuse_locked)` | Counter triggered | Wait for lock window, or admin force-unlocks |
| `piece "X" does not list connection Y` | `allowed_ssh_connections` missing UUID | Add UUID to the movement's `allowed_ssh_connections` |
| `connection Y is not registered to this workspace` | Connection not registered to the job's workspace | Register it under **Settings → SSH** and enable the `ssh` category in **Settings → Tools** |
| `host_key_first_observe` | First time exercising connection | Verify fingerprint in UI |
| `host_key_not_verified` | Key recorded but never verified | Click Verify in UI |
| `host_key_mismatch` | Server key changed | Investigate (legitimate rotation? MITM?), then Replace via UI |
+105
View File
@@ -0,0 +1,105 @@
# OS 起動時の自動起動(systemd)
MAESTRO を systemd サービスとして登録し、マシンの起動時に自動で立ち上げる手順。
プロセス監督・異常終了時の自動再起動・ログ集約(journald)も systemd が担う。
## 前提と役割分担
- **`scripts/server.sh`** … ビルド・手動運用・開発用。`start` は毎回ビルドしてから
`dist/main.js` を起動する。
- **systemd サービス** … boot 自動起動と本番のプロセス監督用。ビルドはしない
`node dist/main.js` を直接監督する)。
> **同時起動しないこと**。`server.sh start` が動かしているプロセスと systemd の
> サービスを両方立てると、同じ DB に対して二重起動になる(`instance-lock` が最悪の
> 破損は防ぐが、正しくない状態)。systemd に任せると決めたら、運用中の起動・停止は
> `systemctl start/stop` に統一し、`server.sh start` は使わない。
## インストール
まずビルドして `dist/` を最新にする(**root では実行しない**。アプリを動かすユーザーで)。
```bash
scripts/server.sh start # ビルド+起動を確認。この後 stop して systemd に委ねる
scripts/server.sh stop
```
生成されるユニットを先に確認したいときは `--print`(何もインストールしない)。
```bash
scripts/install-systemd.sh --print
```
### user サービス(既定・推奨)
root 不要。スクリプトを叩いたユーザー自身のサービスとして動く。引数なしがこれ。
```bash
scripts/install-systemd.sh
```
やっていること: `deploy/maestro.service`(テンプレート)を現在のチェックアウトから
埋めて `~/.config/systemd/user/maestro.service` に配置し、`systemctl --user enable
--now` する。ユニットに `User=` は付かない(起動ユーザーで動く)。
boot 時にログインなしで動かすため **linger** を有効化する(インストーラが自動で試す)。
自分のユーザーの linger は通常 root なしで有効化できる。ポリシー上できなかった場合だけ
警告が出るので、そのときは一度 `sudo loginctl enable-linger <user>` を実行する。
```bash
systemctl --user status maestro # 状態
journalctl --user -u maestro -f # ログ
systemctl --user restart maestro # 再起動(ビルドし直した後など)
```
### system サービス(マシン全体・root が使える場合)
ログインユーザーに依らずマシン起動時に立ち上げたいとき。インストールに sudo が要る。
```bash
scripts/install-systemd.sh --mode system --run-as <app-user>
```
`/etc/systemd/system/maestro.service` に配置し、`User=<app-user>` で動く。
状態・ログは `systemctl status maestro` / `journalctl -u maestro -f`
## よく使うオプション
| オプション | 意味 |
|---|---|
| `--print` / `--dry-run` | 生成されるユニットを表示するだけ。インストールしない |
| `--mode system` | マシン全体の system サービスとして入れる(既定は `user` |
| `--run-as <user>` | system モードのアプリ実行ユーザー |
| `--name <n>` | サービス名(既定 `maestro`)。複数インスタンス用 |
| `--no-start` | boot 用に enable するが、いますぐ起動はしない |
## デプロイ後の更新
新しいコードを反映するとき(user サービスの場合):
```bash
git pull
npm run build # または scripts/server.sh を使わず build のみ
systemctl --user restart maestro # system サービスなら sudo systemctl restart maestro
```
## 設定・環境変数
- ポートやモードなどはリポジトリ内 `config.yaml` を読む(アプリが自前で読み込む)。
- `.env` があれば `EnvironmentFile` として読み込む(任意)。ただし systemd の
`.env` 解釈は素朴な `KEY=value` 前提。`server.sh` が対応している凝ったクォートは
効かないので、systemd 経由で使う値は単純な形式にすること。
- 既定の実行モードは `AAO_MODE=worker`(フルオーケストレータ)。gateway として
動かすなら `.env``AAO_MODE=gateway` を書く。
## アンインストール
```bash
# system
sudo systemctl disable --now maestro
sudo rm /etc/systemd/system/maestro.service && sudo systemctl daemon-reload
# user
systemctl --user disable --now maestro
rm ~/.config/systemd/user/maestro.service && systemctl --user daemon-reload
```
+1 -1
View File
@@ -31,7 +31,7 @@
## なぜインストールが禁止か
- ジョブ実行環境はサンドボックス化されており、永続化されない
- 必要な機能は専用ツール(`allowed_tools` に列挙されたもの)で提供される
- 必要な機能は専用ツール(ワークスペースのツール設定で許可されたもの)で提供される
- インストールが必要 = ツールの設計が足りないので、ユーザーに報告して機能追加を依頼する
## 代替
+22 -2
View File
@@ -27,7 +27,9 @@ BrowseWeb({ url: "output/viewer.html" })
オプション:
- `waitFor`: 待機する CSS セレクタ(省略時は load イベント完了まで待機)
- `extractSelector`: 特定要素のテキストだけ抽出する CSS セレクタ
- `screenshot`: スクリーンショットを保存するファイル名(例: `"page.png"``output/page.png`
- `screenshot`: スクリーンショットを保存するファイル名(例: `"page.png"``output/page.png`。縦長ページは既定で分割保存(下記)
- `screenshotSegments`: `false` で分割せずフルページ 1 枚(既定: 分割あり)
- `screenshotMaxSegments`: 分割の最大枚数(既定 10
- `timeout`: タイムアウト(ms、デフォルト 60000)
### 2. アクションモード — 連続操作
@@ -47,11 +49,29 @@ BrowseWeb({
- `goto``url` で指定したページに遷移
- `click``selector` または `ref` で要素をクリック
- `fill``selector` または `ref` の input/textarea に `value` を入力
- `screenshot``value` で指定したファイル名で保存(省略時 `screenshot.png`
- `screenshot``value` で指定したファイル名で保存(省略時 `screenshot.png`。縦長ページは既定で分割保存(下記)。`segments: false` でフルページ 1 枚、`maxSegments` で枚数上限を調整
- `getText` — 全ページのスナップショット(ref 注釈付き)または `selector` 内のテキストを取得
- `wait``ms` ミリ秒待機(最大 30000
- `dumpHtml``ref` または `selector`(省略時 body)の outerHTML を取得(脱出口、後述)
### スクリーンショットの分割保存
縦長ページを 1 枚で撮ると画像が極端に縦長になり、細部が潰れて読み取りづらくなる。BrowseWeb は既定で、ページを **1 画面ぶん(ビューポート高さ)ごとに区切って複数枚**に分割保存する。
- 1 画面に収まるページはそのまま `output/page.png` の 1 枚(連番なし)
- 2 画面ぶん以上あるページは `output/page-001.png`, `output/page-002.png` … と連番で保存し、戻り値に全ファイルを列挙する
- 無限スクロールなどの暴走を防ぐため、既定で最大 10 枚まで(`screenshotMaxSegments` / アクションの `maxSegments` で変更)。上限で打ち切った場合は戻り値にその旨を表示する
- 分割せずフルページ 1 枚が欲しいときは `screenshotSegments: false`(基本モード)またはアクションの `segments: false`
```js
// 縦長レポートを 1 画面ぶんずつ分割して撮る(既定)
BrowseWeb({ url: "output/report.html", screenshot: "report.png" })
// → output/report-001.png, output/report-002.png, ...
// フルページ 1 枚で撮りたいとき
BrowseWeb({ url: "output/report.html", screenshot: "report.png", screenshotSegments: false })
```
## 長文ページの取得(preview + ファイル保存)
`getText` (selector 有無問わず) およびスナップショットの戻り値が **5000 文字を超える** 場合、フルテキストはワークスペースの `logs/browse/{ISO-timestamp}-{hash}.txt` に保存され、戻り値は **先頭 5000 文字 + 続きの取得方法案内** になる:
+50
View File
@@ -0,0 +1,50 @@
# GetFileProvenance / ListWorkspaceFiles
Workspace file provenance tools. In a persistent (shared) workspace, files can
outlive a single task: an old task's inputs and outputs stay visible to later
tasks. These tools let you tell WHO a file belongs to before you edit or rely on
it.
Both are META tools — always available regardless of the workspace tool policy.
## When to use
- Before editing a file whose relevance to the current task is unclear.
- When you see a file in `input/` or `output/` that you did not create this run.
- To find which files the current task actually produced vs. inherited.
## GetFileProvenance({ path })
Returns a compact record for one file (workspace-relative path, e.g.
`output/report.md`):
- `source_kind` — one of `user_input`, `agent_output`, `agent_edit`,
`bash_generated`, `subtask_output`, `imported_existing`, `unknown`.
- `created_by_task_id` — the task that first created/uploaded the file.
- `created_by_piece` / movement — the piece + movement that created it.
- `last_modified_by_task_id` + `last_modified_at`.
If there is no record (a file that pre-dates the ledger), you get an
`unknown`-equivalent message — treat the file as possibly belonging to another
task and verify its contents before overwriting.
## ListWorkspaceFiles({ path?, sourceKind?, createdByTaskId?, lastModifiedByTaskId?, includeUnknown?, limit? })
Returns a bounded listing (default 50, max 200 rows) of known files with a
one-line provenance summary each. It never returns file contents.
Filters:
- `path` — path prefix, e.g. `output/`.
- `sourceKind` — filter by source kind.
- `createdByTaskId` / `lastModifiedByTaskId` — filter by task.
- `includeUnknown` — set `false` to hide `unknown` / `imported_existing` files.
## Guidance
- Do NOT blindly edit files whose `source_kind` is `user_input` or whose
`created_by_task_id` differs from the current task. Prefer creating a new file
under `output/`.
- These tools report task/job IDs only — never task titles or user identities.
- The tools are scoped to the current run's workspace; you cannot inspect other
workspaces.
-1
View File
@@ -34,7 +34,6 @@ movements:
- name: gather
persona: ...
instruction: ...
allowed_tools: [Read, Write, ...]
rules:
- condition: ...
next: ...
+1 -1
View File
@@ -50,4 +50,4 @@ RunUserScript({ name: 'foo', params: { date: '2026-05-01' } })
- Scripts without a frontmatter block are listed with an empty description and no params.
- A parse error in one script is reported inline for that entry; other scripts are still listed.
- The tool is a META_TOOL — no need to add it to `allowed_tools` in piece YAML.
- The tool is a META_TOOL — always available regardless of the workspace tool policy.
+5 -2
View File
@@ -1,6 +1,6 @@
# MissionUpdate
タスクの **Mission Brief**`goal` / `done` / `open` / `clarifications`)を更新するメタツール。`allowed_tools` に書かなくても常時利用可能(META_TOOL)。
タスクの **Mission Brief**`goal` / `done` / `open` / `clarifications` / `user_constraints` / `decisions` / `current_focus`)を更新するメタツール。ワークスペースのツール設定に関係なく常時利用可能(META_TOOL)。
Mission Brief は毎 movement のシステムプロンプト冒頭に常に描画され、会話が長くなった後やステップをまたいでも消えない「参照点」になる。ユーザーも Overview タブから直接編集できる。
@@ -18,8 +18,11 @@ Mission Brief は毎 movement のシステムプロンプト冒頭に常に描
| `done` | これまでに完了した主要マイルストーン。箇条書き推奨 |
| `open` | 残っている作業・未解決のブロッカー。箇条書き推奨 |
| `clarifications` | ユーザーから追加された補足・制約。Markdown 可 |
| `user_constraints` | ユーザーが明示した恒久的な制約(「X は変えないで」「認証フローは維持」等)。可能なら `comment:N` / `transcript:N` を出典として添える |
| `decisions` | 検討の末に確定した設計判断とその理由。後で蒸し返さないための記録 |
| `current_focus` | いま取り組んでいる作業の焦点。movement をまたいで現在地を見失わないため |
すべて任意だが、最低1つは指定する必要がある(全フィールド未指定はエラー)。
すべて任意だが、最低1つは指定する必要がある(全フィールド未指定はエラー)。`user_constraints` / `decisions``SearchTaskConversation` で掘り起こした古い制約・判断を pin しておく置き場所に向く。
## 部分置換セマンティクス
+109
View File
@@ -0,0 +1,109 @@
# Read
テキストでもドキュメントでも、あらゆるファイルを 1 つの `Read` で読む。拡張子から
フォーマットを自動判定し、Office / PDF / メールは内部の抽出ハンドラへ委譲する。
以前は `ReadExcel` / `ReadDocx` / `ReadPdf` / `ReadPPTX` / `ReadMsg` が独立ツールとして
存在したが、「どのリーダーを選ぶか」で誤りが起きやすかったため `Read` に統合した。
今はどのファイルでも `Read({ file_path })` を呼べばよい。
> 画像ファイル(.png / .jpg / .gif / .webp / .bmp / .tif 等)は **Read では開けない**。
> VLM で内容を見る `ReadImage` を使う(意味的に別物のため統合していない)。
## フォーマット自動判定
| 拡張子 | 委譲先の抽出 | 返るもの |
|--------|--------------|----------|
| `.txt` / `.md` / `.csv` / コード等のテキスト | Read 本体(行 / バイト読み) | 生テキスト |
| `.xlsx` / `.xlsm` | Excel 抽出 | シートのセル値(+任意で装飾) |
| `.docx` | Word 抽出 | 本文+表 |
| `.pdf` | PDF 抽出 | ページ単位テキスト(+任意で検索) |
| `.pptx` | PowerPoint 抽出 | スライドのテキスト・表・ノート |
| `.msg` | Outlook メール抽出 | 件名 / 送受信者 / 本文、添付は `input/` に保存 |
> 旧バイナリ Office`.xls` / `.doc` / `.ppt` の CFB 形式)は**直接抽出できない**。
> Read すると「`.xlsx` / `.docx` / `.pptx` として保存し直してください」と案内されるので、
> 変換してから読む(`.xlsm` / `.docm` / `.pptm` のマクロ有効形式は読める)。
不透明なバイナリ(.zip / .exe / .db / 音声 / 動画 等)や、先頭バイトがバイナリと判定
されたファイルは拒否する(LLM コンテキスト破壊を防ぐため)。
## 共通パラメータ(inline schema
| パラメータ | 説明 |
|-----------|------|
| `file_path` | workspace 内の相対 / 絶対パス(必須) |
| `offset` | 読み始める行番号(0-indexed)。**テキスト読みのみ有効**(PDF/Office では無視される) |
| `limit` | 読む最大行数。**テキスト読みのみ有効**(PDF/Office では無視される) |
| `byte_offset` | 読み始めるバイト位置。改行の無い巨大ファイル向け。`offset` / `limit` と排他。テキスト読みのみ有効 |
| `byte_length` | 読むバイト数。`byte_offset` と併用。テキスト読みのみ有効 |
| `page_range` | **PDF のページ範囲**(例 `5-10` / `3`)。PDF を途中から / 一部だけ読むときは `offset` ではなくこれを使う |
大きすぎる出力は残コンテキスト予算に収まるよう自動で切り詰められ、続きの読み方が
注記される。
> **PDF/Office をずらして読むとき**: `offset` / `limit` / `byte_*` は**テキスト専用**で、
> PDF・Excel・Word・PowerPoint では黙って無視される(=常に先頭から返る)。PDF は
> `page_range`、Excel は `sheet` / `range` を使うこと。これらのフォーマットに
> `offset` / `limit` を渡すと、出力の先頭に正しいパラメータへの誘導注記が付く。
## フォーマット固有の詳細オプション(inline schema には無いが実行時に受理される)
`Read` はこれらを **raw のまま抽出ハンドラへ素通し**する。inline schema を軽く保つため
一覧には出していないので、必要なときはここを見て指定する。
### Excel.xlsx / .xlsm
| オプション | 説明 |
|-----------|------|
| `sheet` | シート名(省略時は全シート) |
| `range` | セル範囲(例 `A1:D10`、省略時はシート全体) |
| `max_cells` | 最大セル数(デフォルト 1000) |
| `include_styles` | `true` で背景色 / フォント / 罫線 / 書式 / 結合を `### Styles` として追記(デフォルト false) |
| `max_style_ranges` | `include_styles` 時の style range 上限(デフォルト 250 |
### Word.docx
| オプション | 説明 |
|-----------|------|
| `mode` | `text` / `text+tables`(デフォルト `text+tables` |
| `max_paragraphs` | 最大段落数(デフォルト 200) |
### PDF.pdf
| オプション | 説明 |
|-----------|------|
| `page_range`(別名 `pageRange` | ページ範囲(例 `1-5` / `3`、省略時は全ページ)。inline schema にも公開済み |
| `max_pages` | 抽出する最大ページ数 |
| `max_chars` | 返却する最大文字数(デフォルト 8000) |
| `query` | 指定するとマッチしたページのみを `grep -n` 風(周辺行付き)で返す |
| `query_mode` | `substring`(既定・大小無視の部分一致)/ `regex`(大小区別)/ `iregex`(大小無視の正規表現) |
| `context_lines` | `query` マッチ時の前後コンテキスト行数(デフォルト 2、最大 20) |
スキャン PDF(テキスト無し)は `PdfToImages` で PNG 化してから `ReadImage` で読む。
### PowerPoint.pptx
| オプション | 説明 |
|-----------|------|
| `slideRange` | スライド範囲(例 `1-5` / `3`、省略時は全スライド) |
### Outlook メール(.msg
- 件名・送信者・宛先・本文を抽出する。
- 添付ファイルは `input/` に保存し、保存先パスを出力に列挙する。
- 読み取り専用フェーズ(verify 等、edit 不可)では添付を保存せず、その旨を注記する。
- ファイル名衝突時は `name-1.ext` のように連番で退避し、既存ファイルは上書きしない。
## 使用例
```
Read({ file_path: "input/report.md" }) // テキスト
Read({ file_path: "input/sales.xlsx", sheet: "Q1", range: "A1:D20" })
Read({ file_path: "input/spec.docx", mode: "text" })
Read({ file_path: "input/manual.pdf", page_range: "5-10" }) // PDF の 5-10 ページ目
Read({ file_path: "input/manual.pdf", query: "認証", context_lines: 3 })
Read({ file_path: "input/deck.pptx", slideRange: "2-4" })
Read({ file_path: "input/mail.msg" }) // 添付は input/ へ
Read({ file_path: "logs/huge.log", byte_offset: 0, byte_length: 4096 })
```
## 関連ツール
- `ReadImage` — 画像を VLM で読む(Read とは別ツール)
- `PdfToImages` — PDF の各ページを PNG 化(スキャン PDF の前処理)
- `SplitExcelSheets` / `SplitDocxSections` — 巨大な表計算 / 文書をシート / 章単位に分割
+1 -1
View File
@@ -12,7 +12,7 @@ ReadImage({ file_path: "input/screenshot.png" })
## 動作要件
- 呼び出し時の worker が `vlm: true` で設定されている必要がある
- 設定がない場合、このツールは `allowed_tools` に書いてあっても利用不可(function definition から自動除外される)
- 設定がない場合、このツールはワークスペースで許可されていても利用不可(function definition から自動除外される)
## 用途
+33
View File
@@ -0,0 +1,33 @@
# ReadTaskConversation
`SearchTaskConversation` が返した ref の**前後を数件だけ**読み、当時の文脈を確認する META ツール(全 piece で常時利用可能)。検索を安価に保ちつつ、古い決定の周辺だけを覗くために使う。
## パラメータ
| 名前 | 必須 | 説明 |
|------|------|------|
| `ref` | ○ | `comment:<id>` または `transcript:<index>``SearchTaskConversation` の出力からコピーする) |
| `before` | | 前に含める件数。既定 2、上限 5 |
| `after` | | 後に含める件数。既定 2、上限 5 |
## 挙動
- `comment:<id>`: その id のコメントを中心に、コメント列の前後を返す
- `transcript:<index>`: その行を中心に、transcript の前後行を返す
- 現在タスクの会話だけを対象にする(別タスクは読めない)
- 出力は常に上限付き。中心のエントリには `←` マーカーが付く
## 出力例
```md
## ReadTaskConversation — comment #17 (前 2 / 後 2)
- comment:15 user/comment 2026-06-30T09:58:00Z
先に前提を共有します。
- comment:17 user/request 2026-06-30T10:00:00Z ←
keep the existing auth flow unchanged.
- comment:18 agent/progress 2026-06-30T10:02:00Z
了解しました。認証フローは触りません。
```
典型的な流れ: `SearchTaskConversation` でヒットを見つける → 気になる ref を `ReadTaskConversation` で開く → 恒久的な制約・判断なら `MissionUpdate` で pin する。
+11 -8
View File
@@ -1,10 +1,10 @@
# RequestTool
この movement で提示されていないツールがどうしても必要なときに、その要求を**記録**するためのメタツール(`allowed_tools` に書かなくても常時利用可能)。
この movement で提示されていないツールがどうしても必要なときに、その要求を**記録**し、可能ならユーザー承認を求めるためのメタツール(常時利用可能)。
## いつ使うか
- 依頼を達成するのに必要なツールが、現在の movement`allowed_tools` に無いと気づいたとき。
- 依頼を達成するのに必要なツールが、現在の movement に無いと気づいたとき。
- まず「本当にそのツールが要るか」を検討すること。多くの作業は既存のツール(`Bash` / `Read` / `WebSearch` 等)で代替できる。
## 引数
@@ -12,13 +12,16 @@
| 引数 | 必須 | 説明 |
|------|------|------|
| `name` | はい | 必要なツール名(例: `WebSearch`, `Bash`, `mcp__foo__bar` |
| `reason` | はい | なぜそのツールが必要かを具体的に。これがピース作者への記録に残る |
| `reason` | はい | なぜそのツールが必要かを具体的に。これが記録に残る |
## 重要: これは「要求の記録」であって「即時付与」ではない
## 承認フローと即時利用の可否
RequestTool を呼んでも、そのツールが**その場で使えるようにはならない**。要求は記録され、タスク詳細とピース集計に表示される。ピース作者が `allowed_tools` / `shared_tools` に追加すれば次回から使える。
ツールの可否は**ワークスペースのツールポリシー(設定 → ツール/SSH)**で決まる。RequestTool の挙動は実行環境で変わる:
要求したあとの進め方:
- **ユーザーが応答できる実行**(対話承認が有効): 承認を求めて停車し、**承認されればそのまま続行してそのツールを使える**。拒否されればツール無しで進む。
- **それ以外の実行**: その場では使えない。要求が記録され、タスク詳細とツール要求の集計に表示される。運用者がワークスペースの設定でそのツールを有効化すれば、次回から使える。
その場で使えない場合の進め方:
1. そのツール無しで達成できないか、もう一度考える。
2. どうしても無理なら `complete({status: "needs_user_input", missing_info: "...", why_no_default: "..."})` でユーザーに依頼する。
@@ -27,9 +30,9 @@ RequestTool を呼んでも、そのツールが**その場で使えるように
## 分類(記録される `category`
- **既に利用可能**: そのツールはこの movement で使える → 記録せず「そのまま呼んでください」と返る。
- **`requested`**: カタログに存在するがこの movement では未許可 → 設定漏れ候補として記録。
- **`requested`**: カタログに存在するがこの movement では未許可 → 対話承認が有効なら承認待ちに、無効なら設定漏れ候補として記録。
- **`unknown`**: そんなツールは存在しない(名前の誤り・能力ギャップ)→ **エラーを返す**(実在ツール名のみ要求可)。診断のため記録は残るが、承認待ちにはならない。エラーを受けたら実在するツールで進めること。
## 関連
足りないツールを呼んで弾かれた場合も、同じ記録に「受動捕捉(`blocked`)」として残る。ピース側の `shared_tools`(全 movement 共通ツール)も参照(`pieces/SCHEMA.md`
足りないツールを呼んで弾かれた場合も、同じ記録に「受動捕捉(`blocked`)」として残る。ワークスペースのツール許可の考え方は「[ツール](../../ui/src/content/help/16-tools.md)」も参照
+1 -1
View File
@@ -86,7 +86,7 @@ On task complete, a candidate patch will be saved as browser-macros/{name}.next.
## Notes
- The tool is a META_TOOL — it is available in every movement without listing it in `allowed_tools`.
- The tool is a META_TOOL — available in every movement regardless of the workspace tool policy.
- Use `ListUserAssets` first to discover available macros and their param specs.
- On macro failure, use `BrowseWeb` as a manual fallback.
- To run Python or other ad-hoc code, use the **Bash** tool (pip packages pre-baked).
+42
View File
@@ -0,0 +1,42 @@
# SearchTaskConversation
このタスクの過去の会話を検索し、出典 ref 付きの短い抜粋を返す META ツール(全 piece で常時利用可能)。長い/継続タスクで「前に何を言われたか」を思い出すために使う。**全文は返さない** — ヒットの一覧だけを返し、詳細は `ReadTaskConversation` で辿る。
## 検索対象
- **comments**: タスクのコメント(ユーザーの依頼・割り込み・エージェントの進捗など)。ref は `comment:<id>`
- **transcript**: `logs/transcript.jsonl`(ReAct スレッドの生ログ)。ref は `transcript:<行index>`
現在のタスクの会話だけを検索する。別タスクのログには到達できない(ツールは現在タスクに束縛されている)。
## パラメータ
| 名前 | 必須 | 説明 |
|------|------|------|
| `query` | ○ | 検索キーワード。部分一致・大文字小文字は無視 |
| `source` | | `comments` / `transcript` / `both`(既定 `both` |
| `author` | | `user` / `agent` / `system` で発言者を絞る |
| `kind` | | コメント種別で絞る(request/comment/interjection/result/ask/progress/handoff)。transcript には適用されない |
| `limit` | | 最大件数。既定 10、上限 50 |
## 使いどころ
- ユーザーに聞き直す前に、その情報が過去に述べられていないか確認する
- 以前に触れたファイル・下した決定を変更する前に、関連する制約を探す
- 文脈圧縮のあと、古い決定を思い出してから作業を再開する
## 出力例
```md
## Conversation Search Results (query: "auth", 2 hit(s))
- comment:17 user/request 2026-06-30T10:00:00Z
…keep the existing auth flow unchanged…
- transcript:42 user
…never touch the payment/auth code…
前後の文脈は ReadTaskConversation({ ref }) で確認できます。
```
ヒットで判明した恒久的な事実(制約・判断)は `MissionUpdate``user_constraints` / `decisions` に pin して、以降の movement でも見失わないようにする。
+87
View File
@@ -0,0 +1,87 @@
# SearchWorkspaceTasks
同じワークスペース(スペース)内の**他タスク**の会話を横断検索する META ツール(全 piece で常時利用可能)。`SearchTaskConversation` が現在タスクの会話だけを検索するのに対し、こちらは「同じスペースの他タスクで前に何を頼まれたか/どう進めたか」を思い出すための対になるツール。**全文は返さない** — ヒットの一覧(抜粋)だけを返す。前後の全文が必要なら `around_ref` で辿る。
## スコープ
- 検索対象は**同じスペースの他タスクのみ**。タスクの状態(実行中/完了/中断など)は問わない
- 呼び出し元がそのスペースの owner またはメンバーであることを前提に、DB クエリ自体がスペース境界で絞り込む。他スペースのタスクには物理的に到達できない
- no-auth(サインインしていない owner に紐づかない)実行や subtask 実行では、このツール自体が利用できない文脈になる。その場合はエラーではなく「このコンテキストでは利用できません」という案内文を返す
## 索引される内容(検索でヒットするテキスト)
検索結果の抜粋は、コメント本文そのものではなく**索引用に正規化・墨消しされたテキスト**。索引されるのは次のみ:
- ユーザーの依頼・割り込み(request / interjection
- エージェントの成果・確認(result / ask)、handoff
- エージェントの思考(progress の thinking
- movement の要約(progress の summary
- ツール実行は**ツール名のみ**(例: `tool: WebFetch`)。引数の値やツールの実行結果は一切索引されない
- 添付ファイルは**ファイル名のみ**を本文末尾に追記して索引する(中身は索引しない)
上記に当てはまらない種別(通常の comment、interjection_ack、未知の progress type など)は索引されない=検索にヒットしない。
## パラメータ
`query``around_ref` はどちらか一方のみ指定する(排他・両方またはどちらも未指定はエラー)。
| 名前 | 必須 | 説明 |
|------|------|------|
| `query` | ○(`around_ref` と排他) | 検索キーワード |
| `around_ref` | ○(`query` と排他) | `"comment:<id>"` 形式。指定した comment の前後を読む |
| `context` | | 各ヒットに付す前後コメント件数(grep -C 相当)。既定 0、最大 5 |
| `limit` | | 最大ヒット件数。既定 10、最大 30(`query` モードのみ) |
| `kind` | | コメント種別で絞り込む(request/comment/interjection/result/ask/progress/handoff |
| `author` | | 発言者で絞り込む |
| `task_id` | | 特定タスク ID に絞り込む |
出力の ref は `task:<id>` / `comment:<id>` の形式。
## 検索マッチング
日本語も含めた部分一致検索を trigram tokenize の FTS5 インデックスで行う。**3 文字未満の語は trigram で MATCH できないため、そのような語が含まれるクエリは自動的に LIKE(部分一致)にフォールバックする**(挙動は同じだが遅い)。
**複数語はスペース区切りの暗黙 AND(Google 的)**`ログイン 認証` と入力すると「ログイン」と「認証」を**両方含む**コメントにヒットする(語順・位置は問わない)。フレーズ一致ではないので、離れて出現していても両語があればヒットする。各語は内部で個別に引用してから AND で結合するため、`AND`/`OR`/`NEAR`/`*`/`"` などの記号が語に混ざっても FTS5 演算子としては解釈されない(=ユーザー側から任意の演算子検索や `OR` 検索を差し込むことはできない)。広く探したいときは 1 語で、絞りたいときは語を足す。
SQLite のビルドが FTS5 に対応していない環境では、検索機能自体が使えない旨のメッセージを返す(エラーにはしない)。
## ドリルダウン(`around_ref`)と検索結果の違い
**検索結果の抜粋は索引用に墨消しされた要約だが、`around_ref`(および `context` 指定時に付随する前後コメント)は対象コメントの生の本文をそのまま返す。** 同じスペースのメンバーはもともと製品 UI 上でその兄弟タスクの全文を読めるため、同じ信頼境界の中で全文を返すことは越権にならない。つまり「検索は軽量で墨消しされた概観、ドリルダウンは同じ信頼境界内でのフル情報」という設計。
## 出力例
```md
## Workspace Task Search Results (query: "認証", 2 hit(s))
- task:12 "ログイン画面の改修" / comment:88 user/request 2026-06-20T10:00:00Z
…既存の認証フローは変更しないでください…
- task:12 "ログイン画面の改修" / comment:95 agent/result 2026-06-20T11:30:00Z
…認証まわりは touch せず、UI だけ差し替えました…
前後の文脈をさらに確認するには around_ref: "comment:<id>" を指定してください。
```
```md
## Workspace Task Search — task:12 "ログイン画面の改修" (comment:88 前後 2 件)
- comment:86 user/comment 2026-06-20T09:55:00Z
質問なんですが、既存の認証部分は今回のスコープに入りますか?
- comment:88 user/request 2026-06-20T10:00:00Z ←
既存の認証フローは変更しないでください。UI 差し替えのみでお願いします。
- comment:90 agent/progress 2026-06-20T10:02:00Z
了解しました。認証ロジックには触れずに進めます。
```
## 使いどころ
- 同じスペースの別タスクで、この件についてすでに依頼・決定が交わされていないか確認する
- ユーザーに「前にどう言いましたっけ」と聞き直す前に、まず自分で探す
- 過去タスクの成果物・進め方を参考にする(他タスクの ID や作業内容が分かれば `task_id` で絞り込める)
ヒットで判明した恒久的な事実(制約・判断)は `MissionUpdate``user_constraints` / `decisions` に pin しておくと、以降の movement でも見失わない。
## 関連ツール
- `SearchTaskConversation` / `ReadTaskConversation` — 現在タスク自身の会話(コメント+transcript)を検索・閲覧する。他タスクへは到達できない。こちらは同じスペースの他タスクを横断検索する対
+5
View File
@@ -6,6 +6,11 @@ Piece の取得・編集には GetPiece / CreatePiece / UpdatePiece を使う。
利用可能なスキル一覧はシステムプロンプトの **Skills Index** に出る。本文を読むには
`ReadSkill({ name: "..." })` を呼ぶ。
> **重要: スキルを `Read` で直接読もうとしないこと。** スキルは workspace の外に
> 保存されており、`Read("skills/<name>/SKILL.md")` のようなパスは(ディレクトリ型を
> ReadSkill で展開する前は)存在しない。まず `ReadSkill({ name })` を呼ぶ。ディレクトリ型は
> それで `skills/<name>/` に展開され、以後は `skills/<name>/...` を `Read` で読める。
## ツール
- **InstallSkill** — スキルを保存する。通常は `content` に SKILL.md 全文(YAML frontmatter + 本文)を渡す。workspace 内に `SKILL.md``scripts/` 等を含むディレクトリを組み立て済みなら `sourcePath`(workspace 内の絶対パス)を渡す。`scope``user`(個人 or 共有ワークスペース)か `system`(全ユーザー共有・admin のみ)。
+31 -18
View File
@@ -1,10 +1,10 @@
# SSH Console Tools (SshConsoleEnsure / SshConsoleSend / SshConsoleSnapshot)
AI と人間が共有する SSH PTY セッションを操作する 3 ツール。1 タスクに 1 PTY セッションが対応し、`cd` / 環境変数 / foreground プロセス job をまたいで維持される。長時間の対話作業 / TUI (vim, top, less, tmux) / 複数ラウンドの調査向け。
AI と人間が共有する SSH PTY セッションを操作する 3 ツール。**1 タスクは接続ごとに 1 つ、複数の PTY セッションを同時に持てる**(例: 2 台のサーバーへ同時接続して並行作業)。各セッション内では `cd` / 環境変数 / foreground プロセス job をまたいで維持される。長時間の対話作業 / TUI (vim, top, less, tmux) / 複数ラウンドの調査向け。
単発コマンドだけなら **`SshExec`** (ssh-ops piece) のほうが軽い。本ツール群は対話的シェル + AI が画面を見続ける用途に最適化されている。
> **ユーザーが先にセッションを開いている場合がある**: タスク詳細の **Console タブ**から、ユーザーが接続を選んで自分でセッションを起動できる。その場合 `SshConsoleEnsure` は既存セッションをそのまま再利用する (`connection_id` を省略すれば active session が採用される)。「まず console を開く」操作を AI 側でやり直す必要はない
> **ユーザーが先にセッションを開いている場合がある**: タスク詳細の **Console タブ**から、ユーザーがタブを追加して自分でセッションを起動できる(複数タブを同時に開ける。→[SSH 接続](../../ui/src/content/help/14-ssh.md))。その場合 `SshConsoleEnsure` は既存セッションをそのまま再利用する。「まず console を開く」操作を AI 側でやり直す必要はない。ただし**タスクに複数セッションが同時に開いている場合、`connection_id` を省略して自動採用されるのは「直前にこのツール群で操作した接続 (current-connection pointer)」か「セッションが 1 つしかない」ときだけ**。2 つ以上開いていて pointer も無い状態で省略すると `ambiguous` エラーになる(詳細は後述)
## 典型的な flow (まずこれを真似る)
@@ -40,10 +40,10 @@ SshConsoleSnapshot({
| Param | Required | Description |
|---|---|---|
| `connection_id` | yes | UUID。piece の `allowed_ssh_connections` に含まれている必要がある。**label / hostname / 思い出した文字列で代用してはいけない** — 必ず `SshListConnections``id` を渡すこと |
| `connection_id` | yes | UUID。このワークスペースに登録された接続である必要がある。**label / hostname / 思い出した文字列で代用してはいけない** — 必ず `SshListConnections``id` を渡すこと |
| `cols` | no | 初回 open 時のターミナル幅。default `ssh.console.default_cols` (120) |
| `rows` | no | 初回 open 時のターミナル高さ。default `ssh.console.default_rows` (32) |
| `force_replace` | no | bool。default `false`既存 session が**別の** `connection_id`ある場合の挙動を制御 (下記参照) |
| `force_replace` | no | bool。default `false`**同じ** `connection_id`既存セッションがあるとき、それを閉じて開き直すかどうかを制御 (下記参照)。**別の** `connection_id` を渡した場合は `force_replace` に関係なく新規セッションが追加される |
Return:
```json
@@ -52,16 +52,28 @@ Return:
`reused: true` なら過去ターンから引き継いだ既存セッション (cd 等の state あり)。`false` なら今回新規 open。
### connection_id mismatch の挙動 (重要)
### 複数セッション: 別の接続を開くと「追加」される (重要)
同じ task で**別の** `connection_id` を渡した場合:
1 つのタスクは接続ごとに 1 つ、複数のセッションを同時に持てる。`SshConsoleEnsure` に**別の** `connection_id` を渡しても既存セッションは閉じられない — 単に新しい接続へのセッションが追加で開くだけ (add-not-replace)。**同じ** `connection_id` を渡した場合だけ「再利用か force_replace での再起動か」の分岐になる:
- `force_replace: false` (default) → エラー返却。レスポンスに **既存セッションの connection_id が含まれる** ので、それをそのまま使うか、本当に切り替えたければ次の呼び出しで `force_replace: true` を渡す
- `force_replace: true`旧セッションは `connection_change` 理由で閉じられ、新セッションが開く (旧 shell の state は失われる)
- 同じ `connection_id` + `force_replace: false` (default) → 既存セッションを再利用 (`alreadyActive: true`, `reused: true`)
- 同じ `connection_id` + `force_replace: true`**その接続のセッションだけ** `connection_change` 理由で閉じて開き直す (他の接続のセッションには影響しない。旧 shell の state は失われる)
**典型的なバグパターン**: ジョブをまたいで動作するエージェントが `connection_id` を覚えていなくて、
LLM の hallucination で適当な UUID を生成 → mismatch reject される、というケース。エラーメッセージの中に
正しい `connection_id` が出ているのでそれを使うか、Send/Snapshot で `connection_id` を省略する。
タスクあたりの上限 (既定 5、上限到達時は `task_session_cap`) やユーザー単位の上限 (`user_session_cap`、設定次第) に達すると新規オープンは拒否される。人間側も Console タブの「+ 接続」ボタンで同じように接続を追加でき、タブの ✕ で不要なセッションを閉じられる (→[SSH 接続](../../ui/src/content/help/14-ssh.md))。
### `connection_id` を省略できるのは 1 セッションのときだけ
`SshConsoleSend` / `SshConsoleRun` / `SshConsoleSnapshot``connection_id` を省略できるが、解決順は次のとおり:
1. `connection_id` を明示 → その接続のセッションを厳密に使う (無ければ `not found` エラー。勝手に新規 open はしない)
2. 省略時は、このツール群で直前に操作した接続 (current-connection pointer) が生きていればそれ
3. pointer も無く、タスクのセッションが**ちょうど 1 つ**ならそれを自動採用
4. タスクに**複数**セッションが同時に開いていて pointer も無い場合 → `ambiguous` エラー (`connection_id required (multiple sessions open: ...)`)。**この場合は `connection_id` を明示しないと動かない**
5. セッションが 1 つも無い場合 → `SshConsoleEnsure` で開くよう促すエラー
**複数接続で並行作業するときの鉄則**: 2 つ目以降のセッションを触るときは常に `connection_id` を明示する。省略に頼ってよいのは、セッションが 1 つしかないタスクか、直前にそのセッションを操作した直後だけ。
**典型的なバグパターン**: ジョブをまたいで動作するエージェントが `connection_id` を覚えておらず、複数セッションが開いている状態で省略 → `ambiguous` で reject される、というケース。エラーメッセージに出ている `connection_id` の一覧 (または `SshListConnections`) から正しいものを選んで明示する。
## SshConsoleSend
@@ -77,7 +89,7 @@ raw のまま送りたい (改行を付けない) ケース:
| Param | Required | Description |
|---|---|---|
| `connection_id` | no | UUID。**省略時はこの task の active session を自動採用 (推奨)**。明示する場合は active session の id と一致する必要があり、不一致なら reject (active id が surface される) |
| `connection_id` | no | UUID。**タスクにセッションが 1 つだけ、または直前にこのツール群で操作した接続 (pointer) がある場合は省略可**。複数セッションが開いていて pointer も無いと `ambiguous` エラー (下記「`connection_id` を省略できるのは 1 セッションのときだけ」参照) |
| `input` | yes | raw 文字列。LF / CRLF / control 文字 (`\x03` Ctrl-C, `\x04` Ctrl-D, `\x1b` Esc, `\t` Tab) を透過 |
| `wait_ms` | no | 送信後の screen_after 取得までの待ち時間 (default 500ms, max 5000ms) |
@@ -113,7 +125,7 @@ Return:
| Param | Required | Description |
|---|---|---|
| `connection_id` | no | UUID。**省略時はこの task の active session を自動採用 (推奨)**。明示する場合は active session の id と一致する必要があり、不一致なら reject |
| `connection_id` | no | UUID。**タスクにセッションが 1 つだけ、または直前にこのツール群で操作した接続 (pointer) がある場合は省略可**。複数セッションが開いていて pointer も無いと `ambiguous` エラー (上記「`connection_id` を省略できるのは 1 セッションのときだけ」参照) |
| `kind` | no | `screen` (デフォルト) — 現在の表示画面 / `scrollback` — それ以前を含む過去の出力 |
| `max_bytes` | no | scrollback の上限 (default 8192, max 65536)。tail から `max_bytes` バイト返す |
@@ -138,7 +150,7 @@ text は ANSI escape strip 済み (色 / cursor 移動シーケンスを除去)
| Param | Required | Description |
|---|---|---|
| `command` | yes | 実行するシェルコマンド |
| `connection_id` | no | UUID。**省略時はこの task の active session を自動採用 (推奨)** |
| `connection_id` | no | UUID。**タスクにセッションが 1 つだけ、または直前にこのツール群で操作した接続 (pointer) がある場合は省略可**。複数セッションが開いていて pointer も無いと `ambiguous` エラー |
| `timeout_ms` | no | タイムアウト (ms)。デフォルト 120000 (2分)、最大 600000 (10分)。タイムアウト時もコマンドは kill されない |
| `idle_ms` | no | 出力が `idle_ms` ms 途切れたら早期終了と判定する。0=無効 (デフォルト) |
@@ -176,12 +188,13 @@ Return:
| `host_key_*` | UI (Settings → User Folder → SSH Connections) で TOFU 検証してから再試行 |
| `command_rejected (builtin_deny / custom_deny)` | deny-list で reject。admin に許可パターン追加を相談 (ローカルで回避してはいけない) |
| `idle_timeout` / `duration_cap` | 古いセッションが閉じた。`SshConsoleEnsure` を再度呼んで開け直す |
| `connection_change` | 同 task で `force_replace: true` 付き Ensure が呼ばれた → 古いセッションが閉じた |
| `this task already has an active session on connection X (...)` | エラー文の中の **X が正しい id**。X を `connection_id` に使うか、Send/Snapshot で省略する。本当に切り替えたければ `force_replace: true` |
| `this task has an active session on connection X, not Y` | Send/Snapshot 側で id mismatch。X を使う or 省略する |
| `connection_change` | **同じ** `connection_id` `force_replace: true` 付き Ensure が呼ばれた → その接続のセッションだけ閉じて開き直した (別の接続を開いても発生しない) |
| `connection_id required (multiple sessions open: ...)` (ambiguous) | Send/Run/Snapshot で `connection_id` を省略したが、このタスクに複数セッションが同時に開いていて pointer も無い。エラー文中の一覧から狙った `connection_id` を明示する |
| `this task already has the maximum of N open console sessions` (`task_session_cap`) | タスクあたりのセッション上限 (既定 5) に到達。使っていない接続を `SshConsoleSend/Run` の完了を確認してからユーザーに Console タブでタブを閉じてもらうか、既存接続を使い回す |
| `you already have the maximum of N open console sessions` (`user_session_cap`) | ユーザー単位の上限 (設定されている場合) に到達。他タスクのセッションを閉じるまで新規オープンは拒否される |
| `maintenance` | admin の対応を待つ。`complete({status: 'needs_user_input', missing_info: 'SSH maintenance window'})` で停止 |
| `not initialised` | `ssh.enabled` または `ssh.console.enabled` が false / `MCP_ENCRYPTION_KEY` 未設定。admin に依頼 |
| `does not declare allowed_ssh_connections` | piece YAML の movement に `allowed_ssh_connections: ['*']` 等を追加する必要あり |
| `not registered to this workspace` | 対象接続がこのワークスペースに未登録。Settings → SSH で登録し、Settings → Tools で `ssh` カテゴリを有効化する |
## deny-list の限界
+6 -6
View File
@@ -6,12 +6,12 @@
| ツール | 用途 | 入力 |
|--------|------|------|
| `SshListConnections` | この movement で使える接続の UUID + label + host 一覧を取得 | (引数なし) |
| `SshListConnections` | このワークスペースで使える接続の UUID + label + host 一覧を取得 | (引数なし) |
| `SshExec` | リモートで shell 単一行を実行 | `connection_id`, `command`, (任意) `timeout_ms` |
| `SshUpload` | workspace → リモートへファイル転送 (SFTP) | `connection_id`, `local_path`, `remote_path`, (任意) `timeout_ms` |
| `SshDownload` | リモート → workspace へファイル取得 (SFTP) | `connection_id`, `remote_path`, `local_path`, (任意) `timeout_ms` |
転送系の 3 ツールは、接続側の `remote_path_prefix` 配下の絶対パスのみを受け付け、`workspace` 外への local パスは reject される。`connection_id` piece 側の `allowed_ssh_connections` に明示されている UUID のみ使用可能。
転送系の 3 ツールは、接続側の `remote_path_prefix` 配下の絶対パスのみを受け付け、`workspace` 外への local パスは reject される。`connection_id`、このワークスペースに登録されている接続の UUID のみ使用可能(ツール可否・接続スコープはワークスペースのツールポリシーが決める)
タスク本文に `connection_id` が記されていないときは、まず `SshListConnections` を呼んで該当の host / label の UUID を取得すること。
@@ -21,10 +21,10 @@
1. **`ssh.enabled: true`** が `config.yaml` で設定されている
2. **`MCP_ENCRYPTION_KEY`** 環境変数が 64 hex 文字 (= 32 バイト) で設定されている
3. **対象 connection の host key が verify 済**。新規作成直後は `host_key_verified_at IS NULL` 状態で SshExec/Upload/Download は `host_key_not_verified` で失敗する。SSH Connections パネル (Settings → User Folder → SSH Connections) で `/test` を実行 → 鍵 fingerprint を確認 → "Verify" ボタンで verify する
4. **piece の現在 movement で `allowed_ssh_connections` に当該 UUID が明示**されている (またはワイルドカード `*`)。空配列 `[]` は「SSH 使用するが許可なし」の deny 宣言とみなされ全 UUID が reject される
3. **対象 connection の host key が verify 済**。新規作成直後は `host_key_verified_at IS NULL` 状態で SshExec/Upload/Download は `host_key_not_verified` で失敗する。SSH 接続パネル (Settings → SSH) で `/test` を実行 → 鍵 fingerprint を確認 → "Verify" ボタンで verify する
4. **ワークスペースのツールポリシーで `ssh` カテゴリが有効化**され、対象 connection が **そのワークスペースに登録**されている (Settings → Tools で ssh を有効化、Settings → SSH で接続を登録)。接続スコープはワークスペース単位で、worker がジョブ開始時に解決して全 movement に一律適用する。piece 側の opt-in は不要(撤去済み)
不足時のエラーメッセージ例: `SshExec error: piece "ops" movement "exec" does not list connection abcd1234... in allowed_ssh_connections.`
不足時のエラーメッセージ例: `SshExec: connection abcd1234... is not registered to this workspace. Register it under Settings → SSH.`
## SshListConnections
@@ -32,7 +32,7 @@
SshListConnections({})
```
引数なし。現在の movement の `allowed_ssh_connections` + ジョブ owner の access grant を満たす接続だけを返す (admin 無効化 / piece 除外 / grant 無しは filter out)。
引数なし。このワークスペースに登録された接続のうち、ジョブ owner の access grant を満たすものだけを返す (admin 無効化 / grant 無しは filter out)。
戻り値 (JSON 文字列):
+1 -1
View File
@@ -2,7 +2,7 @@
ユーザーごとの常時指示書 `AGENTS.md` を読み書きするツール。`AGENTS.md` は各タスクのシステムプロンプトに自動注入される「このユーザーが常に守ってほしいこと」を書いた個人ファイル。memory(`UpdateUserMemory`)が事実の断片を貯めるのに対し、`AGENTS.md` は振る舞いの方針そのもの。
両ツールは META_TOOL(常時利用可能)。piece の `allowed_tools` に書かなくても使える。per-user 機能なので、認証済みユーザーのコンテキスト(`ctx.userId`)が必要。
両ツールは META_TOOL(常時利用可能)。ワークスペースのツール設定に関係なく使える。per-user 機能なので、認証済みユーザーのコンテキスト(`ctx.userId`)が必要。
## ReadUserAgents
+1 -1
View File
@@ -111,6 +111,6 @@ async function main({ context, params }) {
## Notes
- `WriteUserScript` is a META_TOOL — available in every movement without listing it in `allowed_tools`.
- `WriteUserScript` is a META_TOOL — available in every movement regardless of the workspace tool policy.
- After writing, use `RunUserScript` to immediately execute and verify the macro.
- Use `ListUserAssets` to see all macros currently in the folder.