agentopology 0.1.0 → 0.2.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.
Files changed (61) hide show
  1. package/README.md +70 -33
  2. package/dist/analyzer/index.d.ts +1 -0
  3. package/dist/analyzer/index.d.ts.map +1 -1
  4. package/dist/analyzer/index.js +35 -0
  5. package/dist/analyzer/index.js.map +1 -1
  6. package/dist/bindings/claude-code.d.ts.map +1 -1
  7. package/dist/bindings/claude-code.js +283 -2
  8. package/dist/bindings/claude-code.js.map +1 -1
  9. package/dist/bindings/openclaw.d.ts.map +1 -1
  10. package/dist/bindings/openclaw.js +390 -12
  11. package/dist/bindings/openclaw.js.map +1 -1
  12. package/dist/cli/index.js +2 -2
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/docs/__tests__/docs.test.js +2 -1
  15. package/dist/docs/__tests__/docs.test.js.map +1 -1
  16. package/dist/docs/content.d.ts.map +1 -1
  17. package/dist/docs/content.js +730 -4
  18. package/dist/docs/content.js.map +1 -1
  19. package/dist/exporters/markdown.d.ts.map +1 -1
  20. package/dist/exporters/markdown.js +165 -0
  21. package/dist/exporters/markdown.js.map +1 -1
  22. package/dist/exporters/mermaid.d.ts.map +1 -1
  23. package/dist/exporters/mermaid.js +30 -0
  24. package/dist/exporters/mermaid.js.map +1 -1
  25. package/dist/import/__tests__/import.test.js +554 -0
  26. package/dist/import/__tests__/import.test.js.map +1 -1
  27. package/dist/import/claude-code.d.ts.map +1 -1
  28. package/dist/import/claude-code.js +2 -0
  29. package/dist/import/claude-code.js.map +1 -1
  30. package/dist/import/index.d.ts +1 -0
  31. package/dist/import/index.d.ts.map +1 -1
  32. package/dist/import/index.js +7 -1
  33. package/dist/import/index.js.map +1 -1
  34. package/dist/import/openclaw.d.ts +70 -0
  35. package/dist/import/openclaw.d.ts.map +1 -0
  36. package/dist/import/openclaw.js +1060 -0
  37. package/dist/import/openclaw.js.map +1 -0
  38. package/dist/import/serializer.d.ts.map +1 -1
  39. package/dist/import/serializer.js +171 -4
  40. package/dist/import/serializer.js.map +1 -1
  41. package/dist/parser/__tests__/parser.test.js +463 -0
  42. package/dist/parser/__tests__/parser.test.js.map +1 -1
  43. package/dist/parser/ast.d.ts +122 -0
  44. package/dist/parser/ast.d.ts.map +1 -1
  45. package/dist/parser/index.d.ts +14 -2
  46. package/dist/parser/index.d.ts.map +1 -1
  47. package/dist/parser/index.js +226 -5
  48. package/dist/parser/index.js.map +1 -1
  49. package/dist/parser/validator.d.ts +1 -1
  50. package/dist/parser/validator.d.ts.map +1 -1
  51. package/dist/parser/validator.js +214 -1
  52. package/dist/parser/validator.js.map +1 -1
  53. package/dist/visualizer/index.d.ts.map +1 -1
  54. package/dist/visualizer/index.js +263 -27
  55. package/dist/visualizer/index.js.map +1 -1
  56. package/examples/memory-system.at +162 -0
  57. package/examples/script-agent-pipeline.at +65 -0
  58. package/package.json +1 -1
  59. package/spec/grammar.md +345 -10
  60. package/spec/reserved-keywords.md +25 -1
  61. package/spec/validation.md +171 -2
@@ -55,7 +55,15 @@ function gitkeep(dirPath) {
55
55
  }
56
56
  /**
57
57
  * Map topology model strings to OpenClaw model identifiers.
58
- * OpenClaw uses `provider/model-id` format with hyphens.
58
+ *
59
+ * OpenClaw resolves the provider from the FIRST segment of the model ID
60
+ * (everything before the first "/"). For proxy providers like OpenRouter,
61
+ * the model ID must include both the proxy provider AND the upstream
62
+ * model path: `openrouter/google/gemini-2.5-flash`.
63
+ *
64
+ * When the .at file specifies a model like `google/gemini-2.5-flash`
65
+ * under an `openrouter` provider, the binding prepends the provider
66
+ * name to produce the correct 3-segment ID.
59
67
  */
60
68
  function mapModel(model, defaultProvider) {
61
69
  const provider = defaultProvider ?? "anthropic";
@@ -64,10 +72,19 @@ function mapModel(model, defaultProvider) {
64
72
  sonnet: `${provider}/claude-sonnet-4-6`,
65
73
  haiku: `${provider}/claude-haiku-4-5`,
66
74
  };
67
- // If model already has provider prefix, return as-is
68
- if (model.includes("/"))
75
+ // Alias lookup (short names like "opus", "sonnet", "haiku")
76
+ if (MODEL_MAP[model])
77
+ return MODEL_MAP[model];
78
+ // If model already starts with the provider prefix, return as-is
79
+ if (model.startsWith(`${provider}/`))
69
80
  return model;
70
- return MODEL_MAP[model] ?? `${provider}/${model}`;
81
+ // If model has a slash (e.g. "google/gemini-2.5-flash") but doesn't
82
+ // start with the provider, prepend the provider so OpenClaw routes
83
+ // through the correct provider (e.g. "openrouter/google/gemini-2.5-flash")
84
+ if (model.includes("/"))
85
+ return `${provider}/${model}`;
86
+ // Bare model name — prepend provider
87
+ return `${provider}/${model}`;
71
88
  }
72
89
  /**
73
90
  * Map topology permission strings to OpenClaw permission labels.
@@ -205,7 +222,7 @@ function generateOpenClawJson(ast) {
205
222
  name: toTitle(ast.topology.name) + " Coordinator",
206
223
  model: orchModel,
207
224
  workspace: `~/.openclaw/workspace-${ast.topology.name}`,
208
- agentDir: `./config/agents/orchestrator/agent`,
225
+ agentDir: `~/.openclaw/config/agents/orchestrator/agent`,
209
226
  subagents: {
210
227
  allowAgents: agentIds,
211
228
  },
@@ -225,7 +242,7 @@ function generateOpenClawJson(ast) {
225
242
  name: toTitle(agent.id),
226
243
  model: agentModel,
227
244
  workspace: `~/.openclaw/workspace-${agent.id}`,
228
- agentDir: `./config/agents/${agent.id}/agent`,
245
+ agentDir: `~/.openclaw/config/agents/${agent.id}/agent`,
229
246
  };
230
247
  // No per-agent tools in openclaw.json — tool restrictions go in AGENTS.md
231
248
  // Sampling params
@@ -233,9 +250,32 @@ function generateOpenClawJson(ast) {
233
250
  entry.temperature = agent.temperature;
234
251
  if (agent.maxTokens != null)
235
252
  entry.maxTokens = agent.maxTokens;
236
- // Timeout
237
- if (agent.timeout)
238
- entry.timeout = agent.timeout;
253
+ // Note: OpenClaw does NOT support per-agent "timeout" in openclaw.json.
254
+ // Timeouts are documented in AGENTS.md instead.
255
+ // subagents.allowAgents — OpenClaw requires explicit delegation lists
256
+ // for agents to use sessions_spawn. We detect supervisors using:
257
+ // 1. The topology's interface entry point (if defined)
258
+ // 2. The first agent by phase order
259
+ // 3. Any agent that is the target of error edges (error handlers coordinate)
260
+ // Supervisors get allowAgents for ALL other agents in the topology,
261
+ // because OpenClaw delegation is about permission, not flow direction.
262
+ const isSupervisor =
263
+ // Interface entry point
264
+ (ast.interfaceEndpoints?.entry === agent.id) ||
265
+ // First agent by phase (lowest phase = coordinator)
266
+ (agents.length > 0 && agents[0].id === agent.id) ||
267
+ // Error handler target (receives error edges from other agents)
268
+ ast.edges.some((e) => e.isError && e.to === agent.id) ||
269
+ // Topology declares "supervisor" pattern and this is the first agent
270
+ (ast.topology.patterns.includes("supervisor") && agents[0]?.id === agent.id);
271
+ if (isSupervisor) {
272
+ const otherAgentIds = agents
273
+ .filter((a) => a.id !== agent.id)
274
+ .map((a) => a.id);
275
+ if (otherAgentIds.length > 0) {
276
+ entry.subagents = { allowAgents: otherAgentIds };
277
+ }
278
+ }
239
279
  // Merge agent-level openclaw extensions
240
280
  if (agent.extensions?.openclaw) {
241
281
  for (const [k, v] of Object.entries(agent.extensions.openclaw)) {
@@ -704,6 +744,56 @@ function generateAgentsMd(ast) {
704
744
  if (agent.writes && agent.writes.length > 0) {
705
745
  sections.push(`- **Writes:** ${agent.writes.join(", ")}`);
706
746
  }
747
+ // Memory stores — rich details per store
748
+ if (agent.memory && agent.memory.length > 0) {
749
+ const storeMap = new Map((ast.stores ?? []).map(s => [s.id, s]));
750
+ sections.push("- **Memory stores:**");
751
+ for (const storeId of agent.memory) {
752
+ const store = storeMap.get(storeId);
753
+ if (!store) {
754
+ sections.push(` - ${storeId}`);
755
+ continue;
756
+ }
757
+ const parts = [`**${storeId}**`, `type: ${store.type}`, `backend: ${store.backend}`];
758
+ if (store.path)
759
+ parts.push(`path: \`${store.path}\``);
760
+ if (store.scope)
761
+ parts.push(`scope: ${store.scope}`);
762
+ if (store.embedding?.model)
763
+ parts.push(`embedding: ${store.embedding.model} (${store.embedding.dimensions}d)`);
764
+ if (store.search?.strategy)
765
+ parts.push(`search: ${store.search.strategy}`);
766
+ sections.push(` - ${parts.join(" — ")}`);
767
+ }
768
+ }
769
+ // Retrieval strategy — rich details
770
+ if (agent.retrieval) {
771
+ const retrieval = (ast.retrievals ?? []).find(r => r.id === agent.retrieval);
772
+ if (retrieval) {
773
+ const parts = [`**${retrieval.id}**`];
774
+ if (retrieval.sources)
775
+ parts.push(`sources: ${retrieval.sources.join(", ")}`);
776
+ if (retrieval.budget)
777
+ parts.push(`budget: ${retrieval.budget} tokens`);
778
+ if (retrieval.rerank)
779
+ parts.push(`reranked`);
780
+ if (retrieval.scoring) {
781
+ const weights = [];
782
+ if (retrieval.scoring.semanticWeight != null)
783
+ weights.push(`semantic: ${retrieval.scoring.semanticWeight}`);
784
+ if (retrieval.scoring.recencyWeight != null)
785
+ weights.push(`recency: ${retrieval.scoring.recencyWeight}`);
786
+ if (retrieval.scoring.importanceWeight != null)
787
+ weights.push(`importance: ${retrieval.scoring.importanceWeight}`);
788
+ if (weights.length)
789
+ parts.push(`scoring: ${weights.join(", ")}`);
790
+ }
791
+ sections.push(`- **Retrieval strategy:** ${parts.join(" — ")}`);
792
+ }
793
+ else {
794
+ sections.push(`- **Retrieval strategy:** ${agent.retrieval}`);
795
+ }
796
+ }
707
797
  // Outputs
708
798
  if (agent.outputs) {
709
799
  sections.push("- **Outputs:**");
@@ -1201,6 +1291,127 @@ function generateMemoryMd(ast) {
1201
1291
  sections.push(`Mode: ${metrics.mode}`);
1202
1292
  sections.push("");
1203
1293
  }
1294
+ // Memory Stores — full details
1295
+ const stores = ast.stores ?? [];
1296
+ if (stores.length > 0) {
1297
+ sections.push("## Memory Stores");
1298
+ sections.push("");
1299
+ for (const store of stores) {
1300
+ sections.push(`### ${store.id}`);
1301
+ if (store.description)
1302
+ sections.push(store.description);
1303
+ sections.push("");
1304
+ sections.push(`- **Type**: ${store.type}`);
1305
+ sections.push(`- **Backend**: ${store.backend}`);
1306
+ if (store.path)
1307
+ sections.push(`- **Path**: \`${store.path}\``);
1308
+ if (store.connection)
1309
+ sections.push(`- **Connection**: ${store.connection}`);
1310
+ if (store.scope)
1311
+ sections.push(`- **Scope**: ${store.scope}`);
1312
+ if (store.isolation)
1313
+ sections.push(`- **Isolation**: ${store.isolation}`);
1314
+ if (store.extraction)
1315
+ sections.push(`- **Extraction**: ${store.extraction}`);
1316
+ // Embedding
1317
+ if (store.embedding) {
1318
+ const emb = store.embedding;
1319
+ sections.push(`- **Embedding**: ${emb.model} (${emb.provider}, ${emb.dimensions}d)`);
1320
+ }
1321
+ // Index
1322
+ if (store.index) {
1323
+ const parts = [];
1324
+ if (store.index.collection)
1325
+ parts.push(`collection: ${store.index.collection}`);
1326
+ if (store.index.metric)
1327
+ parts.push(`metric: ${store.index.metric}`);
1328
+ if (parts.length)
1329
+ sections.push(`- **Index**: ${parts.join(", ")}`);
1330
+ }
1331
+ // Search
1332
+ if (store.search) {
1333
+ const parts = [`strategy: ${store.search.strategy}`];
1334
+ if (store.search.topK)
1335
+ parts.push(`top-k: ${store.search.topK}`);
1336
+ if (store.search.rerank)
1337
+ parts.push(`rerank: yes`);
1338
+ sections.push(`- **Search**: ${parts.join(", ")}`);
1339
+ }
1340
+ // Ingestion
1341
+ if (store.ingestion) {
1342
+ const parts = [];
1343
+ if (store.ingestion.sources)
1344
+ parts.push(`sources: ${store.ingestion.sources.join(", ")}`);
1345
+ if (store.ingestion.chunking)
1346
+ parts.push(`chunking: ${store.ingestion.chunking}`);
1347
+ if (store.ingestion.chunkSize)
1348
+ parts.push(`size: ${store.ingestion.chunkSize}`);
1349
+ if (store.ingestion.overlap)
1350
+ parts.push(`overlap: ${store.ingestion.overlap}`);
1351
+ if (parts.length)
1352
+ sections.push(`- **Ingestion**: ${parts.join(", ")}`);
1353
+ }
1354
+ // Lifecycle
1355
+ if (store.lifecycle) {
1356
+ const lc = store.lifecycle;
1357
+ if (lc.retention)
1358
+ sections.push(`- **Retention**: ${lc.retention}`);
1359
+ if (lc.decayHalfLife)
1360
+ sections.push(`- **Decay half-life**: ${lc.decayHalfLife}`);
1361
+ if (lc.consolidation != null)
1362
+ sections.push(`- **Consolidation threshold**: ${lc.consolidation}`);
1363
+ if (lc.contradiction)
1364
+ sections.push(`- **Contradiction handling**: ${lc.contradiction}`);
1365
+ if (lc.auditLog != null)
1366
+ sections.push(`- **Audit log**: ${lc.auditLog}`);
1367
+ }
1368
+ // Which agents use this store
1369
+ const storeAgents = agents.filter(a => a.memory?.includes(store.id));
1370
+ if (storeAgents.length > 0) {
1371
+ sections.push(`- **Used by**: ${storeAgents.map(a => a.id).join(", ")}`);
1372
+ }
1373
+ sections.push("");
1374
+ }
1375
+ }
1376
+ // Retrieval Strategies
1377
+ const retrievals = ast.retrievals ?? [];
1378
+ if (retrievals.length > 0) {
1379
+ sections.push("## Retrieval Strategies");
1380
+ sections.push("");
1381
+ for (const ret of retrievals) {
1382
+ sections.push(`### ${ret.id}`);
1383
+ if (ret.sources)
1384
+ sections.push(`- **Sources**: ${ret.sources.join(", ")}`);
1385
+ if (ret.budget)
1386
+ sections.push(`- **Token budget**: ${ret.budget}`);
1387
+ if (ret.paths)
1388
+ sections.push(`- **Search paths**: ${ret.paths.join(", ")}`);
1389
+ if (ret.rerank)
1390
+ sections.push(`- **Reranking**: enabled`);
1391
+ if (ret.diversity)
1392
+ sections.push(`- **Diversity**: enabled`);
1393
+ if (ret.cacheHitThreshold != null) {
1394
+ sections.push(`- **Cache hit**: threshold ${ret.cacheHitThreshold} → ${ret.cacheHitAction ?? "short-circuit"}`);
1395
+ }
1396
+ if (ret.scoring) {
1397
+ const weights = [];
1398
+ if (ret.scoring.recencyWeight != null)
1399
+ weights.push(`recency: ${ret.scoring.recencyWeight}`);
1400
+ if (ret.scoring.semanticWeight != null)
1401
+ weights.push(`semantic: ${ret.scoring.semanticWeight}`);
1402
+ if (ret.scoring.importanceWeight != null)
1403
+ weights.push(`importance: ${ret.scoring.importanceWeight}`);
1404
+ if (weights.length)
1405
+ sections.push(`- **Scoring**: ${weights.join(", ")}`);
1406
+ }
1407
+ // Which agents use this retrieval
1408
+ const retAgents = agents.filter(a => a.retrieval === ret.id);
1409
+ if (retAgents.length > 0) {
1410
+ sections.push(`- **Used by**: ${retAgents.map(a => a.id).join(", ")}`);
1411
+ }
1412
+ sections.push("");
1413
+ }
1414
+ }
1204
1415
  // Agent access rules
1205
1416
  const agentsWithAccess = agents.filter((a) => (a.reads && a.reads.length > 0) || (a.writes && a.writes.length > 0));
1206
1417
  if (agentsWithAccess.length > 0) {
@@ -1348,9 +1559,11 @@ function generateSkillFiles(ast) {
1348
1559
  const files = [];
1349
1560
  for (const skill of ast.skills) {
1350
1561
  const sections = [];
1351
- // OpenClaw requires YAML frontmatter with name and description
1562
+ // OpenClaw requires YAML frontmatter with name, version, AND description.
1563
+ // Missing any of the three = skill silently not registered.
1352
1564
  sections.push("---");
1353
1565
  sections.push(`name: ${skill.id}`);
1566
+ sections.push(`version: "${ast.topology.version}"`);
1354
1567
  if (skill.description) {
1355
1568
  sections.push(`description: ${JSON.stringify(skill.description)}`);
1356
1569
  }
@@ -1398,10 +1611,11 @@ function generateSkillFiles(ast) {
1398
1611
  function generateToolSkills(ast) {
1399
1612
  const files = [];
1400
1613
  for (const tool of ast.toolDefs) {
1401
- // SKILL.md wrapper — OpenClaw requires YAML frontmatter
1614
+ // SKILL.md wrapper — OpenClaw requires name, version, AND description.
1402
1615
  const sections = [];
1403
1616
  sections.push("---");
1404
1617
  sections.push(`name: ${tool.id}`);
1618
+ sections.push(`version: "${ast.topology.version}"`);
1405
1619
  sections.push(`description: ${JSON.stringify(tool.description)}`);
1406
1620
  sections.push("---");
1407
1621
  sections.push("");
@@ -1498,6 +1712,11 @@ function generateMemoryDirs(ast) {
1498
1712
  }
1499
1713
  // Domains dir
1500
1714
  files.push(gitkeep("domains"));
1715
+ // Store directory markers
1716
+ const stores = ast.stores ?? [];
1717
+ for (const store of stores) {
1718
+ files.push(gitkeep(`stores/${store.id}`));
1719
+ }
1501
1720
  return files;
1502
1721
  }
1503
1722
  /** Generate metering script + metrics file. */
@@ -1534,6 +1753,163 @@ function generateMetering(ast) {
1534
1753
  });
1535
1754
  return files;
1536
1755
  }
1756
+ /**
1757
+ * Generate per-agent identity directories.
1758
+ *
1759
+ * OpenClaw reads agent identity from `config/agents/<id>/agent/`.
1760
+ * Each agent gets its own IDENTITY.md, MEMORY.md (scoped to its stores),
1761
+ * and SOUL.md with role-specific instructions.
1762
+ */
1763
+ function generatePerAgentDirs(ast) {
1764
+ const files = [];
1765
+ const agents = getAgentsSorted(ast);
1766
+ const orchestrator = getOrchestrator(ast);
1767
+ const storeMap = new Map((ast.stores ?? []).map(s => [s.id, s]));
1768
+ const defaultProvider = getDefaultProviderName(ast);
1769
+ // Helper: build per-agent MEMORY.md with only stores this agent uses
1770
+ function agentMemoryMd(agentId, storeIds, retrievalId) {
1771
+ const lines = ["# Memory Stores", ""];
1772
+ lines.push("You have access to the following memory stores. Use them to persist and retrieve knowledge.");
1773
+ lines.push("");
1774
+ for (const sid of storeIds) {
1775
+ const store = storeMap.get(sid);
1776
+ if (!store)
1777
+ continue;
1778
+ lines.push(`## ${store.id}`);
1779
+ if (store.description)
1780
+ lines.push(store.description);
1781
+ lines.push("");
1782
+ lines.push(`- **Type**: ${store.type}`);
1783
+ lines.push(`- **Backend**: ${store.backend}`);
1784
+ if (store.path)
1785
+ lines.push(`- **Path**: \`${store.path}\``);
1786
+ if (store.scope)
1787
+ lines.push(`- **Scope**: ${store.scope}`);
1788
+ if (store.embedding) {
1789
+ lines.push(`- **Embedding**: ${store.embedding.model} (${store.embedding.provider}, ${store.embedding.dimensions}d)`);
1790
+ }
1791
+ if (store.search) {
1792
+ const parts = [`strategy: ${store.search.strategy}`];
1793
+ if (store.search.topK)
1794
+ parts.push(`top-k: ${store.search.topK}`);
1795
+ if (store.search.rerank)
1796
+ parts.push(`rerank: yes`);
1797
+ lines.push(`- **Search**: ${parts.join(", ")}`);
1798
+ }
1799
+ if (store.ingestion?.sources) {
1800
+ lines.push(`- **Ingestion sources**: ${store.ingestion.sources.join(", ")}`);
1801
+ }
1802
+ if (store.lifecycle?.retention)
1803
+ lines.push(`- **Retention**: ${store.lifecycle.retention}`);
1804
+ lines.push("");
1805
+ }
1806
+ // Retrieval strategy
1807
+ if (retrievalId) {
1808
+ const retrieval = (ast.retrievals ?? []).find(r => r.id === retrievalId);
1809
+ if (retrieval) {
1810
+ lines.push("## Retrieval Strategy");
1811
+ lines.push("");
1812
+ lines.push(`**${retrieval.id}** — When you need to recall information:`);
1813
+ lines.push("");
1814
+ if (retrieval.sources)
1815
+ lines.push(`- **Sources**: ${retrieval.sources.join(", ")}`);
1816
+ if (retrieval.budget)
1817
+ lines.push(`- **Token budget**: ${retrieval.budget}`);
1818
+ if (retrieval.paths)
1819
+ lines.push(`- **Search paths**: ${retrieval.paths.join(", ")}`);
1820
+ if (retrieval.rerank)
1821
+ lines.push(`- **Reranking**: enabled`);
1822
+ if (retrieval.scoring) {
1823
+ const w = [];
1824
+ if (retrieval.scoring.semanticWeight != null)
1825
+ w.push(`semantic: ${retrieval.scoring.semanticWeight}`);
1826
+ if (retrieval.scoring.recencyWeight != null)
1827
+ w.push(`recency: ${retrieval.scoring.recencyWeight}`);
1828
+ if (retrieval.scoring.importanceWeight != null)
1829
+ w.push(`importance: ${retrieval.scoring.importanceWeight}`);
1830
+ if (w.length)
1831
+ lines.push(`- **Scoring**: ${w.join(", ")}`);
1832
+ }
1833
+ lines.push("");
1834
+ }
1835
+ }
1836
+ return lines.join("\n");
1837
+ }
1838
+ // Generate per-agent dirs
1839
+ const allAgents = orchestrator
1840
+ ? [{ id: "orchestrator", description: `${toTitle(ast.topology.name)} orchestrator`, model: orchestrator.model, memory: undefined, retrieval: undefined, prompt: undefined }]
1841
+ : [];
1842
+ for (const a of agents) {
1843
+ allAgents.push({ id: a.id, description: a.description ?? "", model: a.model ?? "sonnet", memory: a.memory, retrieval: a.retrieval, prompt: a.prompt });
1844
+ }
1845
+ for (const agent of allAgents) {
1846
+ const base = `config/agents/${agent.id}/agent`;
1847
+ // IDENTITY.md
1848
+ files.push({
1849
+ path: `${base}/IDENTITY.md`,
1850
+ content: [
1851
+ `# ${toTitle(agent.id)}`,
1852
+ "",
1853
+ agent.description || `Agent in the ${ast.topology.name} topology.`,
1854
+ "",
1855
+ `Model: ${mapModel(agent.model, defaultProvider)}`,
1856
+ "",
1857
+ ].join("\n"),
1858
+ category: "machine",
1859
+ });
1860
+ // MEMORY.md — scoped to this agent's stores
1861
+ if (agent.memory && agent.memory.length > 0) {
1862
+ files.push({
1863
+ path: `${base}/MEMORY.md`,
1864
+ content: agentMemoryMd(agent.id, agent.memory, agent.retrieval) + "\n",
1865
+ category: "machine",
1866
+ });
1867
+ }
1868
+ // SOUL.md — agent-specific instructions
1869
+ const soulLines = [
1870
+ `# ${toTitle(agent.id)} — Soul`,
1871
+ "",
1872
+ `You are the **${toTitle(agent.id)}** agent in the **${toTitle(ast.topology.name)}** topology.`,
1873
+ "",
1874
+ ];
1875
+ if (agent.description) {
1876
+ soulLines.push(`## Role`, "", agent.description, "");
1877
+ }
1878
+ if (agent.prompt) {
1879
+ soulLines.push("## Instructions", "", agent.prompt, "");
1880
+ }
1881
+ if (agent.memory && agent.memory.length > 0) {
1882
+ soulLines.push(`## Memory`, "", `You have access to these stores: ${agent.memory.join(", ")}. See MEMORY.md for details.`, "");
1883
+ }
1884
+ files.push({
1885
+ path: `${base}/SOUL.md`,
1886
+ content: soulLines.join("\n"),
1887
+ category: "machine",
1888
+ });
1889
+ }
1890
+ // OpenClaw injects ALL files from agentDir into the agent's system prompt.
1891
+ // The team-level AGENTS.md, TOOLS.md, USER.md, and BOOTSTRAP.md must be
1892
+ // copied into each agent's dir so the gateway can find them.
1893
+ // We re-generate the content here (same functions used for root-level files)
1894
+ // to avoid cross-function dependencies.
1895
+ const sharedContent = {
1896
+ "AGENTS.md": generateAgentsMd(ast).content,
1897
+ "TOOLS.md": generateToolsMd(ast).content,
1898
+ "USER.md": generateUserMd().content,
1899
+ "BOOTSTRAP.md": generateBootstrapMd(ast).content,
1900
+ };
1901
+ for (const agent of allAgents) {
1902
+ const base = `config/agents/${agent.id}/agent`;
1903
+ for (const [filename, content] of Object.entries(sharedContent)) {
1904
+ files.push({
1905
+ path: `${base}/${filename}`,
1906
+ content,
1907
+ category: "machine",
1908
+ });
1909
+ }
1910
+ }
1911
+ return files;
1912
+ }
1537
1913
  // ---------------------------------------------------------------------------
1538
1914
  // Binding export
1539
1915
  // ---------------------------------------------------------------------------
@@ -1568,7 +1944,9 @@ export const openClawBinding = {
1568
1944
  // 5. Memory and skills directory markers
1569
1945
  files.push(gitkeep("memory"));
1570
1946
  files.push(gitkeep("skills"));
1571
- // 6. Workspace protocol
1947
+ // 6. Per-agent identity directories (config/agents/<id>/agent/)
1948
+ files.push(...generatePerAgentDirs(ast));
1949
+ // 7. Workspace protocol
1572
1950
  const protocol = generateWorkspaceProtocol(ast);
1573
1951
  if (protocol)
1574
1952
  files.push(protocol);