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.
- package/README.md +70 -33
- package/dist/analyzer/index.d.ts +1 -0
- package/dist/analyzer/index.d.ts.map +1 -1
- package/dist/analyzer/index.js +35 -0
- package/dist/analyzer/index.js.map +1 -1
- package/dist/bindings/claude-code.d.ts.map +1 -1
- package/dist/bindings/claude-code.js +283 -2
- package/dist/bindings/claude-code.js.map +1 -1
- package/dist/bindings/openclaw.d.ts.map +1 -1
- package/dist/bindings/openclaw.js +390 -12
- package/dist/bindings/openclaw.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/docs/__tests__/docs.test.js +2 -1
- package/dist/docs/__tests__/docs.test.js.map +1 -1
- package/dist/docs/content.d.ts.map +1 -1
- package/dist/docs/content.js +730 -4
- package/dist/docs/content.js.map +1 -1
- package/dist/exporters/markdown.d.ts.map +1 -1
- package/dist/exporters/markdown.js +165 -0
- package/dist/exporters/markdown.js.map +1 -1
- package/dist/exporters/mermaid.d.ts.map +1 -1
- package/dist/exporters/mermaid.js +30 -0
- package/dist/exporters/mermaid.js.map +1 -1
- package/dist/import/__tests__/import.test.js +554 -0
- package/dist/import/__tests__/import.test.js.map +1 -1
- package/dist/import/claude-code.d.ts.map +1 -1
- package/dist/import/claude-code.js +2 -0
- package/dist/import/claude-code.js.map +1 -1
- package/dist/import/index.d.ts +1 -0
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +7 -1
- package/dist/import/index.js.map +1 -1
- package/dist/import/openclaw.d.ts +70 -0
- package/dist/import/openclaw.d.ts.map +1 -0
- package/dist/import/openclaw.js +1060 -0
- package/dist/import/openclaw.js.map +1 -0
- package/dist/import/serializer.d.ts.map +1 -1
- package/dist/import/serializer.js +171 -4
- package/dist/import/serializer.js.map +1 -1
- package/dist/parser/__tests__/parser.test.js +463 -0
- package/dist/parser/__tests__/parser.test.js.map +1 -1
- package/dist/parser/ast.d.ts +122 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/index.d.ts +14 -2
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +226 -5
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/validator.d.ts +1 -1
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +214 -1
- package/dist/parser/validator.js.map +1 -1
- package/dist/visualizer/index.d.ts.map +1 -1
- package/dist/visualizer/index.js +263 -27
- package/dist/visualizer/index.js.map +1 -1
- package/examples/memory-system.at +162 -0
- package/examples/script-agent-pipeline.at +65 -0
- package/package.json +1 -1
- package/spec/grammar.md +345 -10
- package/spec/reserved-keywords.md +25 -1
- 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
|
-
*
|
|
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
|
-
//
|
|
68
|
-
if (model
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
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
|
|
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.
|
|
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);
|