claude-code-swarm 0.3.5 → 0.3.7

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.
Files changed (42) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.claude-plugin/run-agent-inbox-mcp.sh +22 -3
  4. package/.gitattributes +3 -0
  5. package/.opentasks/config.json +9 -0
  6. package/.opentasks/graph.jsonl +0 -0
  7. package/e2e/helpers/opentasks-daemon.mjs +149 -0
  8. package/e2e/tier6-live-inbox-flow.test.mjs +938 -0
  9. package/e2e/tier7-hooks.test.mjs +992 -0
  10. package/e2e/tier7-minimem.test.mjs +461 -0
  11. package/e2e/tier7-opentasks.test.mjs +513 -0
  12. package/e2e/tier7-skilltree.test.mjs +506 -0
  13. package/e2e/vitest.config.e2e.mjs +1 -1
  14. package/package.json +6 -2
  15. package/references/agent-inbox/package-lock.json +2 -2
  16. package/references/agent-inbox/package.json +1 -1
  17. package/references/agent-inbox/src/index.ts +16 -2
  18. package/references/agent-inbox/src/ipc/ipc-server.ts +58 -0
  19. package/references/agent-inbox/src/mcp/mcp-proxy.ts +326 -0
  20. package/references/agent-inbox/src/types.ts +26 -0
  21. package/references/agent-inbox/test/ipc-new-commands.test.ts +200 -0
  22. package/references/agent-inbox/test/mcp-proxy.test.ts +191 -0
  23. package/references/minimem/package-lock.json +2 -2
  24. package/references/minimem/package.json +1 -1
  25. package/scripts/bootstrap.mjs +8 -1
  26. package/scripts/map-hook.mjs +6 -2
  27. package/scripts/map-sidecar.mjs +19 -0
  28. package/scripts/team-loader.mjs +15 -8
  29. package/skills/swarm/SKILL.md +16 -22
  30. package/src/__tests__/agent-generator.test.mjs +9 -10
  31. package/src/__tests__/context-output.test.mjs +13 -14
  32. package/src/__tests__/e2e-inbox-integration.test.mjs +732 -0
  33. package/src/__tests__/e2e-live-inbox.test.mjs +597 -0
  34. package/src/__tests__/inbox-integration.test.mjs +298 -0
  35. package/src/__tests__/integration.test.mjs +12 -11
  36. package/src/__tests__/skilltree-client.test.mjs +47 -1
  37. package/src/agent-generator.mjs +79 -88
  38. package/src/bootstrap.mjs +24 -3
  39. package/src/context-output.mjs +238 -64
  40. package/src/index.mjs +2 -0
  41. package/src/sidecar-server.mjs +30 -0
  42. package/src/skilltree-client.mjs +50 -5
@@ -0,0 +1,298 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { generateAgentMd } from "../agent-generator.mjs";
3
+ import { buildCapabilitiesContext } from "../context-output.mjs";
4
+ import { createCommandHandler, respond } from "../sidecar-server.mjs";
5
+
6
+ // Minimal manifest for agent generation
7
+ const minManifest = {
8
+ name: "gsd",
9
+ topology: { root: { role: "coordinator" } },
10
+ communication: {},
11
+ };
12
+
13
+ describe("inbox integration — agent identity in AGENT.md", () => {
14
+ it("embeds inbox identity section when inboxEnabled is true", () => {
15
+ const md = generateAgentMd({
16
+ roleName: "executor",
17
+ teamName: "gsd",
18
+ position: "spawned",
19
+ description: "Executes tasks",
20
+ tools: ["Read", "Bash"],
21
+ skillContent: "# Role: executor",
22
+ manifest: minManifest,
23
+ inboxEnabled: true,
24
+ });
25
+
26
+ expect(md).toContain("## Your Agent Inbox Identity");
27
+ expect(md).toContain("**gsd-executor**");
28
+ expect(md).toContain('agent-inbox__check_inbox(agentId: "gsd-executor")');
29
+ expect(md).toContain('agent-inbox__send_message(from: "gsd-executor"');
30
+ expect(md).toContain("agent-inbox__read_thread");
31
+ expect(md).toContain("agent-inbox__list_agents");
32
+ });
33
+
34
+ it("does not embed inbox identity when inboxEnabled is false", () => {
35
+ const md = generateAgentMd({
36
+ roleName: "executor",
37
+ teamName: "gsd",
38
+ position: "spawned",
39
+ description: "Executes tasks",
40
+ tools: ["Read", "Bash"],
41
+ skillContent: "# Role: executor",
42
+ manifest: minManifest,
43
+ inboxEnabled: false,
44
+ });
45
+
46
+ expect(md).not.toContain("Your Agent Inbox Identity");
47
+ expect(md).not.toContain("agent-inbox__check_inbox");
48
+ });
49
+
50
+ it("uses correct ID format: teamName-roleName", () => {
51
+ const md = generateAgentMd({
52
+ roleName: "verifier",
53
+ teamName: "my-team",
54
+ position: "spawned",
55
+ description: "Verifies",
56
+ tools: ["Read"],
57
+ skillContent: "# Role: verifier",
58
+ manifest: minManifest,
59
+ inboxEnabled: true,
60
+ });
61
+
62
+ expect(md).toContain("**my-team-verifier**");
63
+ expect(md).toContain('agentId: "my-team-verifier"');
64
+ expect(md).toContain('from: "my-team-verifier"');
65
+ });
66
+
67
+ it("mentions federated addressing", () => {
68
+ const md = generateAgentMd({
69
+ roleName: "executor",
70
+ teamName: "gsd",
71
+ position: "spawned",
72
+ description: "Executes",
73
+ tools: ["Read"],
74
+ skillContent: "# Role: executor",
75
+ manifest: minManifest,
76
+ inboxEnabled: true,
77
+ });
78
+
79
+ expect(md).toContain("agent@system");
80
+ });
81
+
82
+ it("AGENT.md frontmatter name matches inbox ID format", () => {
83
+ const md = generateAgentMd({
84
+ roleName: "executor",
85
+ teamName: "gsd",
86
+ position: "spawned",
87
+ description: "Executes",
88
+ tools: ["Read"],
89
+ skillContent: "# Role: executor",
90
+ manifest: minManifest,
91
+ inboxEnabled: true,
92
+ });
93
+
94
+ // Frontmatter should have same format as inbox ID
95
+ expect(md).toContain("name: gsd-executor");
96
+ expect(md).toContain("**gsd-executor**");
97
+ });
98
+ });
99
+
100
+ describe("inbox integration — capabilities context", () => {
101
+ it("includes detailed inbox MCP tool instructions when enabled", () => {
102
+ const ctx = buildCapabilitiesContext({
103
+ inboxEnabled: true,
104
+ });
105
+
106
+ expect(ctx).toContain("agent-inbox__check_inbox");
107
+ expect(ctx).toContain("agent-inbox__send_message");
108
+ expect(ctx).toContain("agent-inbox__read_thread");
109
+ expect(ctx).toContain("agent-inbox__list_agents");
110
+ expect(ctx).toContain("cross-system messages");
111
+ expect(ctx).toContain("threaded conversations");
112
+ });
113
+
114
+ it("omits inbox tool instructions when disabled", () => {
115
+ const ctx = buildCapabilitiesContext({
116
+ inboxEnabled: false,
117
+ });
118
+
119
+ expect(ctx).not.toContain("agent-inbox__check_inbox");
120
+ expect(ctx).not.toContain("Structured messaging");
121
+ });
122
+
123
+ it("distinguishes between SendMessage and inbox use cases", () => {
124
+ const ctx = buildCapabilitiesContext({
125
+ inboxEnabled: true,
126
+ });
127
+
128
+ expect(ctx).toContain("Use inbox for:");
129
+ expect(ctx).toContain("Use `SendMessage` for:");
130
+ });
131
+
132
+ it("mentions federated addressing in tool description", () => {
133
+ const ctx = buildCapabilitiesContext({
134
+ inboxEnabled: true,
135
+ });
136
+
137
+ expect(ctx).toContain("agent@system");
138
+ });
139
+ });
140
+
141
+ describe("inbox integration — sidecar websocket mode agent registration", () => {
142
+ it("registers agent in inbox storage during websocket spawn", async () => {
143
+ // Mock connection that returns agent data
144
+ const mockConn = {
145
+ spawn: vi.fn().mockResolvedValue({ agentId: "gsd-exec", name: "exec" }),
146
+ callExtension: vi.fn().mockResolvedValue({}),
147
+ updateState: vi.fn().mockResolvedValue(undefined),
148
+ updateMetadata: vi.fn().mockResolvedValue(undefined),
149
+ send: vi.fn().mockResolvedValue(undefined),
150
+ };
151
+
152
+ const mockInboxStorage = {
153
+ putAgent: vi.fn(),
154
+ };
155
+ const mockInboxInstance = { storage: mockInboxStorage };
156
+
157
+ const registeredAgents = new Map();
158
+ const handler = createCommandHandler(mockConn, "swarm:gsd", registeredAgents, {
159
+ inboxInstance: mockInboxInstance,
160
+ transportMode: "websocket",
161
+ });
162
+
163
+ const client = { write: vi.fn() };
164
+ await handler({
165
+ action: "spawn",
166
+ agent: {
167
+ agentId: "gsd-executor",
168
+ name: "executor",
169
+ role: "executor",
170
+ scopes: ["swarm:gsd"],
171
+ metadata: { template: "gsd" },
172
+ },
173
+ }, client);
174
+
175
+ // Should have called conn.spawn (MAP SDK)
176
+ expect(mockConn.spawn).toHaveBeenCalledOnce();
177
+
178
+ // Should also register in inbox storage
179
+ expect(mockInboxStorage.putAgent).toHaveBeenCalledOnce();
180
+ const agentArg = mockInboxStorage.putAgent.mock.calls[0][0];
181
+ expect(agentArg.agent_id).toBe("gsd-executor");
182
+ expect(agentArg.status).toBe("active");
183
+ expect(agentArg.scope).toBe("swarm:gsd");
184
+
185
+ // Should be in registered agents map
186
+ expect(registeredAgents.has("gsd-executor")).toBe(true);
187
+ });
188
+
189
+ it("marks agent disconnected in inbox storage during websocket done", async () => {
190
+ const mockConn = {
191
+ spawn: vi.fn().mockResolvedValue({}),
192
+ callExtension: vi.fn().mockResolvedValue({}),
193
+ updateState: vi.fn(),
194
+ updateMetadata: vi.fn(),
195
+ send: vi.fn(),
196
+ };
197
+
198
+ const mockInboxStorage = {
199
+ putAgent: vi.fn(),
200
+ };
201
+ const mockInboxInstance = { storage: mockInboxStorage };
202
+
203
+ const registeredAgents = new Map();
204
+ registeredAgents.set("gsd-executor", { name: "executor", role: "executor" });
205
+
206
+ const handler = createCommandHandler(mockConn, "swarm:gsd", registeredAgents, {
207
+ inboxInstance: mockInboxInstance,
208
+ transportMode: "websocket",
209
+ });
210
+
211
+ const client = { write: vi.fn() };
212
+ await handler({
213
+ action: "done",
214
+ agentId: "gsd-executor",
215
+ reason: "completed",
216
+ }, client);
217
+
218
+ // Should have called MAP unregister
219
+ expect(mockConn.callExtension).toHaveBeenCalledOnce();
220
+
221
+ // Should mark disconnected in inbox storage
222
+ expect(mockInboxStorage.putAgent).toHaveBeenCalledOnce();
223
+ const agentArg = mockInboxStorage.putAgent.mock.calls[0][0];
224
+ expect(agentArg.agent_id).toBe("gsd-executor");
225
+ expect(agentArg.status).toBe("disconnected");
226
+
227
+ // Should be removed from registered agents
228
+ expect(registeredAgents.has("gsd-executor")).toBe(false);
229
+ });
230
+
231
+ it("does not fail when inbox storage is not available in websocket spawn", async () => {
232
+ const mockConn = {
233
+ spawn: vi.fn().mockResolvedValue({}),
234
+ callExtension: vi.fn(),
235
+ updateState: vi.fn(),
236
+ updateMetadata: vi.fn(),
237
+ send: vi.fn(),
238
+ };
239
+
240
+ const registeredAgents = new Map();
241
+ const handler = createCommandHandler(mockConn, "swarm:gsd", registeredAgents, {
242
+ // No inboxInstance provided
243
+ transportMode: "websocket",
244
+ });
245
+
246
+ const client = { write: vi.fn() };
247
+
248
+ // Should not throw
249
+ await handler({
250
+ action: "spawn",
251
+ agent: {
252
+ agentId: "gsd-worker",
253
+ name: "worker",
254
+ role: "worker",
255
+ scopes: ["swarm:gsd"],
256
+ metadata: {},
257
+ },
258
+ }, client);
259
+
260
+ expect(mockConn.spawn).toHaveBeenCalledOnce();
261
+ expect(registeredAgents.has("gsd-worker")).toBe(true);
262
+ });
263
+ });
264
+
265
+ describe("inbox integration — agent ID consistency", () => {
266
+ it("AGENT.md name matches the ID format used by sidecar and hooks", () => {
267
+ // The AGENT.md frontmatter uses teamName-roleName
268
+ const teamName = "gsd";
269
+ const roleName = "executor";
270
+ const expectedId = `${teamName}-${roleName}`;
271
+
272
+ // Generated AGENT.md
273
+ const md = generateAgentMd({
274
+ roleName,
275
+ teamName,
276
+ position: "spawned",
277
+ description: "test",
278
+ tools: [],
279
+ skillContent: "",
280
+ manifest: minManifest,
281
+ inboxEnabled: true,
282
+ });
283
+
284
+ // Frontmatter name
285
+ expect(md).toContain(`name: ${expectedId}`);
286
+ // Inbox identity
287
+ expect(md).toContain(`**${expectedId}**`);
288
+ // check_inbox instruction
289
+ expect(md).toContain(`agentId: "${expectedId}"`);
290
+ });
291
+
292
+ it("main agent ID follows teamName-main convention", () => {
293
+ // This is what bootstrap.mjs and map-hook.mjs use
294
+ const teamName = "gsd";
295
+ const mainAgentId = `${teamName}-main`;
296
+ expect(mainAgentId).toBe("gsd-main");
297
+ });
298
+ });
@@ -95,8 +95,8 @@ describe("integration: agent generation with opentasks disabled", () => {
95
95
  // In fallback mode all roles get TaskCreate in the tools list
96
96
  expect(workerFrontmatter).toContain("TaskCreate");
97
97
 
98
- // Body should mention native task tools
99
- expect(workerMd).toContain("Claude Code's native task tools");
98
+ // Body should mention native task tools in capabilities
99
+ expect(workerMd).toContain("Claude Code native task tools");
100
100
  });
101
101
 
102
102
  it("root and companion roles include TaskCreate in determineTools", async () => {
@@ -237,20 +237,20 @@ describe("integration: native task hook → MAP bridge events", () => {
237
237
  // ── 4. Context output with opentasks ─────────────────────────────────────────────
238
238
 
239
239
  describe("integration: context output with opentasks", () => {
240
- it("formatBootstrapContext mentions opentasks MCP tools when connected", async () => {
240
+ it("formatBootstrapContext mentions opentasks MCP tools when enabled and connected", async () => {
241
241
  const { formatBootstrapContext } = await import("../context-output.mjs");
242
242
 
243
243
  const output = formatBootstrapContext({
244
244
  template: "gsd",
245
+ opentasksEnabled: true,
245
246
  opentasksStatus: "connected",
246
247
  });
247
248
 
248
- expect(output).toContain("Opentasks: connected");
249
249
  expect(output).toContain("opentasks MCP tools");
250
250
  expect(output).toContain("opentasks__create_task");
251
251
  });
252
252
 
253
- it("formatBootstrapContext does not mention opentasks tools when not connected", async () => {
253
+ it("formatBootstrapContext does not mention opentasks tools when not enabled", async () => {
254
254
  const { formatBootstrapContext } = await import("../context-output.mjs");
255
255
 
256
256
  const output = formatBootstrapContext({
@@ -261,21 +261,21 @@ describe("integration: context output with opentasks", () => {
261
261
  expect(output).not.toContain("opentasks__create_task");
262
262
  });
263
263
 
264
- it("formatTeamLoadedContext mentions opentasks when connected", async () => {
264
+ it("formatTeamLoadedContext mentions opentasks when enabled and connected", async () => {
265
265
  const { formatTeamLoadedContext } = await import("../context-output.mjs");
266
266
 
267
267
  const output = formatTeamLoadedContext(
268
268
  "/tmp/agents",
269
269
  "/tmp/template",
270
270
  "gsd",
271
- { opentasksStatus: "connected" },
271
+ { opentasksEnabled: true, opentasksStatus: "connected" },
272
272
  );
273
273
 
274
274
  expect(output).toContain("opentasks MCP tools");
275
275
  expect(output).toContain("opentasks__create_task");
276
276
  });
277
277
 
278
- it("formatTeamLoadedContext mentions native task tools when opentasks not connected", async () => {
278
+ it("formatTeamLoadedContext mentions native task tools when opentasks not enabled", async () => {
279
279
  const { formatTeamLoadedContext } = await import("../context-output.mjs");
280
280
 
281
281
  const output = formatTeamLoadedContext(
@@ -284,16 +284,17 @@ describe("integration: context output with opentasks", () => {
284
284
  "gsd",
285
285
  );
286
286
 
287
- expect(output).toContain("Claude Code's native team features");
288
- expect(output).toContain("TaskCreate/TaskUpdate");
287
+ expect(output).toContain("Claude Code native task tools");
288
+ expect(output).toContain("TaskCreate");
289
289
  expect(output).not.toContain("opentasks MCP tools");
290
290
  });
291
291
 
292
- it("formatBootstrapContext with opentasksStatus 'enabled' also shows opentasks tools", async () => {
292
+ it("formatBootstrapContext with opentasksEnabled and status 'enabled' shows opentasks tools", async () => {
293
293
  const { formatBootstrapContext } = await import("../context-output.mjs");
294
294
 
295
295
  const output = formatBootstrapContext({
296
296
  template: "gsd",
297
+ opentasksEnabled: true,
297
298
  opentasksStatus: "enabled",
298
299
  });
299
300
 
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { parseSkillTreeExtension } from "../skilltree-client.mjs";
2
+ import { parseSkillTreeExtension, inferProfileFromRole } from "../skilltree-client.mjs";
3
3
 
4
4
  describe("skilltree-client", () => {
5
5
  describe("parseSkillTreeExtension", () => {
@@ -77,4 +77,50 @@ describe("skilltree-client", () => {
77
77
  expect(result.roles).toEqual({});
78
78
  });
79
79
  });
80
+
81
+ describe("inferProfileFromRole", () => {
82
+ it("maps executor to implementation", () => {
83
+ expect(inferProfileFromRole("executor")).toBe("implementation");
84
+ });
85
+
86
+ it("maps developer to implementation", () => {
87
+ expect(inferProfileFromRole("developer")).toBe("implementation");
88
+ });
89
+
90
+ it("maps debugger to debugging", () => {
91
+ expect(inferProfileFromRole("debugger")).toBe("debugging");
92
+ });
93
+
94
+ it("maps verifier to testing", () => {
95
+ expect(inferProfileFromRole("verifier")).toBe("testing");
96
+ });
97
+
98
+ it("maps qa to testing", () => {
99
+ expect(inferProfileFromRole("qa")).toBe("testing");
100
+ });
101
+
102
+ it("maps plan-checker to code-review", () => {
103
+ expect(inferProfileFromRole("plan-checker")).toBe("code-review");
104
+ });
105
+
106
+ it("maps tech-writer to documentation", () => {
107
+ expect(inferProfileFromRole("tech-writer")).toBe("documentation");
108
+ });
109
+
110
+ it("returns empty string for unknown role", () => {
111
+ expect(inferProfileFromRole("orchestrator")).toBe("");
112
+ });
113
+
114
+ it("returns empty string for roadmapper", () => {
115
+ expect(inferProfileFromRole("roadmapper")).toBe("");
116
+ });
117
+
118
+ it("matches partial role names", () => {
119
+ expect(inferProfileFromRole("senior-developer")).toBe("implementation");
120
+ });
121
+
122
+ it("matches quick-flow-dev to implementation", () => {
123
+ expect(inferProfileFromRole("quick-flow-dev")).toBe("implementation");
124
+ });
125
+ });
80
126
  });
@@ -7,6 +7,7 @@
7
7
 
8
8
  import fs from "fs";
9
9
  import path from "path";
10
+ import { buildCapabilitiesContext } from "./context-output.mjs";
10
11
 
11
12
  /**
12
13
  * Parse team.yaml without js-yaml dependency (basic YAML subset).
@@ -91,6 +92,17 @@ export function generateAgentMd({
91
92
  opentasksEnabled,
92
93
  minimemEnabled,
93
94
  skillLoadout,
95
+ skillProfile,
96
+ // Capabilities context options (passed through to buildCapabilitiesContext)
97
+ opentasksStatus,
98
+ minimemStatus,
99
+ skilltreeEnabled,
100
+ skilltreeStatus,
101
+ inboxEnabled,
102
+ meshEnabled,
103
+ mapEnabled,
104
+ mapStatus,
105
+ sessionlogSync,
94
106
  }) {
95
107
  const lines = [];
96
108
 
@@ -111,25 +123,7 @@ export function generateAgentMd({
111
123
  lines.push(skillContent);
112
124
  lines.push("");
113
125
 
114
- // Add team coordination instructions
115
- lines.push("## Team Coordination");
116
- lines.push("");
117
- lines.push(`You are part of the **${teamName}** team.`);
118
- lines.push("");
119
-
120
- // Communication via SendMessage
121
- lines.push("### Communication");
122
- lines.push("");
123
- lines.push("Use **SendMessage** to communicate with teammates:");
124
- lines.push(
125
- '- `SendMessage(type="message", recipient="<agent-name>", content="...", summary="...")`'
126
- );
127
- lines.push(
128
- "- Only use broadcast when absolutely necessary (it messages every teammate)."
129
- );
130
- lines.push("");
131
-
132
- // Add role-specific communication patterns from topology routing
126
+ // Role-specific communication patterns from topology (unique per role, not in capabilities)
133
127
  if (manifest.communication?.routing?.peers) {
134
128
  const outbound = manifest.communication.routing.peers.filter(
135
129
  (r) => r.from === roleName
@@ -138,7 +132,7 @@ export function generateAgentMd({
138
132
  (r) => r.to === roleName
139
133
  );
140
134
  if (outbound.length > 0 || inbound.length > 0) {
141
- lines.push("#### Your communication patterns");
135
+ lines.push("## Your Communication Patterns");
142
136
  lines.push("");
143
137
  for (const route of outbound) {
144
138
  const signals = route.signals?.join(", ") || "updates";
@@ -152,10 +146,10 @@ export function generateAgentMd({
152
146
  }
153
147
  }
154
148
 
155
- // Signals this role emits — now via SendMessage
149
+ // Signals this role emits
156
150
  if (manifest.communication?.emissions?.[roleName]) {
157
151
  const emissions = manifest.communication.emissions[roleName];
158
- lines.push("#### Signals you emit");
152
+ lines.push("## Signals You Emit");
159
153
  lines.push("");
160
154
  lines.push(
161
155
  "When these events occur, notify the relevant agents via SendMessage:"
@@ -169,7 +163,7 @@ export function generateAgentMd({
169
163
  // Signals this role subscribes to
170
164
  if (manifest.communication?.subscriptions?.[roleName]) {
171
165
  const subs = manifest.communication.subscriptions[roleName];
172
- lines.push("#### Signals you receive");
166
+ lines.push("## Signals You Receive");
173
167
  lines.push("");
174
168
  lines.push("Watch for messages from teammates about:");
175
169
  for (const sub of subs) {
@@ -179,79 +173,48 @@ export function generateAgentMd({
179
173
  lines.push("");
180
174
  }
181
175
 
182
- // Task management section
183
- if (opentasksEnabled) {
184
- lines.push("### Task Management (via opentasks)");
185
- lines.push("");
186
- lines.push("Use **opentasks MCP tools** for all task management:");
187
- lines.push(
188
- "- **opentasks__list_tasks** — check available tasks and their status"
189
- );
190
- lines.push(
191
- "- **opentasks__update_task** — claim tasks (set assignee to your name), update status"
192
- );
193
- if (position === "root" || position === "companion") {
194
- lines.push(
195
- "- **opentasks__create_task** — create new tasks for the team when you identify additional work"
196
- );
197
- }
198
- lines.push("");
199
- lines.push(
200
- "After completing a task, mark it completed via opentasks__update_task, then check opentasks__list_tasks for your next assignment."
201
- );
202
- lines.push("");
203
- } else {
204
- lines.push("### Task Management");
205
- lines.push("");
206
- lines.push("Use Claude Code's native task tools:");
207
- lines.push(
208
- "- **TaskList** — check available tasks and their status"
209
- );
210
- lines.push(
211
- "- **TaskUpdate** — claim tasks (set owner to your name), update status to in_progress or completed"
212
- );
213
- if (position === "root" || position === "companion") {
214
- lines.push(
215
- "- **TaskCreate** — create new tasks for the team when you identify additional work"
216
- );
217
- }
176
+ // Skills section (skill-tree loadout — role-specific content, kept separate)
177
+ if (skillLoadout) {
178
+ lines.push("## Skills");
218
179
  lines.push("");
219
- lines.push(
220
- "After completing a task, mark it completed with TaskUpdate, then check TaskList for your next assignment."
221
- );
180
+ lines.push(skillLoadout);
222
181
  lines.push("");
223
182
  }
224
183
 
225
- // Memory section (minimem)
226
- if (minimemEnabled) {
227
- lines.push("### Memory");
184
+ // Agent inbox identity (embedded per-agent so each agent knows its inbox ID)
185
+ if (inboxEnabled) {
186
+ const agentInboxId = `${teamName}-${roleName}`;
187
+ lines.push("## Your Agent Inbox Identity");
228
188
  lines.push("");
229
- lines.push("Use **minimem MCP tools** to recall and store knowledge:");
230
- lines.push("- **minimem__memory_search** — search past decisions, context, patterns");
231
- lines.push("- **minimem__memory_get_details** — get full text for a search result");
232
- lines.push("- **minimem__knowledge_search** — search with domain/entity filters");
189
+ lines.push(`Your inbox ID is **${agentInboxId}**. Use this with agent-inbox MCP tools:`);
233
190
  lines.push("");
234
- lines.push("Before starting major work, search memory for relevant prior context.");
191
+ lines.push(`- Check your inbox: \`agent-inbox__check_inbox(agentId: "${agentInboxId}")\``);
192
+ lines.push(`- Send messages: \`agent-inbox__send_message(from: "${agentInboxId}", to: "<recipient>", body: "...")\``);
193
+ lines.push(`- Read threads: \`agent-inbox__read_thread(threadTag: "...")\``);
194
+ lines.push("- List agents: `agent-inbox__list_agents()`");
235
195
  lines.push("");
236
- }
237
-
238
- // Skills section (skill-tree loadout)
239
- if (skillLoadout) {
240
- lines.push("## Skills");
241
- lines.push("");
242
- lines.push(skillLoadout);
196
+ lines.push("Check your inbox periodically for messages from teammates and external systems.");
197
+ lines.push("Use `agent@system` addressing for cross-system (federated) messages.");
243
198
  lines.push("");
244
199
  }
245
200
 
246
- // Optional MAP note
247
- lines.push("### External Observability (MAP)");
248
- lines.push("");
249
- lines.push(
250
- "If MAP is enabled, lifecycle events are automatically emitted for external dashboards."
251
- );
252
- lines.push(
253
- "You do not need to interact with MAP directly — it is handled by hooks."
254
- );
201
+ // Unified capabilities context (shared with main agent)
202
+ lines.push(buildCapabilitiesContext({
203
+ role: roleName,
204
+ teamName,
205
+ opentasksEnabled,
206
+ opentasksStatus: opentasksStatus || (opentasksEnabled ? "enabled" : "disabled"),
207
+ minimemEnabled,
208
+ minimemStatus: minimemStatus || (minimemEnabled ? "ready" : "disabled"),
209
+ skilltreeEnabled,
210
+ skilltreeStatus: skilltreeStatus || (skilltreeEnabled ? "ready" : "disabled"),
211
+ skillProfile: skillProfile || "",
212
+ inboxEnabled,
213
+ meshEnabled,
214
+ mapEnabled,
215
+ mapStatus: mapStatus || "disabled",
216
+ sessionlogSync: sessionlogSync || "off",
217
+ }));
255
218
 
256
219
  return lines.join("\n") + "\n";
257
220
  }
@@ -282,6 +245,7 @@ export async function generateAllAgents(templateDir, outputDir, options = {}) {
282
245
  }
283
246
 
284
247
  // Load skill loadouts if available (compiled by skilltree-client during team loading)
248
+ // Format: { roleName: { content, profile } } (or legacy string format)
285
249
  let skillLoadouts = {};
286
250
  const loadoutsPath = path.join(absOutputDir, "..", "skill-loadouts.json");
287
251
  if (fs.existsSync(loadoutsPath)) {
@@ -289,6 +253,13 @@ export async function generateAllAgents(templateDir, outputDir, options = {}) {
289
253
  skillLoadouts = JSON.parse(fs.readFileSync(loadoutsPath, "utf-8"));
290
254
  } catch { /* ignore */ }
291
255
  }
256
+ // Helper to extract content and profile from loadout entry (handles both formats)
257
+ function getLoadout(roleName) {
258
+ const entry = skillLoadouts[roleName];
259
+ if (!entry) return { content: "", profile: "" };
260
+ if (typeof entry === "string") return { content: entry, profile: "" };
261
+ return { content: entry.content || "", profile: entry.profile || "" };
262
+ }
292
263
 
293
264
  const generatedRoles = [];
294
265
 
@@ -331,8 +302,18 @@ export async function generateAllAgents(templateDir, outputDir, options = {}) {
331
302
  skillContent: roleSkill.content,
332
303
  manifest,
333
304
  opentasksEnabled: options.opentasksEnabled,
305
+ opentasksStatus: options.opentasksStatus,
334
306
  minimemEnabled: options.minimemEnabled,
335
- skillLoadout: skillLoadouts[roleName] || "",
307
+ minimemStatus: options.minimemStatus,
308
+ skilltreeEnabled: options.skilltreeEnabled,
309
+ skilltreeStatus: options.skilltreeStatus,
310
+ inboxEnabled: options.inboxEnabled,
311
+ meshEnabled: options.meshEnabled,
312
+ mapEnabled: options.mapEnabled,
313
+ mapStatus: options.mapStatus,
314
+ sessionlogSync: options.sessionlogSync,
315
+ skillLoadout: getLoadout(roleName).content,
316
+ skillProfile: getLoadout(roleName).profile,
336
317
  });
337
318
 
338
319
  const agentDir = path.join(absOutputDir, roleName);
@@ -377,8 +358,18 @@ export async function generateAllAgents(templateDir, outputDir, options = {}) {
377
358
  description: `${roleName} agent in the ${manifest.name} team`,
378
359
  tools: fallbackTools,
379
360
  opentasksEnabled: options.opentasksEnabled,
361
+ opentasksStatus: options.opentasksStatus,
380
362
  minimemEnabled: options.minimemEnabled,
381
- skillLoadout: skillLoadouts[roleName] || "",
363
+ minimemStatus: options.minimemStatus,
364
+ skilltreeEnabled: options.skilltreeEnabled,
365
+ skilltreeStatus: options.skilltreeStatus,
366
+ inboxEnabled: options.inboxEnabled,
367
+ meshEnabled: options.meshEnabled,
368
+ mapEnabled: options.mapEnabled,
369
+ mapStatus: options.mapStatus,
370
+ sessionlogSync: options.sessionlogSync,
371
+ skillLoadout: getLoadout(roleName).content,
372
+ skillProfile: getLoadout(roleName).profile,
382
373
  skillContent: prompt
383
374
  ? `# Role: ${roleName}\n\nMember of the **${manifest.name}** team.\n\n## Instructions\n\n${prompt}`
384
375
  : `# Role: ${roleName}\n\nMember of the **${manifest.name}** team.`,