docs(tasks-2): rewrite with concrete implementation plan based on existing code

Analyze current SubagentManager/SpawnTool/Sandbox implementation and
restructure TASKS-2 into 3 phases with 7 concrete tasks:
- Phase 1: ContainerMessage channels + AskConductorTool + escalation
- Phase 2: Deliberate Plan Mode (clarifying→review→executing)
- Phase 3: SubagentEnvironment MEMORY.md injection + guidance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-03-04 23:33:49 +09:00
parent c95dee28ef
commit 3233752715

View file

@ -1,34 +1,89 @@
# TASKS-2: Subagent Orchestration (Container Model) # TASKS-2: Subagent Orchestration (Container Model)
SubagentManager を Container ベースの Orchestrator に進化させる。 SubagentManager を Container ベースの Orchestrator に進化させる。
セッション管理の内部実装 (TASKS-3) には依存しない — 現行の SessionManager 上で動作させ、後から SessionStore に差し替える。 escalation chain質問→回答と Deliberate preset の plan mode を追加。
## 既存実装の現状
TASKS-2 の下地はかなり実装済み。以下を前提として差分のみ実装する。
### 実装済み ✅
| 要素 | ファイル | 状態 |
|---|---|---|
| **SubagentManager** | `pkg/tools/subagent.go` | Spawn/SubagentTool、タスク管理、goroutine lifecycle、WaitAll |
| **SpawnTool / SubagentTool** | `pkg/tools/spawn.go`, `subagent.go` | async/blocking 両パターン |
| **Preset 定義** | `pkg/tools/sandbox.go` | 5 preset、`SandboxConfig``ExecPolicy``AllowedToolsForPreset()` |
| **Registry 隔離** | `subagent.go` `buildPresetRegistry()` | preset 毎に専用 ToolRegistry + ExecTool インスタンス |
| **orch.Broadcaster** | `pkg/orch/broadcaster.go` | Spawn/StateChange/Conversation/GC イベント配信 |
| **SessionRecorder** | `pkg/tools/session_recorder.go` + `pkg/agent/session_recorder.go` | Fork/Turn/Completion/Report の DAG 記録 |
| **SubagentEnvironment 型** | `pkg/tools/sandbox.go` | Workspace/WorktreeDir/Background/Constraints/ContextFiles |
| **Async Callback** | `pkg/agent/loop.go` `processRequest` | spawn 完了 → MessageBus → conductor に結果注入 |
| **Orchestration Nudge** | `pkg/agent/loop.go` `buildOrchReminder()` | plan 実行中に spawn/subagent 使用を促すリマインダ |
### 未実装 ❌ → TASKS-2 スコープ
| # | 要素 | 概要 |
|---|---|---|
| 1 | **ContainerMessage channel** | subagent→conductor の question/status/result 双方向通信 |
| 2 | **Escalation chain** | subagent question → conductor 回答 or → human escalate |
| 3 | **Deliberate Plan Mode** | coder/worker/coordinator の clarifying→review→executing 状態遷移 |
| 4 | **SubagentEnvironment injection** | MEMORY.md からの自動コンテキスト注入 |
| 5 | **MEMORY.md Orchestration section** | conductor guidance に delegated/findings/decisions 追記 |
---
## 設計コンテキスト ## 設計コンテキスト
### アーキテクチャ ### 現行アーキテクチャspawn 非同期フロー)
``` ```
Conductor goroutine Conductor (main loop)
│ ContainerRequest (task, preset, environment) │ SpawnTool.Execute({"task":..., "preset":"scout"})
Container goroutine: provision → run → finalize ├─ manager.Spawn() → goroutine 起動 → AsyncResult 即返却
│ ContainerMessage (question / result / status)
│ [goroutine]
Conductor goroutine │ ├─ buildPresetRegistry(preset) ← 隔離された ToolRegistry
│ answer (question への回答) │ ├─ RunToolLoop(messages, tools) ← LLM + tool 実行ループ
▼ (わからなければ human に escalate) │ ├─ recorder.RecordCompletion()
Container goroutine (再開) │ ├─ reporter.ReportGC()
│ └─ callback() → msgBus.PublishInbound() → conductor に結果到着
▼ conductor 次イテレーションで結果を受信
```
### Container 追加後のアーキテクチャ
```
Conductor (main loop)
│ SpawnTool.Execute({"task":..., "preset":"coder"})
├─ manager.Spawn() → goroutine 起動 → AsyncResult 即返却
│ [goroutine: Container]
│ ├─ buildPresetRegistry(preset)
│ ├─ RunToolLoop() — subagent が ask_conductor tool を呼ぶ
│ │ └─ AskConductorTool.Execute()
│ │ ├─ outCh ← ContainerMessage{Type:"question", Content:...}
│ │ ├─ ← inCh (ブロック: conductor の回答を待つ)
│ │ └─ return ToolResult{ForLLM: answer}
│ │
│ ├─ recorder.RecordCompletion()
│ └─ callback() → conductor に結果到着
▼ conductor は outCh からの question を受信し回答 or escalate
``` ```
### escalation chain ### escalation chain
``` ```
subagent (clarifying) → question → conductor subagent (clarifying) → outCh: question → conductor 受信
→ conductor が答えられる: inCh に回答 → conductor が答えられる → inCh: answer → subagent 再開
→ conductor もわからない: message tool で human に投げ、回答を転送 → conductor もわからない → message tool で human に投げ → 回答を転送 → inCh
``` ```
### Presets (5種) ### Presets (5種、定義済み)
| preset | 性格 | write | exec | search | spawn | | preset | 性格 | write | exec | search | spawn |
|---|---|---|---|---|---| |---|---|---|---|---|---|
@ -38,190 +93,581 @@ subagent (clarifying) → question → conductor
| `worker` | Deliberate | o sandbox | build/package manager 系 | o | x | | `worker` | Deliberate | o sandbox | build/package manager 系 | o | x |
| `coordinator` | Deliberate | o sandbox | go/pnpm/bun/curl 系 | o | scout-worker のみ | | `coordinator` | Deliberate | o sandbox | go/pnpm/bun/curl 系 | o | scout-worker のみ |
**性格の分類:**
- **Exploratory** (scout/analyst): open-ended、見てきて報告。clarifying フェーズなし。
- **Deliberate** (coder/worker/coordinator): 成果物を作る。clarifying フェーズあり。
**npm は全 preset で禁止** (git worktree に node_modules が生じるため)。pnpm/bun は可。
**websearch/webfetch は全 preset で許可** (read-only のため)。
### exec allowlist regex
```go
var presetExecPatterns = map[string]string{
"scout": ``,
"analyst": `^(go\s+(test|vet)|git\s+(log|diff|status)|curl|wget|grep|find)\b`,
"coder": `^(` +
`go\s+(test|vet|fmt)|gofmt|goimports|golangci-lint|` +
`prettier|eslint|` +
`black|ruff|` +
`cargo\s+(test|fmt|clippy)|` +
`pnpm\s+(test|run\s+(test|lint|format))|` +
`bun\s+(test|run\s+(test|lint|format))|` +
`uv\s+run\s+` +
`)\b`,
"worker": `^(` +
`go\s+|` +
`pnpm\s+(install|add|run|test|build)|` +
`bun\s+(install|add|run|test|build)|` +
`uv\s+(run|sync|add|pip\s+install)|` +
`pip\s+install|` +
`cargo\s+` +
`)\b`,
"coordinator": `^(go\s+|pnpm\s+|bun\s+|curl|wget)\b`,
}
```
--- ---
## タスク一覧 ## Phase 1: Container + Escalation
### 1. SubagentContainer 実装 ### Task 1: ContainerMessage channel 追加
goroutine + channel でサブエージェントのライフサイクルを表現。 **目的:** subagent goroutine と conductor 間の双方向通信を既存の SubagentManager に追加。
新規ファイルは作らず SubagentTask を拡張する。
**対象ファイル:** `pkg/tools/subagent.go`
**変更内容:**
1. SubagentTask にチャネルフィールド追加:
```go ```go
// 既存の SubagentTask に追加
type SubagentTask struct {
// ... existing fields ...
// Container channels — nil for Exploratory presets
inCh chan string // conductor → subagent (回答)
outCh chan ContainerMessage // subagent → conductor (質問・結果)
}
type ContainerMessage struct { type ContainerMessage struct {
Type string // "question" | "result" | "status" Type string // "question" | "status"
Content string Content string
} TaskID string // 送信元タスクID
type SubagentContainer struct {
inCh chan string // conductor → subagent (回答)
outCh chan ContainerMessage // subagent → conductor (質問・結果・進捗)
cancel context.CancelFunc
} }
``` ```
- spawn (async): outCh を返して即リターン 2. `Spawn()` で Deliberate preset の場合のみチャネル生成:
- subagent (sync): その場で result を待つ
- goroutine 終了時に `defer orchestrator.active.Delete(id)` + `defer close(outCh)` で自動 GC
**対象ファイル:** `pkg/tools/container.go` (新規)
---
### 2. Orchestrator 実装
SubagentManager を置き換える上位構造。
- Container の生成・管理
- escalation chain の実装 (subagent → conductor → human)
- spawn/subagent の使い分けロジック
**対象ファイル:** `pkg/tools/orchestrator.go` (新規)
---
### 3. SubagentEnvironment (Context Injection)
conductor が subagent に渡すコンテキストの構造化。
```go ```go
type SubagentEnvironment struct { func (sm *SubagentManager) Spawn(...) (string, error) {
Workspace string // ... existing code ...
WorktreeDir string if isDeliberatePreset(preset) {
PlanTask string // MEMORY.md > Task: から自動抽出 task.inCh = make(chan string, 1)
PlanContext string // MEMORY.md ## Context から自動抽出 task.outCh = make(chan ContainerMessage, 4)
Commands string // MEMORY.md ## Commands から自動抽出 }
CurrentPhase string // ...
Background string // conductor が明示的に追加
Constraints string
ContextFiles []string
} }
``` ```
`inject_plan_context: true` で MEMORY.md からの自動注入を有効化。 3. `PendingQuestions()` メソッド追加 — conductor が polling で question を回収:
**対象ファイル:** `pkg/tools/container.go` または `pkg/tools/environment.go` (新規)
---
### 4. SandboxConfig enforcement
`ToolRegistry.Execute()` の入口で一括 enforcement。subagent は透過的に sandboxed になる。
```go ```go
type SandboxConfig struct { // PendingQuestions returns all pending questions from active subagents.
Preset string // Non-blocking: returns nil if no questions.
WriteRoot string func (sm *SubagentManager) PendingQuestions() []ContainerMessage {
AllowedTools map[string]bool sm.mu.RLock()
ExecPolicy *ExecPolicy defer sm.mu.RUnlock()
SpawnablePresets []string var questions []ContainerMessage
for _, t := range sm.tasks {
if t.outCh == nil { continue }
select {
case msg := <-t.outCh:
questions = append(questions, msg)
default:
}
}
return questions
} }
``` ```
workDir = worktreeDir として設定し、AI が隔離に気づかず振る舞う透過的隔離を実現。 4. `AnswerQuestion()` メソッド追加:
**対象ファイル:** `pkg/tools/sandbox.go` (既存拡張), `pkg/tools/registry.go` ```go
func (sm *SubagentManager) AnswerQuestion(taskID, answer string) error {
sm.mu.RLock()
t, ok := sm.tasks[taskID]
sm.mu.RUnlock()
if !ok { return fmt.Errorf("task %s not found", taskID) }
if t.inCh == nil { return fmt.Errorf("task %s has no question channel", taskID) }
select {
case t.inCh <- answer:
return nil
default:
return fmt.Errorf("task %s answer channel full", taskID)
}
}
```
**テスト:** `pkg/tools/subagent_container_test.go`
- Deliberate preset → outCh/inCh 生成確認
- Exploratory preset → outCh/inCh = nil 確認
- PendingQuestions の non-blocking 動作
- AnswerQuestion の正常系/異常系
--- ---
### 5. Preset enforcement ### Task 2: AskConductorTool
preset 定義と exec allowlist regex を SandboxConfig 経由で enforce。 **目的:** subagent goroutine 内で conductor に質問を送り、回答をブロック待ちする tool。
`pkg/tools/sandbox.go` に既存の定義があるが、ToolRegistry.Execute() での一括 enforcement がまだ。
**対象ファイル:** `pkg/tools/ask_conductor.go` (新規)
**実装:**
```go
type AskConductorTool struct {
taskID string
outCh chan<- ContainerMessage
inCh <-chan string
}
func NewAskConductorTool(taskID string, outCh chan<- ContainerMessage, inCh <-chan string) *AskConductorTool
func (t *AskConductorTool) Name() string { return "ask_conductor" }
func (t *AskConductorTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
question := args["question"].(string)
// 1. Send question to conductor
select {
case t.outCh <- ContainerMessage{Type: "question", Content: question, TaskID: t.taskID}:
case <-ctx.Done():
return ErrorResult("canceled while sending question")
}
// 2. Block waiting for answer (with context cancellation)
select {
case answer := <-t.inCh:
return SuccessResult(answer)
case <-ctx.Done():
return ErrorResult("canceled while waiting for answer")
}
}
```
**Parameters:**
```go
{
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "A clarifying question for the conductor about the task requirements, constraints, or approach."
}
},
"required": ["question"]
}
```
**Registry 登録:** `buildPresetRegistry()` で Deliberate preset のみ登録:
```go
// In buildPresetRegistry():
if task.inCh != nil && task.outCh != nil {
reg.Register(NewAskConductorTool(task.ID, task.outCh, task.inCh))
}
```
**テスト:** `pkg/tools/ask_conductor_test.go`
- question 送信 → answer 受信の正常フロー
- context キャンセルでの中断
- channel 閉鎖時の graceful error
--- ---
### 6. Subagent Plan Mode (in-memory) ### Task 3: Conductor 側の question 処理
Deliberate preset 用のミニ plan mode。MEMORY.md には触れない。 **目的:** conductor の main loop が subagent question を検出し、LLM に判断させる。
**対象ファイル:** `pkg/agent/loop.go`
**方式:** processRequest のイテレーションごとに pending questions をチェック。
questions があれば system message として LLM に注入。
**変更内容:**
1. `runAgentLoop()` のイテレーション先頭に question チェック追加:
```go
// In runAgentLoop(), after getting LLM response:
if agent.SubagentMgr != nil {
questions := agent.SubagentMgr.PendingQuestions()
for _, q := range questions {
// LLM の次の応答で answer_subagent tool call を期待
messages = append(messages, providers.Message{
Role: "user",
Content: fmt.Sprintf("[Subagent %s asks]: %s\n\nRespond using the answer_subagent tool.",
q.TaskID, q.Content),
})
}
}
```
2. `AnswerSubagentTool` 追加 (`pkg/tools/answer_subagent.go`、新規):
```go
type AnswerSubagentTool struct {
manager *SubagentManager
}
func (t *AnswerSubagentTool) Name() string { return "answer_subagent" }
func (t *AnswerSubagentTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
taskID := args["task_id"].(string)
answer := args["answer"].(string)
if err := t.manager.AnswerQuestion(taskID, answer); err != nil {
return ErrorResult(err.Error())
}
return SuccessResult("Answer delivered to " + taskID)
}
```
**Parameters:**
```go
{
"type": "object",
"properties": {
"task_id": {"type": "string", "description": "The subagent task ID that asked the question"},
"answer": {"type": "string", "description": "Your answer to the subagent's question"}
},
"required": ["task_id", "answer"]
}
```
3. conductor の ToolRegistry に `answer_subagent` 登録:
```go
// In registerSharedTools(), after creating SubagentManager:
agent.Tools.Register(tools.NewAnswerSubagentTool(subagentManager))
```
**escalation (conductor → human):**
conductor LLM が answer_subagent で答えられないと判断した場合:
- message tool で human に質問を転送
- human 回答後に conductor が answer_subagent を呼ぶ
- **追加実装不要** — 既存の message tool + LLM 判断で自然に発生
**テスト:** `pkg/agent/loop_escalation_test.go`
- question 注入 → LLM が answer_subagent を呼ぶフローmock LLM
- 複数 subagent から同時に question が来るケース
---
## Phase 2: Deliberate Plan Mode
### Task 4: SubagentPlanState
**目的:** Deliberate preset (coder/worker/coordinator) の subagent に clarifying→review→executing の状態遷移を追加。
**対象ファイル:** `pkg/tools/subagent.go` (SubagentTask に追加)
**変更内容:**
1. 状態定義:
```go ```go
type SubagentPlanState int type SubagentPlanState int
const ( const (
PlanStateNone SubagentPlanState = iota // Exploratory preset PlanStateNone SubagentPlanState = iota // Exploratory — 即実行
PlanStateClarifying // 目的・制約を確認中 PlanStateClarifying // 目的・制約を確認中
PlanStateReview // conductor の承認待ち PlanStateReview // conductor の承認待ち
PlanStateExecuting // 実行中 PlanStateExecuting // 実行中
PlanStateCompleted // 完了
) )
type SubagentPlan struct { // SubagentTask に追加
State SubagentPlanState type SubagentTask struct {
Goal string // ... existing ...
Approach []string PlanState SubagentPlanState
QA []QAItem PlanGoal string // clarifying で確定した目標
mu sync.Mutex PlanSteps []string // review で提出したステップ
} }
``` ```
SubagentContainer がフィールドとして保持。goroutine 終了とともに消える。 2. `runTask()` の Deliberate preset フロー変更:
**System prompt 使い分け:** ```go
- Deliberate: clarifying → review → executing の3段階 func (sm *SubagentManager) runTask(ctx context.Context, task *SubagentTask, preset string, callback AsyncCallback) {
- Exploratory: 即座に探索開始、判断は自律的に p := Preset(preset)
--- if isDeliberatePreset(p) {
// Phase A: Clarifying — ask_conductor で不明点を解消
task.PlanState = PlanStateClarifying
clarifyResult := sm.runClarifyingPhase(ctx, task, p)
if ctx.Err() != nil { return }
### 7. MEMORY.md Orchestration Section // Phase B: Review — conductor に計画を提出、承認待ち
task.PlanState = PlanStateReview
approved := sm.submitPlanForReview(ctx, task, clarifyResult)
if !approved || ctx.Err() != nil { return }
conductor の guidance に Orchestration セクションの使い方を追記。 // Phase C: Executing — 承認済み計画を実行
task.PlanState = PlanStateExecuting
```markdown sm.runExecutingPhase(ctx, task, p, callback)
## Orchestration } else {
// Exploratory — 既存フロー(即実行)
### Delegated sm.runExploratoryTask(ctx, task, p, callback)
- coder-1 (coder): rate limiter 実装 → Phase 2 Step 1 }
}
### Findings
- pkg/auth は middleware パターン (scout-1)
### Decisions
- auth: JWT を選択 (外部依存なし、scout-2 推奨)
``` ```
conductor は spawn 後に Delegated に記録、結果受信後に Findings に追記、方向選択時に Decisions に記録。 3. 各フェーズの system prompt テンプレート:
**対象ファイル:** `pkg/agent/context.go` (guidance 追記) ```go
func clarifyingSystemPrompt(task, preset string) string {
return fmt.Sprintf(`You are a %s subagent. Your task:
%s
PHASE: CLARIFYING
You must understand the task fully before executing.
- If anything is unclear, use ask_conductor to ask questions.
- When you are confident you understand the task, call submit_plan with your goal and approach.
- Do NOT write code or make changes yet.`, preset, task)
}
func executingSystemPrompt(task, preset, plan string) string {
return fmt.Sprintf(`You are a %s subagent. Your task:
%s
APPROVED PLAN:
%s
PHASE: EXECUTING
Execute the approved plan step by step. You may use your tools to implement.
Do not deviate from the plan without asking via ask_conductor.`, preset, task, plan)
}
```
**テスト:** `pkg/tools/subagent_plan_test.go`
- Deliberate preset → clarifying→review→executing 遷移
- Exploratory preset → PlanStateNone のまま即実行
- clarifying 中に context cancel → graceful exit
--- ---
### Task 5: SubmitPlanTool + review フロー
**目的:** subagent が clarifying 完了後に計画を conductor に提出する tool。
**対象ファイル:** `pkg/tools/submit_plan.go` (新規)
**実装:**
```go
type SubmitPlanTool struct {
taskID string
outCh chan<- ContainerMessage
inCh <-chan string
}
func (t *SubmitPlanTool) Name() string { return "submit_plan" }
func (t *SubmitPlanTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
goal := args["goal"].(string)
steps := args["steps"] // []any → []string
planText := fmt.Sprintf("Goal: %s\nSteps:\n", goal)
for i, s := range steps.([]any) {
planText += fmt.Sprintf(" %d. %s\n", i+1, s)
}
// Send plan to conductor for review
select {
case t.outCh <- ContainerMessage{Type: "plan_review", Content: planText, TaskID: t.taskID}:
case <-ctx.Done():
return ErrorResult("canceled")
}
// Wait for approval/rejection
select {
case response := <-t.inCh:
if response == "approved" || strings.HasPrefix(response, "approved") {
return SuccessResult("Plan approved. Proceed with execution.")
}
return SuccessResult("Plan rejected: " + response + "\nRevise your plan and resubmit.")
case <-ctx.Done():
return ErrorResult("canceled while waiting for review")
}
}
```
**Parameters:**
```go
{
"type": "object",
"properties": {
"goal": {"type": "string", "description": "One-sentence goal of the task"},
"steps": {"type": "array", "items": {"type": "string"}, "description": "Ordered implementation steps"}
},
"required": ["goal", "steps"]
}
```
**Conductor 側:** `review_subagent_plan` tool 追加 (`pkg/tools/answer_subagent.go` に同居):
```go
type ReviewSubagentPlanTool struct {
manager *SubagentManager
}
func (t *ReviewSubagentPlanTool) Name() string { return "review_subagent_plan" }
func (t *ReviewSubagentPlanTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
taskID := args["task_id"].(string)
decision := args["decision"].(string) // "approved" or "rejected: reason"
if err := t.manager.AnswerQuestion(taskID, decision); err != nil {
return ErrorResult(err.Error())
}
return SuccessResult("Review delivered to " + taskID)
}
```
**Question injection:** conductor の LLM には plan_review も question と同じ経路で注入:
```go
// PendingQuestions already returns plan_review messages
// Conductor LLM sees:
// "[Subagent coder-1 submits plan for review]:
// Goal: ...
// Steps: ...
// Use review_subagent_plan to approve or reject."
```
**テスト:** `pkg/tools/submit_plan_test.go`
- submit → approved フロー
- submit → rejected → 再 submit → approved フロー
- context cancel 中断
---
## Phase 3: Context Injection + Guidance
### Task 6: SubagentEnvironment injection
**目的:** `SubagentEnvironment` (既存型) に MEMORY.md からの自動コンテキスト注入を実装。
**対象ファイル:** `pkg/tools/subagent.go` (runTask 内)
**変更内容:**
1. `extractPlanContext()` ヘルパー追加:
```go
// extractPlanContext reads MEMORY.md and extracts Task/Context/Commands sections.
func extractPlanContext(workspace string) (task, context, commands string) {
data, err := os.ReadFile(filepath.Join(workspace, "MEMORY.md"))
if err != nil { return }
// Parse markdown sections:
// - "Task:" or "# Task" line → task
// - "## Context" section → context
// - "## Commands" section → commands
// Simple line-based parser, no external deps
return
}
```
2. `runTask()` で Environment を system prompt に注入:
```go
func (sm *SubagentManager) runTask(...) {
// ... existing ...
env := SubagentEnvironment{
Workspace: sm.workspace,
WorktreeDir: sm.workspace, // TODO: worktree 有効時は worktree path
}
// Auto-inject from MEMORY.md if available
env.PlanTask, env.PlanContext, env.Commands = extractPlanContext(sm.workspace)
// Build system prompt with environment
systemPrompt := buildSubagentSystemPrompt(preset, task.Task, env)
// ...
}
```
3. `buildSubagentSystemPrompt()` に environment セクション追加:
```go
func buildSubagentSystemPrompt(preset, task string, env SubagentEnvironment) string {
var sb strings.Builder
// ... existing preset-based prompt ...
if env.PlanTask != "" || env.PlanContext != "" {
sb.WriteString("\n## Project Context\n")
if env.PlanTask != "" {
sb.WriteString("Current task: " + env.PlanTask + "\n")
}
if env.PlanContext != "" {
sb.WriteString(env.PlanContext + "\n")
}
}
if env.Commands != "" {
sb.WriteString("\n## Build Commands\n" + env.Commands + "\n")
}
if env.Constraints != "" {
sb.WriteString("\n## Constraints\n" + env.Constraints + "\n")
}
return sb.String()
}
```
**テスト:** `pkg/tools/subagent_env_test.go`
- MEMORY.md ありの場合にセクション抽出確認
- MEMORY.md なし → 空文字列、エラーなし
---
### Task 7: MEMORY.md Orchestration section guidance
**目的:** conductor が spawn/結果受信/方向決定時に MEMORY.md の Orchestration セクションを更新するよう guidance を追加。
**対象ファイル:** `pkg/agent/context.go`
**変更内容:**
既存の `buildOrchGuidance()` に Orchestration セクションの使い方を追記:
```go
func buildOrchGuidance() string {
return `## Orchestration Memory
When orchestrating subagents, maintain an "## Orchestration" section in MEMORY.md:
### Delegated
Track active delegations:
- <label> (<preset>): <task summary><plan phase/step>
### Findings
Record discoveries from subagent reports:
- <finding> (<source subagent>)
### Decisions
Record architectural/design decisions:
- <decision> (<rationale>, <recommending subagent>)
Update this section after each spawn and after receiving each subagent report.`
}
```
**テスト:** 既存の context_test.go で guidance 文字列に "Orchestration" が含まれることを確認。
---
## ファイル一覧
| ファイル | Action | Phase |
|---|---|---|
| `pkg/tools/subagent.go` | 拡張: ContainerMessage, inCh/outCh, PendingQuestions, AnswerQuestion, PlanState | 1, 2 |
| `pkg/tools/ask_conductor.go` | 新規: AskConductorTool | 1 |
| `pkg/tools/answer_subagent.go` | 新規: AnswerSubagentTool, ReviewSubagentPlanTool | 1, 2 |
| `pkg/tools/submit_plan.go` | 新規: SubmitPlanTool | 2 |
| `pkg/agent/loop.go` | 拡張: question polling + 注入, answer/review tool 登録 | 1, 2 |
| `pkg/agent/context.go` | 拡張: Orchestration guidance | 3 |
| `pkg/tools/subagent_container_test.go` | 新規 | 1 |
| `pkg/tools/ask_conductor_test.go` | 新規 | 1 |
| `pkg/tools/subagent_plan_test.go` | 新規 | 2 |
| `pkg/tools/submit_plan_test.go` | 新規 | 2 |
| `pkg/tools/subagent_env_test.go` | 新規 | 3 |
## Phase 間の依存
```
Phase 1: Container + Escalation (Task 1-3)
↓ (inCh/outCh が前提)
Phase 2: Deliberate Plan Mode (Task 4-5)
↓ (subagent の system prompt 拡張が前提)
Phase 3: Context Injection + Guidance (Task 6-7) ← 独立して先行も可
```
## 完了基準 ## 完了基準
- `go test ./...` 全パス - `go test ./...` 全パス
- scout/analyst/coder preset で spawn → 結果受信 → conductor 応答の E2E フロー動作 - Exploratory preset (scout/analyst): 既存フロー変更なし、即実行
- Deliberate preset の clarifying → review → executing フロー動作 - Deliberate preset (coder/worker): clarifying → review → executing の 3 段階動作
- SandboxConfig による exec 制限が全 preset で正しく enforcement - `ask_conductor` → conductor LLM 回答 → subagent 再開 のラウンドトリップ
- escalation chain (subagent → conductor → human) が動作 - conductor が回答不可 → message tool で human escalate → 回答転送
- MEMORY.md Orchestration セクションが conductor guidance に含まれる
- 既存の spawn/subagent E2E フローExploratoryに regression なし