legion-cc 0.20.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -97,6 +97,7 @@ When your work is complete, execute these steps IN ORDER:
97
97
 
98
98
  1. **Write report to task**: `TaskUpdate(taskId: "<your-task-id>", status: "completed", description: "<your full structured report>")`
99
99
  2. **Notify teammates** (if needed): `SendMessage(type: "message", recipient: "<teammate-name>", content: "<report or summary>", summary: "<brief>")`
100
+ 3. **Respond to shutdown** (if received): If a `shutdown_request` message arrives, call `SendMessage(type: "shutdown_response", request_id: "<requestId>", approve: true)`. Do NOT wait actively for it — you may stop after steps 1-2.
100
101
 
101
102
  Your task ID and team name are provided in your spawn prompt. If not provided, check `TaskList` to find your assigned task.
102
103
 
@@ -92,6 +92,7 @@ When your work is complete, execute these steps IN ORDER:
92
92
 
93
93
  1. **Write report to task**: `TaskUpdate(taskId: "<your-task-id>", status: "completed", description: "<your full structured report>")`
94
94
  2. **Notify teammates** (if needed): `SendMessage(type: "message", recipient: "<teammate-name>", content: "<report or summary>", summary: "<brief>")`
95
+ 3. **Respond to shutdown** (if received): If a `shutdown_request` message arrives, call `SendMessage(type: "shutdown_response", request_id: "<requestId>", approve: true)`. Do NOT wait actively for it — you may stop after steps 1-2.
95
96
 
96
97
  Your task ID and team name are provided in your spawn prompt. If not provided, check `TaskList` to find your assigned task.
97
98
 
@@ -99,6 +99,7 @@ When your work is complete, execute these steps IN ORDER:
99
99
 
100
100
  1. **Write report to task**: `TaskUpdate(taskId: "<your-task-id>", status: "completed", description: "<your full context document>")`
101
101
  2. **Notify teammates** (if needed): In quick pipeline, send Bundle Section 3 to architect via `SendMessage`. In cycle pipeline, TaskUpdate is sufficient.
102
+ 3. **Respond to shutdown** (if received): If a `shutdown_request` message arrives, call `SendMessage(type: "shutdown_response", request_id: "<requestId>", approve: true)`. Do NOT wait actively for it — you may stop after steps 1-2.
102
103
 
103
104
  Your task ID and team name are provided in your spawn prompt. If not provided, check `TaskList` to find your assigned task.
104
105
 
@@ -113,6 +113,7 @@ When your work is complete, execute these steps IN ORDER:
113
113
 
114
114
  1. **Write report to task**: `TaskUpdate(taskId: "<your-task-id>", status: "completed", description: "<your full implementation report>")`
115
115
  2. **Notify teammates** (if needed): `SendMessage(type: "message", recipient: "<teammate-name>", content: "<summary>", summary: "<brief>")`
116
+ 3. **Respond to shutdown** (if received): If a `shutdown_request` message arrives, call `SendMessage(type: "shutdown_response", request_id: "<requestId>", approve: true)`. Do NOT wait actively for it — you may stop after steps 1-2.
116
117
 
117
118
  Your task ID and team name are provided in your spawn prompt. If not provided, check `TaskList` to find your assigned task.
118
119
 
@@ -122,10 +122,10 @@ Cap at 10.
122
122
  3. Spawn 2 teammates **in parallel** — include task IDs in prompts:
123
123
  ```
124
124
  Agent(team_name: ..., name: "context-curator", subagent_type: "context-curator",
125
- prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-1-id>\n\nCollect project context. Write Bundle Section 3 following the ## Bundle Section 3: Project Context schema (Identity, Rules & Conventions, Build/Run/Test, Code Style, Config & Environment, Key Constraints). CRITICAL: MUST NOT exceed 400 lines.\n\n## End of Work Protocol (MANDATORY)\n1. TaskUpdate(taskId: '<task-1-id>', status: 'completed', description: '<Bundle Section 3>')\n2. SendMessage(type: 'message', recipient: 'architect', content: '<Bundle Section 3>', summary: 'Bundle Section 3 ready')\nBoth steps REQUIRED.")
125
+ prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-1-id>\n\nCollect project context. Write Bundle Section 3 following the ## Bundle Section 3: Project Context schema (Identity, Rules & Conventions, Build/Run/Test, Code Style, Config & Environment, Key Constraints). CRITICAL: MUST NOT exceed 400 lines.\n\n## End of Work Protocol (MANDATORY)\n1. TaskUpdate(taskId: '<task-1-id>', status: 'completed', description: '<Bundle Section 3>')\n2. SendMessage(type: 'message', recipient: 'architect', content: '<Bundle Section 3>', summary: 'Bundle Section 3 ready')\nBoth steps REQUIRED.\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
126
126
 
127
127
  Agent(team_name: ..., name: "architect", subagent_type: "architect",
128
- prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-2-id>\n\nWait for Bundle Section 3 from context-curator via SendMessage. Then write Bundle Sections 1+2 combined (Architecture + Code Analysis, budget: 300-600 lines) following the schemas:\n- ## Bundle Sections 1+2: Architecture + Code Analysis\n- Subsections: 1.1 Approach, 1.2 Change Boundaries, 1.3 Architecture Decisions, 2.1 Entry Points, 2.2 Files to Change, 2.3 Risks\n\nAND produce a lightweight Execution Pack (Plan + Diff + Acceptance Criteria + Rollback). Write both as your task output via TaskUpdate.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-2-id>', status: 'completed', description: '<Bundle Sections 1+2 + Execution Pack>')\nDo NOT go idle without calling TaskUpdate.")
128
+ prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-2-id>\n\nWait for Bundle Section 3 from context-curator via SendMessage. Then write Bundle Sections 1+2 combined (Architecture + Code Analysis, budget: 300-600 lines) following the schemas:\n- ## Bundle Sections 1+2: Architecture + Code Analysis\n- Subsections: 1.1 Approach, 1.2 Change Boundaries, 1.3 Architecture Decisions, 2.1 Entry Points, 2.2 Files to Change, 2.3 Risks\n\nAND produce a lightweight Execution Pack (Plan + Diff + Acceptance Criteria + Rollback). Write both as your task output via TaskUpdate.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-2-id>', status: 'completed', description: '<Bundle Sections 1+2 + Execution Pack>')\nDo NOT go idle without calling TaskUpdate.\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
129
129
  ```
130
130
  4. Context Curator collects context → writes Bundle Section 3 to task via TaskUpdate → sends to Architect via SendMessage
131
131
  5. Architect receives Bundle Section 3 → writes Bundle Sections 1+2 + Execution Pack to task via TaskUpdate
@@ -134,9 +134,9 @@ Cap at 10.
134
134
  8. Spawn Implementer with full Execution Pack text from TaskGet(task-2-id) and task 3's ID:
135
135
  ```
136
136
  Agent(team_name: ..., name: "implementer", subagent_type: "implementer",
137
- prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-3-id>\n\n<full text from TaskGet(task-2-id).description>\n\nThe above contains Bundle Sections 1+2 and an Execution Pack. Execute the Plan from the Execution Pack. Use Bundle sections for architectural context if needed.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-3-id>', status: 'completed', description: '<implementation report>')\nDo NOT go idle without calling TaskUpdate.")
137
+ prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-3-id>\n\n<full text from TaskGet(task-2-id).description>\n\nThe above contains Bundle Sections 1+2 and an Execution Pack. Execute the Plan from the Execution Pack. Use Bundle sections for architectural context if needed.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-3-id>', status: 'completed', description: '<implementation report>')\nDo NOT go idle without calling TaskUpdate.\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
138
138
  ```
139
- 9. Poll TaskList until task 3 completed → Read result from TaskGet(task-3-id) → Shutdown teammates TeamDelete
139
+ 9. Poll TaskList until task 3 completed → Read result from TaskGet(task-3-id) → Call `TeamDelete`. Optionally send `shutdown_request` as best-effort, but do NOT wait for `shutdown_response`.
140
140
 
141
141
  ## Cycle Pipeline (score 7-10)
142
142
 
@@ -154,13 +154,13 @@ Cap at 10.
154
154
  3. Spawn 3 teammates **in parallel** — each prompt MUST include task, team name, task ID, and Bundle Section instruction:
155
155
  ```
156
156
  Agent(team_name: ..., name: "architect", subagent_type: "architect",
157
- prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-1-id>\n\nAnalyze architecture for this task. Write Bundle Section 1 following the ## Bundle Section 1: Architecture schema. CRITICAL: MUST NOT exceed 400 lines.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-1-id>', status: 'completed', description: '<Bundle Section 1>')\nDo NOT go idle without calling TaskUpdate.")
157
+ prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-1-id>\n\nAnalyze architecture for this task. Write Bundle Section 1 following the ## Bundle Section 1: Architecture schema. CRITICAL: MUST NOT exceed 400 lines.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-1-id>', status: 'completed', description: '<Bundle Section 1>')\nDo NOT go idle without calling TaskUpdate.\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
158
158
 
159
159
  Agent(team_name: ..., name: "code-analyst", subagent_type: "code-analyst",
160
- prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-2-id>\n\nAnalyze codebase for this task. Write Bundle Section 2 following the ## Bundle Section 2: Code Analysis schema. CRITICAL: MUST NOT exceed 600 lines.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-2-id>', status: 'completed', description: '<Bundle Section 2>')\nDo NOT go idle without calling TaskUpdate.")
160
+ prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-2-id>\n\nAnalyze codebase for this task. Write Bundle Section 2 following the ## Bundle Section 2: Code Analysis schema. CRITICAL: MUST NOT exceed 600 lines.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-2-id>', status: 'completed', description: '<Bundle Section 2>')\nDo NOT go idle without calling TaskUpdate.\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
161
161
 
162
162
  Agent(team_name: ..., name: "context-curator", subagent_type: "context-curator",
163
- prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-3-id>\n\nCollect project context. Write Bundle Section 3 following the ## Bundle Section 3: Project Context schema. CRITICAL: MUST NOT exceed 400 lines.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-3-id>', status: 'completed', description: '<Bundle Section 3>')\nDo NOT go idle without calling TaskUpdate.")
163
+ prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-3-id>\n\nCollect project context. Write Bundle Section 3 following the ## Bundle Section 3: Project Context schema. CRITICAL: MUST NOT exceed 400 lines.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-3-id>', status: 'completed', description: '<Bundle Section 3>')\nDo NOT go idle without calling TaskUpdate.\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
164
164
  ```
165
165
  4. Wait for all 3 to complete — poll `TaskList` until tasks 1, 2, 3 are all `status: "completed"`
166
166
 
@@ -170,12 +170,12 @@ Cap at 10.
170
170
  Spawn Planner:
171
171
  ```
172
172
  Agent(team_name: ..., name: "planner", subagent_type: "planner",
173
- prompt: "## Assignment\n**Team**: <team-name>\n**Your task ID**: <task-4-id>\n\n## Task\n<the user's original request>\n\n## Phase 1 Bundle Section Task IDs\nRead each Bundle Section using TaskGet(taskId) — the section is in the task's `description` field. Each section starts with `## Bundle Section N:`.\n- **Architect (Bundle Section 1)**: TaskGet('<task-1-id>')\n- **Code Analyst (Bundle Section 2)**: TaskGet('<task-2-id>')\n- **Context Curator (Bundle Section 3)**: TaskGet('<task-3-id>')\n\nRead all three Bundle Sections, assemble the Bundle, then produce an Execution Pack with sub-tasks, pseudo-diffs, acceptance criteria, risks, and rollback. Use ToolSearch('+legion-sequential-thinking') and ToolSearch('+legion-memory') before plan construction. Read Infrastructure Notes sections (§1.6, §2.7) from Bundle Sections. Query mcp__legion-memory__read_graph for cross-session context. If legion-memory MCP is unavailable, proceed without. Include User Questions section (≤3) only if critical clarifications are needed.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-4-id>', status: 'completed', description: '<your full Execution Pack>')\nIf you have user questions, also: SendMessage(type: 'message', recipient: 'legion-orchestrator', content: '<questions>', summary: 'Clarifying questions for user')\nDo NOT go idle without calling TaskUpdate.")
173
+ prompt: "## Assignment\n**Team**: <team-name>\n**Your task ID**: <task-4-id>\n\n## Task\n<the user's original request>\n\n## Phase 1 Bundle Section Task IDs\nRead each Bundle Section using TaskGet(taskId) — the section is in the task's `description` field. Each section starts with `## Bundle Section N:`.\n- **Architect (Bundle Section 1)**: TaskGet('<task-1-id>')\n- **Code Analyst (Bundle Section 2)**: TaskGet('<task-2-id>')\n- **Context Curator (Bundle Section 3)**: TaskGet('<task-3-id>')\n\nRead all three Bundle Sections, assemble the Bundle, then produce an Execution Pack with sub-tasks, pseudo-diffs, acceptance criteria, risks, and rollback. Use ToolSearch('+legion-sequential-thinking') and ToolSearch('+legion-memory') before plan construction. Read Infrastructure Notes sections (§1.6, §2.7) from Bundle Sections. Query mcp__legion-memory__read_graph for cross-session context. If legion-memory MCP is unavailable, proceed without. Include User Questions section (≤3) only if critical clarifications are needed.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-4-id>', status: 'completed', description: '<your full Execution Pack>')\nIf you have user questions, also: SendMessage(type: 'message', recipient: 'legion-orchestrator', content: '<questions>', summary: 'Clarifying questions for user')\nDo NOT go idle without calling TaskUpdate.\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
174
174
  ```
175
175
  Spawn Memory (fire-and-forget):
176
176
  ```
177
- Agent(team_name: ..., name: "memory", subagent_type: "general-purpose",
178
- prompt: "## Assignment\n**Team**: <team-name>\n**Your task ID**: <task-6-id>\n\n## Phase 1 Bundle Section Task IDs\nRead Bundle Sections 1 and 2 using TaskGet(taskId) — the section is in the task's `description` field.\n- **Architect (Bundle Section 1)**: TaskGet('<task-1-id>')\n- **Code Analyst (Bundle Section 2)**: TaskGet('<task-2-id>')\n\nLoad MCP tools first: ToolSearch(\"+legion-memory\"). Then extract key findings and store them in project memory via MCP legion-memory server. Pay special attention to Infrastructure Notes (§1.6, §2.7) — store these as separate named entities using mcp__legion-memory__create_entities and mcp__legion-memory__add_observations. If MCP is unavailable, note the error and complete anyway.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-6-id>', status: 'completed', description: '<summary of stored memories>')\nDo NOT go idle without calling TaskUpdate.")
177
+ Agent(team_name: ..., name: "memory", subagent_type: "general-purpose", model: "haiku",
178
+ prompt: "## Assignment\n**Team**: <team-name>\n**Your task ID**: <task-6-id>\n\n## Phase 1 Bundle Section Task IDs\nRead Bundle Sections 1 and 2 using TaskGet(taskId) — the section is in the task's `description` field.\n- **Architect (Bundle Section 1)**: TaskGet('<task-1-id>')\n- **Code Analyst (Bundle Section 2)**: TaskGet('<task-2-id>')\n\nLoad MCP tools first: ToolSearch(\"+legion-memory\"). Then extract key findings and store them in project memory via MCP legion-memory server. Pay special attention to Infrastructure Notes (§1.6, §2.7) — store these as separate named entities using mcp__legion-memory__create_entities and mcp__legion-memory__add_observations. If MCP is unavailable, note the error and complete anyway.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-6-id>', status: 'completed', description: '<summary of stored memories>')\nDo NOT go idle without calling TaskUpdate.\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
179
179
  ```
180
180
  6. Wait for plan — poll `TaskList` until tasks 4 and 6 are `status: "completed"`
181
181
 
@@ -195,10 +195,10 @@ Cap at 10.
195
195
  9. Spawn Implementer with full Execution Pack text from TaskGet(task-4-id) and task 5's ID:
196
196
  ```
197
197
  Agent(team_name: ..., name: "implementer", subagent_type: "implementer",
198
- prompt: "## Assignment\n**Team**: <team-name>\n**Your task ID**: <task-5-id>\n\n<full text from TaskGet(task-4-id).description>\n\nThe Execution Pack above contains Plan, Diff, Acceptance Criteria, Risks, and Rollback.\nExecute the Plan. Use Bundle References (TaskGet) for deeper context if needed.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-5-id>', status: 'completed', description: '<your implementation report>')\nDo NOT go idle without calling TaskUpdate.")
198
+ prompt: "## Assignment\n**Team**: <team-name>\n**Your task ID**: <task-5-id>\n\n<full text from TaskGet(task-4-id).description>\n\nThe Execution Pack above contains Plan, Diff, Acceptance Criteria, Risks, and Rollback.\nExecute the Plan. Use Bundle References (TaskGet) for deeper context if needed.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-5-id>', status: 'completed', description: '<your implementation report>')\nDo NOT go idle without calling TaskUpdate.\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
199
199
  ```
200
200
  10. Wait for implementation — poll `TaskList` until task 5 is `status: "completed"`
201
- 11. Read final report from `TaskGet(task-5-id).description` → Shutdown all teammates TeamDelete
201
+ 11. Read final report from `TaskGet(task-5-id).description` → Call `TeamDelete`. Optionally send `shutdown_request` as best-effort, but do NOT wait for `shutdown_response`.
202
202
 
203
203
  ## Partial Pipeline (score 4-6)
204
204
 
@@ -262,7 +262,7 @@ Agent(
262
262
  3. **Create team before spawning** — TeamCreate must come before Agent calls
263
263
  4. **Pass task IDs** — give Planner the Phase 1 task IDs to read reports directly
264
264
  5. **Monitor progress** — check TaskList between phases
265
- 6. **Clean shutdown** — SendMessage(type: "shutdown_request") to each teammate, then TeamDelete
265
+ 6. **Clean shutdown** — Call `TeamDelete` immediately. Optionally send `shutdown_request` to teammates as best-effort, but do NOT wait for `shutdown_response`. Idle agents do not process incoming messages.
266
266
  7. **Report to user** — summarize what was done after pipeline completes
267
267
 
268
268
  ### Agent Tool Reference
@@ -290,7 +290,7 @@ Spawn subagents using the **Agent** tool:
290
290
  **Example:**
291
291
  ```json
292
292
  {
293
- "prompt": "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-id>\n\nAnalyze architecture, approach, boundaries, prior art.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-id>', status: 'completed', description: '<your full report>')",
293
+ "prompt": "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-id>\n\nAnalyze architecture, approach, boundaries, prior art.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-id>', status: 'completed', description: '<your full report>')\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).",
294
294
  "description": "Architectural analysis",
295
295
  "subagent_type": "architect",
296
296
  "model": "opus"
@@ -356,7 +356,7 @@ executionPack = TaskGet(Task2.id).description
356
356
 
357
357
  // Phase 5: Spawn Implementer with full Execution Pack
358
358
  Agent(name: "implementer", subagent_type: "implementer",
359
- prompt: "## Assignment\n**Your task ID**: " + Task3.id + "\n\n" + executionPack + "\n\nExecute the Plan from the Execution Pack. Use Bundle sections for context.\n\n## End of Work Protocol\nTaskUpdate(taskId: '" + Task3.id + "', ...)")
359
+ prompt: "## Assignment\n**Your task ID**: " + Task3.id + "\n\n" + executionPack + "\n\nExecute the Plan from the Execution Pack. Use Bundle sections for context.\n\n## End of Work Protocol\nTaskUpdate(taskId: '" + Task3.id + "', ...)\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
360
360
 
361
361
  // Phase 6: Poll until task 3 completed, read final report
362
362
  result = TaskGet(Task3.id).description
@@ -384,10 +384,10 @@ Agent(..., name: "context-curator", subagent_type: "context-curator",
384
384
  while not all [t1, t2, t3] completed: check TaskList()
385
385
 
386
386
  // Spawn Planner AND Memory in parallel (Phase 2)
387
- Agent(..., name: "planner", prompt: "## Assignment\n**Your task ID**: " + t4.id + "\n\n## Task\n<the user's original request>\n\n## Phase 1 Bundle Section Task IDs\nRead each Bundle Section using TaskGet(taskId). Each starts with ## Bundle Section N:.\n- Architect (§1): TaskGet('" + t1.id + "')\n- Code Analyst (§2): TaskGet('" + t2.id + "')\n- Context Curator (§3): TaskGet('" + t3.id + "')\n\nAssemble Bundle, produce Execution Pack with optional user questions (≤3).\n\n## End of Work Protocol\nTaskUpdate(taskId: '" + t4.id + "', ...)")
387
+ Agent(..., name: "planner", prompt: "## Assignment\n**Your task ID**: " + t4.id + "\n\n## Task\n<the user's original request>\n\n## Phase 1 Bundle Section Task IDs\nRead each Bundle Section using TaskGet(taskId). Each starts with ## Bundle Section N:.\n- Architect (§1): TaskGet('" + t1.id + "')\n- Code Analyst (§2): TaskGet('" + t2.id + "')\n- Context Curator (§3): TaskGet('" + t3.id + "')\n\nAssemble Bundle, produce Execution Pack with optional user questions (≤3).\n\n## End of Work Protocol\nTaskUpdate(taskId: '" + t4.id + "', ...)\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
388
388
 
389
- Agent(..., name: "memory", subagent_type: "general-purpose",
390
- prompt: "## Assignment\n**Your task ID**: " + t6.id + "\n\n## Phase 1 Bundle Section Task IDs\n- Architect (§1): TaskGet('" + t1.id + "')\n- Code Analyst (§2): TaskGet('" + t2.id + "')\n\nExtract key findings and store in project memory via MCP legion-memory server.\n\n## End of Work Protocol\nTaskUpdate(taskId: '" + t6.id + "', ...)")
389
+ Agent(..., name: "memory", subagent_type: "general-purpose", model: "haiku",
390
+ prompt: "## Assignment\n**Your task ID**: " + t6.id + "\n\n## Phase 1 Bundle Section Task IDs\n- Architect (§1): TaskGet('" + t1.id + "')\n- Code Analyst (§2): TaskGet('" + t2.id + "')\n\nExtract key findings and store in project memory via MCP legion-memory server.\n\n## End of Work Protocol\nTaskUpdate(taskId: '" + t6.id + "', ...)\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
391
391
 
392
392
  // Phase 2.5: Check for Planner questions → AskUserQuestion if present
393
393
 
@@ -396,7 +396,7 @@ executionPack = TaskGet(t4.id).description
396
396
  // Extract ## Plan section, present via AskUserQuestion
397
397
 
398
398
  // Spawn Implementer with full Execution Pack
399
- Agent(..., name: "implementer", prompt: "## Assignment\n**Your task ID**: " + t5.id + "\n\n" + executionPack + "\n\nExecute the Plan. Use Bundle References (TaskGet) for deeper context if needed.\n\n## End of Work Protocol\nTaskUpdate(taskId: '" + t5.id + "', ...)")
399
+ Agent(..., name: "implementer", prompt: "## Assignment\n**Your task ID**: " + t5.id + "\n\n" + executionPack + "\n\nExecute the Plan. Use Bundle References (TaskGet) for deeper context if needed.\n\n## End of Work Protocol\nTaskUpdate(taskId: '" + t5.id + "', ...)\nIf you receive a shutdown_request message, respond: SendMessage(type: 'shutdown_response', request_id: '<requestId>', approve: true).")
400
400
  ```
401
401
 
402
402
  ## Behavioral Guidelines
@@ -426,5 +426,5 @@ Agent(..., name: "implementer", prompt: "## Assignment\n**Your task ID**: " + t5
426
426
  4. **Read reports via TaskGet** — after a task is completed, call `TaskGet(taskId)` to read the report from the `description` field
427
427
  5. **Pass task IDs to Planner** — give Planner the Phase 1 task IDs so it can read reports directly via TaskGet
428
428
  6. **Create new tasks for each phase** — don't reuse tasks; Phase 2 is a new task (Planner), Phase 3 is another new task (Implementer)
429
- 7. **Shutdown gracefully** — after pipeline completes, send `shutdown_request` to each agent before `TeamDelete`
429
+ 7. **Shutdown gracefully** — Call `TeamDelete` immediately after pipeline completes. Optionally send `shutdown_request` as best-effort, but do NOT wait for `shutdown_response`. Idle agents do not process incoming messages.
430
430
  8. **Don't rely on stdout or SendMessage for reports** — reports are delivered via TaskUpdate(description). TaskGet reads them.
package/agents/memory.md CHANGED
@@ -80,8 +80,9 @@ If MCP servers are available in the project, you MUST:
80
80
 
81
81
  ## End of Work Protocol (MANDATORY)
82
82
 
83
- TaskUpdate(taskId: "<your-task-id>", status: "completed", description: "<summary of what was stored in memory, or error if MCP unavailable>")
84
- Do NOT go idle without calling TaskUpdate.
83
+ 1. TaskUpdate(taskId: "<your-task-id>", status: "completed", description: "<summary of what was stored in memory, or error if MCP unavailable>")
84
+ 2. Do NOT go idle without calling TaskUpdate.
85
+ 3. **Respond to shutdown** (if received): If a `shutdown_request` message arrives, call `SendMessage(type: "shutdown_response", request_id: "<requestId>", approve: true)`. Do NOT wait actively for it — you may stop after step 1.
85
86
 
86
87
  ## Rules
87
88
 
package/agents/planner.md CHANGED
@@ -114,6 +114,7 @@ When your work is complete, execute these steps IN ORDER:
114
114
  1. **Write Execution Pack to task**: `TaskUpdate(taskId: "<your-task-id>", status: "completed", description: "<your full Execution Pack>")`
115
115
  2. **Send questions to orchestrator** (if any): If your Execution Pack contains a `## User Questions` section with 1+ questions, also: `SendMessage(type: "message", recipient: "legion-orchestrator", content: "<questions text>", summary: "Clarifying questions for user")`
116
116
  3. **Notify teammates** (if needed): `SendMessage(type: "message", recipient: "<teammate-name>", content: "<summary>", summary: "<brief>")`
117
+ 4. **Respond to shutdown** (if received): If a `shutdown_request` message arrives, call `SendMessage(type: "shutdown_response", request_id: "<requestId>", approve: true)`. Do NOT wait actively for it — you may stop after steps 1-3.
117
118
 
118
119
  Your task ID and team name are provided in your spawn prompt. If not provided, check `TaskList` to find your assigned task.
119
120
 
package/bin/install.js CHANGED
@@ -88,13 +88,21 @@ const COMMAND_FILES = [
88
88
  'update.md',
89
89
  ];
90
90
 
91
+ // Cross-platform helpers
92
+ const isWindows = process.platform === 'win32';
93
+ function npmCmd(cmd) { return isWindows ? cmd + '.cmd' : cmd; }
94
+ function makeHookCmd(hookPath) {
95
+ const p = isWindows ? hookPath.replace(/\\/g, '/') : hookPath;
96
+ return `node "${p}"`;
97
+ }
98
+
91
99
  // Claude Code >=2.1 hook format: { matcher: {}, hooks: [{ type, command }] }
92
100
  // Statusline is a top-level `statusLine` setting, not a hook event.
93
- const STATUSLINE_CMD = `node "${path.join(hooksTarget, 'legion-statusline.js')}"`;
94
- const CONTEXT_MONITOR_CMD = `node "${path.join(hooksTarget, 'legion-context-monitor.js')}"`;
95
- const TEAMMATE_IDLE_CMD = `node "${path.join(hooksTarget, 'legion-teammate-idle.js')}"`;
96
- const TASK_COMPLETED_CMD = `node "${path.join(hooksTarget, 'legion-task-completed.js')}"`;
97
- const MCP_ADVISOR_CMD = `node "${path.join(hooksTarget, 'legion-mcp-advisor.js')}"`;
101
+ const STATUSLINE_CMD = makeHookCmd(path.join(hooksTarget, 'legion-statusline.js'));
102
+ const CONTEXT_MONITOR_CMD = makeHookCmd(path.join(hooksTarget, 'legion-context-monitor.js'));
103
+ const TEAMMATE_IDLE_CMD = makeHookCmd(path.join(hooksTarget, 'legion-teammate-idle.js'));
104
+ const TASK_COMPLETED_CMD = makeHookCmd(path.join(hooksTarget, 'legion-task-completed.js'));
105
+ const MCP_ADVISOR_CMD = makeHookCmd(path.join(hooksTarget, 'legion-mcp-advisor.js'));
98
106
 
99
107
  // Marker prefix for identifying Legion hook entries in settings.json.
100
108
  // All Legion hook commands start with this path prefix.
@@ -105,14 +113,14 @@ const LEGION_MCP_PREFIX = 'legion-';
105
113
 
106
114
  const LEGION_MCP_SERVERS = {
107
115
  'legion-memory': {
108
- command: 'npx',
116
+ command: npmCmd('npx'),
109
117
  args: ['-y', '@modelcontextprotocol/server-memory'],
110
118
  env: {
111
119
  MEMORY_FILE_PATH: path.join(os.homedir(), '.legion', 'memory.jsonl'),
112
120
  },
113
121
  },
114
122
  'legion-sequential-thinking': {
115
- command: 'npx',
123
+ command: npmCmd('npx'),
116
124
  args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
117
125
  },
118
126
  };
@@ -169,7 +177,7 @@ function copyFile(src, dest, options) {
169
177
  const tmpPath = dest + '.tmp.' + process.pid;
170
178
  try {
171
179
  fs.copyFileSync(src, tmpPath);
172
- fs.chmodSync(tmpPath, mode);
180
+ if (!isWindows) fs.chmodSync(tmpPath, mode);
173
181
  fs.renameSync(tmpPath, dest);
174
182
  log(`copied ${path.basename(dest)}`);
175
183
  } catch (err) {
@@ -387,13 +395,15 @@ function upsertHookEntry(settings, eventName, entry) {
387
395
  settings.hooks[eventName] = [];
388
396
  }
389
397
 
390
- const existingIdx = settings.hooks[eventName].findIndex((e) =>
391
- e.hooks && e.hooks.some((h) => h.command && h.command.includes(LEGION_HOOK_MARKER))
392
- );
393
-
394
398
  // Extract hook filename from command (e.g. 'node "/path/to/legion-foo.js"' → 'legion-foo.js')
395
399
  const hookFile = path.basename(entry.hooks[0].command.replace(/"/g, ''));
396
400
 
401
+ const existingIdx = settings.hooks[eventName].findIndex((e) =>
402
+ e.hooks && e.hooks.some((h) => h.command &&
403
+ h.command.includes(LEGION_HOOK_MARKER) &&
404
+ path.basename(h.command.replace(/"/g, '')) === hookFile)
405
+ );
406
+
397
407
  if (existingIdx === -1) {
398
408
  settings.hooks[eventName].push(entry);
399
409
  log(`registered hook: ${eventName} → ${hookFile}`);
@@ -472,15 +482,13 @@ function registerHooks() {
472
482
  log('updated hook: PostToolUse → legion-context-monitor.js');
473
483
  }
474
484
 
475
- // ── TeammateIdle hook ────────────────────────────────────────────────────
485
+ // ── TeammateIdle hook (no matcher support — fires on every occurrence) ──
476
486
  upsertHookEntry(settings, 'TeammateIdle', {
477
- matcher: '*',
478
487
  hooks: [{ type: 'command', command: TEAMMATE_IDLE_CMD }],
479
488
  });
480
489
 
481
- // ── TaskCompleted hook ───────────────────────────────────────────────────
490
+ // ── TaskCompleted hook (no matcher support — fires on every occurrence) ─
482
491
  upsertHookEntry(settings, 'TaskCompleted', {
483
- matcher: '*',
484
492
  hooks: [{ type: 'command', command: TASK_COMPLETED_CMD }],
485
493
  });
486
494
 
@@ -78,7 +78,7 @@ Announce your score and selected pipeline to the user before proceeding.
78
78
  Agent(team_name: ..., name: "implementer", subagent_type: "implementer",
79
79
  prompt: "## Assignment\n**Task**: <task>\n**Team**: <team-name>\n**Your task ID**: <task-3-id>\n\n<full text from TaskGet(task-2-id).description>\n\nThe above contains Bundle Sections 1+2 and an Execution Pack. Execute the Plan from the Execution Pack. Use Bundle sections for architectural context if needed.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-3-id>', status: 'completed', description: '<implementation report>')\nDo NOT go idle without calling TaskUpdate.")
80
80
  ```
81
- 9. Poll TaskList until task 3 completed → Read result from TaskGet(task-3-id) → Shutdown teammates TeamDelete
81
+ 9. Poll TaskList until task 3 completed → Read result from TaskGet(task-3-id) → Call `TeamDelete`. Optionally send `shutdown_request` to teammates as best-effort, but do NOT wait for `shutdown_response`. Idle agents do not process incoming messages.
82
82
 
83
83
  ---
84
84
 
@@ -153,7 +153,7 @@ Escalation: shutdown quick team → create cycle team → run full Cycle Pipelin
153
153
  prompt: "## Assignment\n**Team**: <team-name>\n**Your task ID**: <task-5-id>\n\n<full text from TaskGet(task-4-id).description>\n\nThe Execution Pack above contains Plan, Diff, Acceptance Criteria, Risks, and Rollback.\nExecute the Plan. Use Bundle References (TaskGet) for deeper context if needed.\n\n## End of Work Protocol (MANDATORY)\nTaskUpdate(taskId: '<task-5-id>', status: 'completed', description: '<your implementation report>')\nDo NOT go idle without calling TaskUpdate.")
154
154
  ```
155
155
  10. Wait for implementation — poll `TaskList` until task 5 is `status: "completed"`
156
- 11. Read final report from `TaskGet(task-5-id).description` → Shutdown all teammates TeamDelete
156
+ 11. Read final report from `TaskGet(task-5-id).description` → Call `TeamDelete`. Optionally send `shutdown_request` to teammates as best-effort, but do NOT wait for `shutdown_response`. Idle agents do not process incoming messages.
157
157
 
158
158
  ---
159
159
 
@@ -87,7 +87,7 @@ $ARGUMENTS
87
87
  - `architect-brief.md`
88
88
  - `code-analyst-brief.md`
89
89
  - `context-curator-brief.md`
90
- 11. **Clean shutdown** — shutdown all teammates, TeamDelete
90
+ 11. **Clean shutdown** — Call `TeamDelete`. Optionally send `shutdown_request` to teammates as best-effort, but do NOT wait for `shutdown_response`. Idle agents do not process incoming messages.
91
91
  12. **Report** — summarize what was generated: 5 codebase analysis files in `.legion/codebase/`, 3 agent briefs in `.legion/codebase/agents/`, and key findings stored in MCP memory
92
92
 
93
93
  ## Rules
@@ -111,7 +111,7 @@ $ARGUMENTS
111
111
  TaskUpdate(taskId: "<your-task-id>", status: "completed", description: "<your implementation report>")
112
112
  ```
113
113
  10. **Wait** for implementation — poll `TaskList` until task 5 is `status: "completed"`
114
- 11. **Clean shutdown** — shutdown all teammates, TeamDelete
114
+ 11. **Clean shutdown** — Call `TeamDelete`. Optionally send `shutdown_request` to teammates as best-effort, but do NOT wait for `shutdown_response`. Idle agents do not process incoming messages.
115
115
  12. **Report** — read final report from `TaskGet` on task 5, summarize what was done to the user
116
116
 
117
117
  ## Rules
@@ -82,7 +82,7 @@ $ARGUMENTS
82
82
  ```
83
83
 
84
84
  8. **Monitor** — poll `TaskList` until Task 3 is `status: "completed"`
85
- 9. **Clean shutdown** — shutdown teammates, TeamDelete
85
+ 9. **Clean shutdown** — Call `TeamDelete`. Optionally send `shutdown_request` to teammates as best-effort, but do NOT wait for `shutdown_response`. Idle agents do not process incoming messages.
86
86
  10. **Report** — read final report from `TaskGet` on Task 3, summarize what was done to the user
87
87
 
88
88
  ## Rules
@@ -93,6 +93,13 @@ function systemNotify(title, message) {
93
93
  } else if (platform === 'linux') {
94
94
  spawnSync('notify-send', ['--urgency=critical', title, message],
95
95
  { timeout: 3000, stdio: 'ignore' });
96
+ } else if (platform === 'win32') {
97
+ const safeMsg = message.replace(/'/g, "''");
98
+ const safeTitle = title.replace(/'/g, "''");
99
+ const ps = `Add-Type -AssemblyName System.Windows.Forms; ` +
100
+ `[System.Windows.Forms.MessageBox]::Show('${safeMsg}', '${safeTitle}')`;
101
+ spawnSync('powershell', ['-NonInteractive', '-Command', ps],
102
+ { timeout: 3000, stdio: 'ignore' });
96
103
  }
97
104
  } catch (_e) { /* best-effort */ }
98
105
 
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- // MCP & Subagent Advisor — Legion SessionStart/SubagentStart hook
2
+ // MCP & Subagent Advisor — Legion SessionStart hook
3
3
  //
4
4
  // Reads .legion/config.json and injects MCP server awareness + subagent
5
5
  // enforcement into agent context. Searches for config up the directory tree.
6
6
  //
7
- // Events: SessionStart (matcher: "startup"), SubagentStart
7
+ // Events: SessionStart (matcher: "startup")
8
8
  // Output: hookSpecificOutput.additionalContext with MCP + subagent rules
9
9
 
10
10
  'use strict';
@@ -57,6 +57,13 @@ process.stdin.on('end', () => {
57
57
  process.exit(0);
58
58
  }
59
59
 
60
+ // Only inject enforcement on SessionStart — NOT on SubagentStart
61
+ // to prevent recursive subagent spawning loops
62
+ const hookEvent = data.hook_event_name || 'SessionStart';
63
+ if (hookEvent === 'SubagentStart') {
64
+ process.exit(0);
65
+ }
66
+
60
67
  const parts = [];
61
68
 
62
69
  // ── MCP enforcement ───────────────────────────────────────────────────
@@ -90,23 +97,23 @@ process.stdin.on('end', () => {
90
97
 
91
98
  // ── Subagent enforcement ──────────────────────────────────────────────
92
99
  if (config.subagents && config.subagents.enforce !== false) {
93
- const sa = config.subagents;
94
- const minCount = sa.minCount || 3;
95
- const types = Array.isArray(sa.types) ? sa.types : [];
96
- const saMessage = sa.message ||
97
- 'ALWAYS delegate tasks to subagents via Task tool.';
98
-
99
- let saContext =
100
- `[LEGION SUBAGENTS] ${saMessage} ` +
101
- `Minimum ${minCount} subagents per task. ` +
102
- 'Use Task tool with specialized subagent_type for each subtask. ' +
103
- 'Maximize parallel execution — launch independent agents simultaneously.';
104
-
105
- if (types.length > 0) {
106
- saContext += ` Recommended types: ${types.join(', ')}.`;
107
- }
108
-
109
- parts.push(saContext);
100
+ const sa = config.subagents;
101
+ const minCount = sa.minCount || 3;
102
+ const types = Array.isArray(sa.types) ? sa.types : [];
103
+ const saMessage = sa.message ||
104
+ 'ALWAYS delegate tasks to subagents via Task tool.';
105
+
106
+ let saContext =
107
+ `[LEGION SUBAGENTS] ${saMessage} ` +
108
+ `Minimum ${minCount} subagents per task. ` +
109
+ 'Use Task tool with specialized subagent_type for each subtask. ' +
110
+ 'Maximize parallel execution — launch independent agents simultaneously.';
111
+
112
+ if (types.length > 0) {
113
+ saContext += ` Recommended types: ${types.join(', ')}.`;
114
+ }
115
+
116
+ parts.push(saContext);
110
117
  }
111
118
 
112
119
  // ── Output ────────────────────────────────────────────────────────────
@@ -114,8 +121,6 @@ process.stdin.on('end', () => {
114
121
  process.exit(0);
115
122
  }
116
123
 
117
- const hookEvent = data.hook_event_name || 'SessionStart';
118
-
119
124
  const output = {
120
125
  hookSpecificOutput: {
121
126
  hookEventName: hookEvent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "legion-cc",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "description": "Legion — context monitoring hooks and agent teams for Claude Code",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "README.md"
16
16
  ],
17
17
  "scripts": {
18
- "postinstall": "node bin/install.js 2>/dev/null || true"
18
+ "postinstall": "node bin/install.js || exit 0"
19
19
  },
20
20
  "author": "Vladyslav Blackmoore",
21
21
  "repository": {