agentopology 0.1.1 → 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 (62) hide show
  1. package/README.md +1 -1
  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 +367 -5
  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 +104 -16
  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
  62. package/examples/simple-pipeline-topology.html +0 -5700
@@ -222,7 +222,7 @@ function generateOpenClawJson(ast) {
222
222
  name: toTitle(ast.topology.name) + " Coordinator",
223
223
  model: orchModel,
224
224
  workspace: `~/.openclaw/workspace-${ast.topology.name}`,
225
- agentDir: `./config/agents/orchestrator/agent`,
225
+ agentDir: `~/.openclaw/config/agents/orchestrator/agent`,
226
226
  subagents: {
227
227
  allowAgents: agentIds,
228
228
  },
@@ -242,7 +242,7 @@ function generateOpenClawJson(ast) {
242
242
  name: toTitle(agent.id),
243
243
  model: agentModel,
244
244
  workspace: `~/.openclaw/workspace-${agent.id}`,
245
- agentDir: `./config/agents/${agent.id}/agent`,
245
+ agentDir: `~/.openclaw/config/agents/${agent.id}/agent`,
246
246
  };
247
247
  // No per-agent tools in openclaw.json — tool restrictions go in AGENTS.md
248
248
  // Sampling params
@@ -252,6 +252,30 @@ function generateOpenClawJson(ast) {
252
252
  entry.maxTokens = agent.maxTokens;
253
253
  // Note: OpenClaw does NOT support per-agent "timeout" in openclaw.json.
254
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
+ }
255
279
  // Merge agent-level openclaw extensions
256
280
  if (agent.extensions?.openclaw) {
257
281
  for (const [k, v] of Object.entries(agent.extensions.openclaw)) {
@@ -720,6 +744,56 @@ function generateAgentsMd(ast) {
720
744
  if (agent.writes && agent.writes.length > 0) {
721
745
  sections.push(`- **Writes:** ${agent.writes.join(", ")}`);
722
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
+ }
723
797
  // Outputs
724
798
  if (agent.outputs) {
725
799
  sections.push("- **Outputs:**");
@@ -1217,6 +1291,127 @@ function generateMemoryMd(ast) {
1217
1291
  sections.push(`Mode: ${metrics.mode}`);
1218
1292
  sections.push("");
1219
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
+ }
1220
1415
  // Agent access rules
1221
1416
  const agentsWithAccess = agents.filter((a) => (a.reads && a.reads.length > 0) || (a.writes && a.writes.length > 0));
1222
1417
  if (agentsWithAccess.length > 0) {
@@ -1364,9 +1559,11 @@ function generateSkillFiles(ast) {
1364
1559
  const files = [];
1365
1560
  for (const skill of ast.skills) {
1366
1561
  const sections = [];
1367
- // 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.
1368
1564
  sections.push("---");
1369
1565
  sections.push(`name: ${skill.id}`);
1566
+ sections.push(`version: "${ast.topology.version}"`);
1370
1567
  if (skill.description) {
1371
1568
  sections.push(`description: ${JSON.stringify(skill.description)}`);
1372
1569
  }
@@ -1414,10 +1611,11 @@ function generateSkillFiles(ast) {
1414
1611
  function generateToolSkills(ast) {
1415
1612
  const files = [];
1416
1613
  for (const tool of ast.toolDefs) {
1417
- // SKILL.md wrapper — OpenClaw requires YAML frontmatter
1614
+ // SKILL.md wrapper — OpenClaw requires name, version, AND description.
1418
1615
  const sections = [];
1419
1616
  sections.push("---");
1420
1617
  sections.push(`name: ${tool.id}`);
1618
+ sections.push(`version: "${ast.topology.version}"`);
1421
1619
  sections.push(`description: ${JSON.stringify(tool.description)}`);
1422
1620
  sections.push("---");
1423
1621
  sections.push("");
@@ -1514,6 +1712,11 @@ function generateMemoryDirs(ast) {
1514
1712
  }
1515
1713
  // Domains dir
1516
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
+ }
1517
1720
  return files;
1518
1721
  }
1519
1722
  /** Generate metering script + metrics file. */
@@ -1550,6 +1753,163 @@ function generateMetering(ast) {
1550
1753
  });
1551
1754
  return files;
1552
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
+ }
1553
1913
  // ---------------------------------------------------------------------------
1554
1914
  // Binding export
1555
1915
  // ---------------------------------------------------------------------------
@@ -1584,7 +1944,9 @@ export const openClawBinding = {
1584
1944
  // 5. Memory and skills directory markers
1585
1945
  files.push(gitkeep("memory"));
1586
1946
  files.push(gitkeep("skills"));
1587
- // 6. Workspace protocol
1947
+ // 6. Per-agent identity directories (config/agents/<id>/agent/)
1948
+ files.push(...generatePerAgentDirs(ast));
1949
+ // 7. Workspace protocol
1588
1950
  const protocol = generateWorkspaceProtocol(ast);
1589
1951
  if (protocol)
1590
1952
  files.push(protocol);