agentopology 0.1.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 (174) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +450 -0
  3. package/dist/analyzer/__tests__/analyzer.test.d.ts +2 -0
  4. package/dist/analyzer/__tests__/analyzer.test.d.ts.map +1 -0
  5. package/dist/analyzer/__tests__/analyzer.test.js +258 -0
  6. package/dist/analyzer/__tests__/analyzer.test.js.map +1 -0
  7. package/dist/analyzer/index.d.ts +44 -0
  8. package/dist/analyzer/index.d.ts.map +1 -0
  9. package/dist/analyzer/index.js +308 -0
  10. package/dist/analyzer/index.js.map +1 -0
  11. package/dist/bindings/__tests__/bindings.test.d.ts +2 -0
  12. package/dist/bindings/__tests__/bindings.test.d.ts.map +1 -0
  13. package/dist/bindings/__tests__/bindings.test.js +2089 -0
  14. package/dist/bindings/__tests__/bindings.test.js.map +1 -0
  15. package/dist/bindings/claude-code.d.ts +12 -0
  16. package/dist/bindings/claude-code.d.ts.map +1 -0
  17. package/dist/bindings/claude-code.js +2063 -0
  18. package/dist/bindings/claude-code.js.map +1 -0
  19. package/dist/bindings/codex.d.ts +18 -0
  20. package/dist/bindings/codex.d.ts.map +1 -0
  21. package/dist/bindings/codex.js +1633 -0
  22. package/dist/bindings/codex.js.map +1 -0
  23. package/dist/bindings/copilot-cli.d.ts +20 -0
  24. package/dist/bindings/copilot-cli.d.ts.map +1 -0
  25. package/dist/bindings/copilot-cli.js +1515 -0
  26. package/dist/bindings/copilot-cli.js.map +1 -0
  27. package/dist/bindings/cursor.d.ts +17 -0
  28. package/dist/bindings/cursor.d.ts.map +1 -0
  29. package/dist/bindings/cursor.js +1715 -0
  30. package/dist/bindings/cursor.js.map +1 -0
  31. package/dist/bindings/gemini-cli.d.ts +18 -0
  32. package/dist/bindings/gemini-cli.d.ts.map +1 -0
  33. package/dist/bindings/gemini-cli.js +1327 -0
  34. package/dist/bindings/gemini-cli.js.map +1 -0
  35. package/dist/bindings/index.d.ts +20 -0
  36. package/dist/bindings/index.d.ts.map +1 -0
  37. package/dist/bindings/index.js +33 -0
  38. package/dist/bindings/index.js.map +1 -0
  39. package/dist/bindings/kiro.d.ts +17 -0
  40. package/dist/bindings/kiro.d.ts.map +1 -0
  41. package/dist/bindings/kiro.js +1624 -0
  42. package/dist/bindings/kiro.js.map +1 -0
  43. package/dist/bindings/openclaw.d.ts +18 -0
  44. package/dist/bindings/openclaw.d.ts.map +1 -0
  45. package/dist/bindings/openclaw.js +1578 -0
  46. package/dist/bindings/openclaw.js.map +1 -0
  47. package/dist/bindings/types.d.ts +33 -0
  48. package/dist/bindings/types.d.ts.map +1 -0
  49. package/dist/bindings/types.js +23 -0
  50. package/dist/bindings/types.js.map +1 -0
  51. package/dist/cli/index.d.ts +19 -0
  52. package/dist/cli/index.d.ts.map +1 -0
  53. package/dist/cli/index.js +679 -0
  54. package/dist/cli/index.js.map +1 -0
  55. package/dist/docs/__tests__/docs.test.d.ts +2 -0
  56. package/dist/docs/__tests__/docs.test.d.ts.map +1 -0
  57. package/dist/docs/__tests__/docs.test.js +438 -0
  58. package/dist/docs/__tests__/docs.test.js.map +1 -0
  59. package/dist/docs/content.d.ts +16 -0
  60. package/dist/docs/content.d.ts.map +1 -0
  61. package/dist/docs/content.js +3273 -0
  62. package/dist/docs/content.js.map +1 -0
  63. package/dist/docs/index.d.ts +17 -0
  64. package/dist/docs/index.d.ts.map +1 -0
  65. package/dist/docs/index.js +117 -0
  66. package/dist/docs/index.js.map +1 -0
  67. package/dist/exporters/__tests__/exporters.test.d.ts +2 -0
  68. package/dist/exporters/__tests__/exporters.test.d.ts.map +1 -0
  69. package/dist/exporters/__tests__/exporters.test.js +731 -0
  70. package/dist/exporters/__tests__/exporters.test.js.map +1 -0
  71. package/dist/exporters/index.d.ts +13 -0
  72. package/dist/exporters/index.d.ts.map +1 -0
  73. package/dist/exporters/index.js +18 -0
  74. package/dist/exporters/index.js.map +1 -0
  75. package/dist/exporters/json.d.ts +8 -0
  76. package/dist/exporters/json.d.ts.map +1 -0
  77. package/dist/exporters/json.js +25 -0
  78. package/dist/exporters/json.js.map +1 -0
  79. package/dist/exporters/markdown.d.ts +9 -0
  80. package/dist/exporters/markdown.d.ts.map +1 -0
  81. package/dist/exporters/markdown.js +686 -0
  82. package/dist/exporters/markdown.js.map +1 -0
  83. package/dist/exporters/mermaid.d.ts +11 -0
  84. package/dist/exporters/mermaid.d.ts.map +1 -0
  85. package/dist/exporters/mermaid.js +161 -0
  86. package/dist/exporters/mermaid.js.map +1 -0
  87. package/dist/exporters/types.d.ts +22 -0
  88. package/dist/exporters/types.d.ts.map +1 -0
  89. package/dist/exporters/types.js +10 -0
  90. package/dist/exporters/types.js.map +1 -0
  91. package/dist/import/__tests__/import.test.d.ts +2 -0
  92. package/dist/import/__tests__/import.test.d.ts.map +1 -0
  93. package/dist/import/__tests__/import.test.js +781 -0
  94. package/dist/import/__tests__/import.test.js.map +1 -0
  95. package/dist/import/claude-code.d.ts +38 -0
  96. package/dist/import/claude-code.d.ts.map +1 -0
  97. package/dist/import/claude-code.js +897 -0
  98. package/dist/import/claude-code.js.map +1 -0
  99. package/dist/import/index.d.ts +18 -0
  100. package/dist/import/index.d.ts.map +1 -0
  101. package/dist/import/index.js +28 -0
  102. package/dist/import/index.js.map +1 -0
  103. package/dist/import/serializer.d.ts +11 -0
  104. package/dist/import/serializer.d.ts.map +1 -0
  105. package/dist/import/serializer.js +960 -0
  106. package/dist/import/serializer.js.map +1 -0
  107. package/dist/index.d.ts +26 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +21 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/parser/__tests__/parser.test.d.ts +2 -0
  112. package/dist/parser/__tests__/parser.test.d.ts.map +1 -0
  113. package/dist/parser/__tests__/parser.test.js +6514 -0
  114. package/dist/parser/__tests__/parser.test.js.map +1 -0
  115. package/dist/parser/ast.d.ts +708 -0
  116. package/dist/parser/ast.d.ts.map +1 -0
  117. package/dist/parser/ast.js +12 -0
  118. package/dist/parser/ast.js.map +1 -0
  119. package/dist/parser/index.d.ts +253 -0
  120. package/dist/parser/index.d.ts.map +1 -0
  121. package/dist/parser/index.js +1985 -0
  122. package/dist/parser/index.js.map +1 -0
  123. package/dist/parser/lexer.d.ts +100 -0
  124. package/dist/parser/lexer.d.ts.map +1 -0
  125. package/dist/parser/lexer.js +304 -0
  126. package/dist/parser/lexer.js.map +1 -0
  127. package/dist/parser/validator.d.ts +42 -0
  128. package/dist/parser/validator.d.ts.map +1 -0
  129. package/dist/parser/validator.js +2568 -0
  130. package/dist/parser/validator.js.map +1 -0
  131. package/dist/scaffold/__tests__/scaffold.test.d.ts +7 -0
  132. package/dist/scaffold/__tests__/scaffold.test.d.ts.map +1 -0
  133. package/dist/scaffold/__tests__/scaffold.test.js +295 -0
  134. package/dist/scaffold/__tests__/scaffold.test.js.map +1 -0
  135. package/dist/scaffold/incremental.d.ts +43 -0
  136. package/dist/scaffold/incremental.d.ts.map +1 -0
  137. package/dist/scaffold/incremental.js +185 -0
  138. package/dist/scaffold/incremental.js.map +1 -0
  139. package/dist/scaffold/index.d.ts +13 -0
  140. package/dist/scaffold/index.d.ts.map +1 -0
  141. package/dist/scaffold/index.js +13 -0
  142. package/dist/scaffold/index.js.map +1 -0
  143. package/dist/scaffold/manifest.d.ts +30 -0
  144. package/dist/scaffold/manifest.d.ts.map +1 -0
  145. package/dist/scaffold/manifest.js +71 -0
  146. package/dist/scaffold/manifest.js.map +1 -0
  147. package/dist/scaffold/merge.d.ts +52 -0
  148. package/dist/scaffold/merge.d.ts.map +1 -0
  149. package/dist/scaffold/merge.js +134 -0
  150. package/dist/scaffold/merge.js.map +1 -0
  151. package/dist/scaffold/types.d.ts +51 -0
  152. package/dist/scaffold/types.d.ts.map +1 -0
  153. package/dist/scaffold/types.js +10 -0
  154. package/dist/scaffold/types.js.map +1 -0
  155. package/dist/sync/index.d.ts +34 -0
  156. package/dist/sync/index.d.ts.map +1 -0
  157. package/dist/sync/index.js +392 -0
  158. package/dist/sync/index.js.map +1 -0
  159. package/dist/visualizer/index.d.ts +17 -0
  160. package/dist/visualizer/index.d.ts.map +1 -0
  161. package/dist/visualizer/index.js +1976 -0
  162. package/dist/visualizer/index.js.map +1 -0
  163. package/examples/code-review.at +167 -0
  164. package/examples/data-processing.at +296 -0
  165. package/examples/openclaw-assistant.at +142 -0
  166. package/examples/scheduled-monitor.at +193 -0
  167. package/examples/simple-pipeline.at +163 -0
  168. package/package.json +77 -0
  169. package/skill/SKILL.md +403 -0
  170. package/skill/scripts/emit.ts +853 -0
  171. package/skill/scripts/visualize.ts +89 -0
  172. package/spec/grammar.md +1466 -0
  173. package/spec/reserved-keywords.md +147 -0
  174. package/spec/validation.md +535 -0
@@ -0,0 +1,1466 @@
1
+ # AgenTopology Language Specification v1.0
2
+
3
+ Created by Nadav Naveh
4
+
5
+ **Extension:** `.at`
6
+ **Language:** AgenTopology
7
+
8
+ ---
9
+
10
+ ## 1. Principles
11
+
12
+ 1. **Single-valued** -- Every construct has exactly one interpretation. No ambiguity.
13
+ 2. **Single source of truth** -- The `.at` file defines the structure. Files are generated from it.
14
+ 3. **Readable** -- A non-engineer can understand the topology by reading it.
15
+ 4. **Parseable** -- LL(2) grammar. A compiler can extract the full graph deterministically in O(n) time with no backtracking.
16
+
17
+ ---
18
+
19
+ ## 2. Lexical Rules
20
+
21
+ ### Comments
22
+ ```
23
+ # single-line comment
24
+ ```
25
+
26
+ Everything after `#` until end-of-line is ignored.
27
+
28
+ ### Primitive Types
29
+
30
+ ```ebnf
31
+ string = '"' <any-char-except-quote>* '"'
32
+ number = [0-9]+ ('.' [0-9]+)?
33
+ boolean = 'true' | 'false'
34
+ identifier = [a-z] [a-z0-9-]*
35
+ template-var = [A-Z] [A-Z0-9_-]*
36
+ ```
37
+
38
+ - `string` -- always quoted with double quotes
39
+ - `number` -- integer (3, 42) or decimal (4.5, 6.5). Numbers establish ordering: 4.5 is between 4 and 5.
40
+ - `boolean` -- `true` or `false`, unquoted
41
+ - `identifier` -- lowercase, hyphens allowed, starts with letter
42
+ - `template-var` -- uppercase, underscores/hyphens allowed, starts with letter. Used for `argument` in triggers.
43
+
44
+ **Rule:** Quote strings. Don't quote identifiers, numbers, or booleans.
45
+
46
+ ### Composite Types
47
+
48
+ ```ebnf
49
+ name-list = '[' identifier (',' identifier)* ']' | '[]'
50
+ string-list = '[' string (',' string)* ']' | '[]'
51
+ id-list = name-list # alias
52
+ path-list = '[' (string | 'ticket') (',' (string | 'ticket'))* ']' | '[]'
53
+ tool-list = '[' tool-item (',' tool-item)* ']' | '[]'
54
+ tool-item = identifier # Read, Write, Bash, Agent
55
+ | identifier '(' string ')' # Bash("pattern")
56
+ | 'mcp.' identifier '.*' # mcp.gitnexus.*
57
+ | 'mcp.' identifier '.' identifier # mcp.render.list_logs
58
+ typed-map = '{' (identifier ':' enum-values)+ '}'
59
+ enum-values = identifier ('|' identifier)*
60
+ skip-expr = 'not' identifier # tag negation
61
+ | identifier '.' identifier op value # condition
62
+ op = '==' | '!=' | '>=' | '<=' | '>' | '<'
63
+ value = number | identifier
64
+ import-path = './' [a-zA-Z0-9._/-]+ '.at'
65
+ ```
66
+
67
+ `ticket` in a `path-list` is a reserved keyword referring to the input ticket data. It appears unquoted.
68
+
69
+ ### Template Variables
70
+ ```
71
+ {IDENTIFIER} # uppercase letters, underscores, hyphens
72
+ ```
73
+
74
+ Template variables appear inside quoted strings and are expanded at runtime. Built-in:
75
+ - `{TICKET}` -- ticket identifier from trigger command
76
+ - `{BATCH_ID}` -- auto-generated batch run identifier
77
+
78
+ Custom variables are declared via `triggers.command.argument`.
79
+
80
+ ### Blocks
81
+
82
+ ```ebnf
83
+ named-block = keyword identifier '{' field* '}'
84
+ anonymous-block = keyword '{' field* '}'
85
+ sub-block = keyword identifier '{' field* '}' # gate, command, level
86
+ nested-object = identifier ':' '{' field* '}' # outputs, conflicts
87
+ field = identifier ':' value
88
+ ```
89
+
90
+ - **Named blocks:** `agent explorer { ... }`, `action intake { ... }`
91
+ - **Anonymous blocks:** `meta { ... }`, `flow { ... }`, `orchestrator { ... }`
92
+ - **Sub-blocks:** `gate validation { ... }`, `command ship { ... }`, `level 1 "label" { ... }`
93
+ - **Nested objects:** `outputs: { depth: 1 | 2 | 3 }`, `conflicts: { detect: [...] }`
94
+
95
+ Curly braces delimit all blocks. Indentation is cosmetic.
96
+
97
+ ### Lists
98
+ ```
99
+ tools: [Read, Write, Edit, Bash]
100
+ ```
101
+
102
+ Square brackets, comma-separated. `[]` is a valid empty list meaning "none."
103
+
104
+ An explicit empty list (e.g., `tools: []`) means "no items" -- distinct from omitting the field entirely (which uses the default).
105
+
106
+ ### Typed Enums (for outputs)
107
+ ```
108
+ verdict: pass | fail | plan-gap
109
+ ```
110
+
111
+ Pipe-separated identifiers after a field name.
112
+
113
+ ---
114
+
115
+ ## 3. File Types
116
+
117
+ Every `.at` file has exactly one root keyword:
118
+
119
+ | Root | Purpose | Example |
120
+ |------|---------|---------|
121
+ | `topology` | A complete agentic structure | `pipeline.at` |
122
+ | `library` | Reusable agent definitions | `shared-agents.at` |
123
+
124
+ ### 3.1 Path Resolution
125
+
126
+ All string paths in `.at` files are **relative to the output directory** scaffolded from the topology. The exact output structure depends on the binding (see Section 11: Bindings).
127
+
128
+ | `.at` field | Example value | Resolves to |
129
+ |-------------|---------------|-------------|
130
+ | `tools.tool.script` | `"scripts/ocr-pipeline.sh"` | `<output-dir>/scripts/ocr-pipeline.sh` |
131
+ | `gates.gate.run` | `"scripts/validate.sh"` | `<output-dir>/scripts/validate.sh` |
132
+ | `hooks.hook.run` | `"scripts/emit-event.sh"` | `<output-dir>/scripts/emit-event.sh` |
133
+ | `agent.prompt` | `prompt { ... }` | Inline content (not a file path) |
134
+ | `agent.reads/writes` | `"workspace/raw/"` | Runtime workspace path (not output-relative) |
135
+ | `memory.domains.path` | `"domains/"` | `<output-dir>/domains/` |
136
+
137
+ **Why output-relative:** Each topology scaffolds into a self-contained directory. Scripts, prompts, references, and domains belong to the topology that owns them. This avoids path collisions when multiple topologies coexist in the same project.
138
+
139
+ **Exception:** `reads`/`writes` paths are runtime workspace paths, resolved by the orchestrator at execution time (typically relative to the working directory, not the output directory). The shared workspace is accessible to all agents.
140
+
141
+ ---
142
+
143
+ ## 4. Grammar
144
+
145
+ ### 4.1 `topology`
146
+
147
+ ```ebnf
148
+ file = import* topology | import* library
149
+ topology = 'topology' identifier ':' '[' pattern-list ']' '{' topology-body '}'
150
+ pattern-list = identifier (',' identifier)*
151
+ topology-body = topology-section*
152
+ topology-section = meta | orchestrator | roles | action | agent | skill-decl | use-stmt
153
+ | memory | flow | gates | depth | batch | environments | triggers
154
+ | hooks | settings | mcp-servers | metering | tools | context | env
155
+ | schedule | interfaces
156
+ ```
157
+
158
+ **Ordering:** Sections may appear in any order. Each of `meta`, `orchestrator`, `roles`, `memory`, `flow`, `gates`, `depth`, `batch`, `environments`, `triggers`, `hooks`, `settings`, `mcp-servers`, `metering`, `tools`, `schedule`, and `interfaces` may appear **at most once** (parser error on duplicates). `action`, `agent`, `skill-decl`, and `use-stmt` may appear multiple times.
159
+
160
+ Sub-block productions used within topology-body:
161
+
162
+ ```ebnf
163
+ action = 'action' identifier '{' action-fields '}'
164
+ action-fields = ('kind' ':' action-kind | 'source' ':' string
165
+ | 'commands' ':' name-list | 'description' ':' string)*
166
+ action-kind = 'external' | 'git' | 'decision' | 'inline' | 'report'
167
+ agent = 'agent' identifier '{' agent-fields '}'
168
+ use-stmt = 'use' identifier '{' agent-fields '}'
169
+ gate-decl = 'gate' identifier '{' gate-fields '}'
170
+ gate-fields = ('after' ':' identifier | 'before' ':' identifier | 'run' ':' string
171
+ | 'checks' ':' name-list | 'retry' ':' number
172
+ | 'on-fail' ':' ('bounce-back' | 'halt')
173
+ | 'behavior' ':' ('advisory' | 'blocking'))*
174
+ command-decl = 'command' identifier '{' command-fields '}'
175
+ level-decl = 'level' number string '{' 'omit' ':' name-list '}'
176
+ env-decl = identifier '{' (identifier ':' (identifier | string))* '}'
177
+ model-id = model-identifier | 'inherit'
178
+ perm-enum = perm-identifier
179
+ ```
180
+
181
+ **Model identifiers:** A model identifier is any string matching `[a-z][a-z0-9-/.]*`. This allows short aliases (`opus`, `sonnet`, `haiku`) as well as full model strings (`gpt-4o`, `gemini-pro`, `claude-sonnet-4-20250514`, `llama-3.1-70b`). The special value `inherit` means the agent inherits the model from its orchestrator or parent context.
182
+
183
+ > **Examples:** `opus`, `sonnet`, `haiku`, `gpt-4o`, `gemini-2.0-flash`, `claude-sonnet-4-20250514`, `llama-3.1-70b`, `mistral-large`, `inherit`
184
+
185
+ **Permission identifiers:** Permissions use an extensible set of universal concepts:
186
+
187
+ | Permission | Meaning |
188
+ |------------|---------|
189
+ | `autonomous` | Agent can read and write without confirmation |
190
+ | `supervised` | Agent can read freely but proposes writes for approval |
191
+ | `interactive` | Agent requires confirmation for all actions |
192
+ | `unrestricted` | Agent bypasses all permission checks |
193
+
194
+ > **Note for binding authors:** Bindings may alias these to platform-specific terms. For example, a Claude Code binding maps `autonomous` to `auto` and `supervised` to `plan`. Short aliases (`auto`, `plan`, `confirm`, `bypass`) are also accepted by the parser for backward compatibility.
195
+
196
+ **Patterns** -- composable list from the topology catalog:
197
+
198
+ | Pattern | What it adds |
199
+ |---------|-------------|
200
+ | `pipeline` | Sequential phases |
201
+ | `supervisor` | Central control, isolated workers |
202
+ | `blackboard` | Shared state |
203
+ | `orchestrator-worker` | Dynamic task discovery |
204
+ | `debate` | Peer challenge |
205
+ | `market-routing` | Auction scoring |
206
+ | `consensus` | Quorum voting |
207
+ | `fan-out` | Parallel slices |
208
+ | `event-driven` | Pub-sub |
209
+ | `human-gate` | Human approval points |
210
+
211
+ ---
212
+
213
+ ### 4.2 `library`
214
+
215
+ ```ebnf
216
+ library = 'library' identifier '{' agent* '}'
217
+ ```
218
+
219
+ A library is a collection of reusable agents. No flow, no orchestrator, no triggers.
220
+
221
+ ---
222
+
223
+ ### 4.3 `import` and `use`
224
+
225
+ ```ebnf
226
+ import = 'import' identifier 'from' import-path
227
+ ```
228
+
229
+ ```agenttopology
230
+ import meta-reviewer from ./shared-agents.at
231
+
232
+ topology my-team : [pipeline] {
233
+ use meta-reviewer {} # as-is, no overrides
234
+ use meta-reviewer { phase: 7 } # with overrides
235
+ }
236
+ ```
237
+
238
+ `import` loads a definition. `use` places it into the topology. Braces always required on `use`.
239
+
240
+ **Import path:** Unquoted, starts with `./`, ends with `.at`. Matches `import-path` production in Section 2.
241
+
242
+ **Merge semantics:** `use` applies REPLACE on specified fields. Omitted fields KEEP their original values. Shallow merge -- nested objects (like `outputs`) and lists (like `reads`) are replaced entirely if specified, not appended.
243
+
244
+ ---
245
+
246
+ ### 4.4 `meta`
247
+
248
+ ```agenttopology
249
+ meta {
250
+ version: "1.3.0"
251
+ description: "Autonomous development pipeline"
252
+ }
253
+ ```
254
+
255
+ | Field | Type | Required | Default |
256
+ |-------|------|----------|---------|
257
+ | `version` | string | yes | -- |
258
+ | `description` | string | yes | -- |
259
+ | `domain` | identifier | no | -- |
260
+
261
+ **`domain`** -- Optional domain identifier (e.g., `legal`, `marketing`, `engineering`). Used by bindings and tooling for categorization and routing.
262
+
263
+ ---
264
+
265
+ ### 4.5 `orchestrator`
266
+
267
+ The coordinator that runs the flow. Handles actions inline, spawns agents.
268
+
269
+ ```agenttopology
270
+ orchestrator {
271
+ model: opus
272
+ generates: "commands/ship.md"
273
+ handles: [intake, classify, context]
274
+ outputs: {
275
+ depth: 1 | 2 | 3
276
+ }
277
+ }
278
+ ```
279
+
280
+ | Field | Type | Required | Default |
281
+ |-------|------|----------|---------|
282
+ | `model` | model-id | yes | -- |
283
+ | `generates` | string | no | -- |
284
+ | `handles` | name-list | yes | -- |
285
+ | `outputs` | typed-map | no | `{}` |
286
+
287
+ **Orchestrator outputs** are referenced in flow as `orchestrator.<output>`.
288
+
289
+ ---
290
+
291
+ ### 4.6 `agent`
292
+
293
+ ```ebnf
294
+ agent = 'agent' identifier '{' agent-fields '}'
295
+ agent-fields = agent-field*
296
+ agent-field = 'role' ':' identifier
297
+ | 'model' ':' model-id
298
+ | 'permissions' ':' perm-enum
299
+ | 'prompt' '{' raw-text '}'
300
+ | 'phase' ':' number
301
+ | 'tools' ':' tool-list
302
+ | 'disallowed-tools' ':' tool-list
303
+ | 'reads' ':' path-list
304
+ | 'writes' ':' path-list
305
+ | 'outputs' ':' typed-map
306
+ | 'skip' ':' skip-expr
307
+ | 'retry' ':' number
308
+ | 'isolation' ':' 'worktree'
309
+ | 'invocation' ':' ('manual' | 'auto')
310
+ | 'behavior' ':' ('advisory' | 'blocking')
311
+ # NOTE: agent.memory is intentionally deferred. Per-agent memory scope
312
+ # is covered by reads/writes (data flow) and the top-level memory {}
313
+ # block (workspace structure). A dedicated agent.memory field would be
314
+ # redundant. Retained in the grammar for future consideration.
315
+ | 'memory' ':' ('user' | 'project' | 'local')
316
+ | 'skills' ':' name-list
317
+ | 'mcp-servers' ':' name-list
318
+ | 'background' ':' boolean
319
+ | 'description' ':' string
320
+ | 'max-turns' ':' number
321
+ | 'sandbox' ':' (boolean | identifier)
322
+ | 'fallback-chain' ':' name-list
323
+ | agent-extensions
324
+ | agent-hooks
325
+ | agent-scale
326
+ agent-extensions = 'extensions' '{' binding-ext-block* '}'
327
+ binding-ext-block = identifier '{' (identifier ':' value)* '}'
328
+ agent-hooks = 'hooks' '{' hook-decl* '}'
329
+ agent-scale = 'scale' '{' scale-fields '}'
330
+ scale-fields = scale-field*
331
+ scale-field = 'mode' ':' ('auto' | 'fixed' | 'config')
332
+ | 'by' ':' ('batch-count' | 'doc-count' | 'token-volume' | 'source-count')
333
+ | 'min' ':' number
334
+ | 'max' ':' number
335
+ | 'batch-size' ':' number
336
+ ```
337
+
338
+ | Field | Type | Required | Default | Description |
339
+ |-------|------|----------|---------|-------------|
340
+ | `role` | identifier | no | -- | Maps to a role defined in the `roles` block |
341
+ | `model` | model-id | yes | -- | LLM model identifier (e.g., `opus`, `gpt-4o`, `gemini-pro`) |
342
+ | `permissions` | perm-enum | no | `autonomous` | Permission mode |
343
+ | `prompt` | block | no | -- | Inline agent instructions (multi-line text block) |
344
+ | `phase` | number | no | -- | Pipeline position (ordering by numeric value) |
345
+ | `tools` | tool-list | no | all | Tool allowlist |
346
+ | `disallowed-tools` | tool-list | no | `[]` | Explicit deny-list |
347
+ | `reads` | path-list | no | `[]` | Input artifacts |
348
+ | `writes` | path-list | no | `[]` | Output artifacts |
349
+ | `outputs` | typed-map | no | `{}` | Values that affect flow |
350
+ | `skip` | skip-expr | no | -- | Skip condition |
351
+ | `retry` | number | no | `0` | Max validation rounds |
352
+ | `isolation` | `worktree` | no | -- | Git worktree isolation |
353
+ | `invocation` | `manual` / `auto` | no | `auto` | User-triggered only |
354
+ | `behavior` | `advisory` / `blocking` | no | `blocking` | Advisory never blocks flow |
355
+ | `memory` | `user` / `project` / `local` | no | -- | Persistent memory scope (intentionally deferred — use reads/writes and top-level memory {} instead) |
356
+ | `skills` | name-list | no | `[]` | Skills to preload |
357
+ | `mcp-servers` | name-list | no | `[]` | MCP servers available |
358
+ | `background` | boolean | no | `false` | Run in background |
359
+ | `description` | string | no | -- | Human-readable purpose of this agent |
360
+ | `max-turns` | number | no | -- | Maximum agentic turns before stopping |
361
+ | `scale` | agent-scale | no | -- | Dynamic scaling configuration (see below) |
362
+ | `hooks` | agent-hooks | no | -- | Per-agent hooks (scoped to agent lifetime) |
363
+ | `sandbox` | string \| boolean | no | -- | Execution sandbox (true, false, docker, none, network-only) |
364
+ | `fallback-chain` | name-list | no | `[]` | Ordered model fallback list |
365
+ | `extensions` | agent-extensions | no | -- | Platform-specific extension fields (see below) |
366
+
367
+ **Per-agent `hooks`:** Hooks defined inside an agent block are scoped to that agent's lifetime. They only fire while the agent is active. The hook sub-block syntax is identical to the global `hooks` section -- same fields (`on`, `matcher`, `run`, `type`, `timeout`), same hook events.
368
+
369
+ **`tools` vs `disallowed-tools`:** Mutually exclusive. Parser error if both are specified on the same agent. Use `tools` to allowlist (only these). Use `disallowed-tools` to denylist (all except these).
370
+
371
+ **Tool list deduplication:** If both an unrestricted tool and a constrained variant appear (e.g., `Bash` and `Bash("pattern")`), the unrestricted form subsumes the constrained. The constrained entry is ignored. No parser error.
372
+
373
+ **Skip expression disambiguation:** The parser checks the first token after `skip:`. If it is `not`, parse as tag-negation (`not <tag>`). Otherwise, parse as condition (`<source>.<output> <op> <value>`). These are lexically unambiguous -- `not` is a keyword, conditions always contain a dot.
374
+
375
+ **When an agent is skipped:** The agent is treated as instantly completed with no outputs. Flow continues to the next node(s) via unconditional outgoing edges. Conditional outgoing edges that reference the skipped agent's outputs are **not taken** (outputs are undefined). This is a validation-time check: if skipping an agent would leave the flow with no viable path, it is a validation error.
376
+
377
+ **Outputs** are referenced in flow as `<agent-name>.<output-name>`.
378
+
379
+ **Agent `scale`:** Dynamic scaling allows an agent definition to spawn multiple instances at runtime based on workload. Define one agent with a `scale` block and the orchestrator spawns the right number of instances.
380
+
381
+ ```agenttopology
382
+ agent classifier {
383
+ role: analyst
384
+ model: sonnet
385
+ phase: 3
386
+ background: true
387
+ scale {
388
+ mode: auto
389
+ by: batch-count
390
+ min: 2
391
+ max: 12
392
+ batch-size: 25
393
+ }
394
+ }
395
+ ```
396
+
397
+ | Scale Field | Type | Required | Default | Description |
398
+ |-------------|------|----------|---------|-------------|
399
+ | `mode` | `auto` / `fixed` / `config` | no | `fixed` | How instance count is determined |
400
+ | `by` | `batch-count` / `doc-count` / `token-volume` / `source-count` | no | `batch-count` | Metric that drives scaling |
401
+ | `min` | number | no | `1` | Minimum instances |
402
+ | `max` | number | no | `6` | Maximum instances |
403
+ | `batch-size` | number | no | `25` | Items per batch (for batch-count mode) |
404
+
405
+ **Scale modes:**
406
+ - `fixed` -- always spawn `min` instances. Equivalent to omitting `scale` entirely.
407
+ - `auto` -- orchestrator counts the workload (e.g., total docs / batch-size = batch count), then spawns `min(max, max(min, batch-count))` instances.
408
+ - `config` -- read instance count from a configuration file. Useful when the user wants manual control.
409
+
410
+ **Scale in flow:** When an agent has `scale`, its flow edges apply to ALL instances. `deduplicator -> classifier` means "deduplicator feeds all classifier instances." `classifier -> synthesizer` means "all classifier instances feed synthesizer." The orchestrator handles fan-out distribution and merge.
411
+
412
+ **Backward compatibility:** An agent without `scale` behaves as `scale { mode: fixed min: 1 max: 1 }` -- exactly one instance, identical to current behavior.
413
+
414
+ ---
415
+
416
+ ### 4.7 `action`
417
+
418
+ Orchestrator-handled steps (not agent spawns).
419
+
420
+ ```agenttopology
421
+ action <name> {
422
+ kind: <action-kind>
423
+ source: "<source>"
424
+ commands: [<cmd>, ...]
425
+ description: "<text>"
426
+ }
427
+ ```
428
+
429
+ | Field | Type | Required | Default |
430
+ |-------|------|----------|---------|
431
+ | `kind` | action-kind | yes | -- |
432
+ | `source` | string | no | -- |
433
+ | `commands` | name-list | no | `[]` |
434
+ | `description` | string | no | -- |
435
+
436
+ **action-kind:** `external` | `git` | `decision` | `inline` | `report`
437
+
438
+ Actions **cannot have outputs**. Only `agent` and `orchestrator` produce outputs.
439
+
440
+ ---
441
+
442
+ ### 4.8 `roles`
443
+
444
+ ```agenttopology
445
+ roles {
446
+ explorer: "Deep codebase analysis and blast radius mapping"
447
+ builder: "Implement code changes following the plan"
448
+ }
449
+ ```
450
+
451
+ Maps role identifiers to human-readable descriptions. Agents reference roles with the `role` field.
452
+
453
+ ---
454
+
455
+ ### 4.9 `memory`
456
+
457
+ ```ebnf
458
+ memory = 'memory' '{' memory-sub-block* '}'
459
+ memory-sub-block = identifier '{' (identifier ':' (string | identifier | name-list))* '}'
460
+ ```
461
+
462
+ ```agenttopology
463
+ memory {
464
+ domains {
465
+ path: "domains/"
466
+ routing: "FILE_MAP.md"
467
+ index: "INDEX.json"
468
+ }
469
+
470
+ references {
471
+ path: "references/"
472
+ blueprints: [bugfix, feature, refactor]
473
+ }
474
+
475
+ external-docs {
476
+ path: "docs/"
477
+ files: ["agent-sdk.md", "composio-sdk.md"]
478
+ load-when: "FILE_MAP.md"
479
+ }
480
+
481
+ metrics {
482
+ path: "metrics.jsonl"
483
+ mode: append-only
484
+ }
485
+
486
+ workspace {
487
+ path: "runs/{TICKET}/"
488
+ protocol: "workspace-protocol.md"
489
+ structure: [explore, plan, build, qa]
490
+ }
491
+ }
492
+ ```
493
+
494
+ | Sub-block | Fields | Description |
495
+ |-----------|--------|-------------|
496
+ | `domains` | `path` (string), `routing` (string), `index` (string, optional) | Shared knowledge base |
497
+ | `references` | `path` (string), `blueprints` (name-list) | Static documentation |
498
+ | `external-docs` | `path` (string), `files` (string-list), `load-when` (string) | Conditional documentation |
499
+ | `metrics` | `path` (string), `mode` (identifier: `append-only`) | Execution log |
500
+ | `workspace` | `path` (string), `protocol` (string), `structure` (name-list) | Per-run working directory |
501
+
502
+ Memory sub-blocks are named blocks with known field schemas. Unknown sub-block names are parser errors.
503
+
504
+ ---
505
+
506
+ ### 4.10 `flow`
507
+
508
+ ```ebnf
509
+ flow = 'flow' '{' flow-statement* '}'
510
+ flow-statement = node ('->' node)+ edge-attrs?
511
+ node = identifier | '[' identifier (',' identifier)+ ']'
512
+ edge-attrs = '[' attr (',' attr)* ']'
513
+ attr = 'when' condition | 'max' number | 'per' identifier
514
+ condition = identifier '.' identifier op value
515
+ ```
516
+
517
+ ```agenttopology
518
+ flow {
519
+ intake -> classify
520
+ classify -> researcher [when orchestrator.depth >= 2]
521
+ classify -> context [when orchestrator.depth == 1]
522
+ researcher -> builder
523
+ context -> builder
524
+ builder -> reviewer
525
+ reviewer -> builder [when reviewer.verdict == revise, max 2]
526
+ reviewer -> done [when reviewer.verdict == approve]
527
+ reviewer -> researcher [when reviewer.verdict == reject, max 1]
528
+ }
529
+ ```
530
+
531
+ **Operators:**
532
+
533
+ | Syntax | Meaning |
534
+ |--------|---------|
535
+ | `a -> b` | Sequential: a completes, then b starts |
536
+ | `a -> b -> c` | Chained: syntactic sugar for `a -> b` + `b -> c` |
537
+ | `a -> [b, c, d]` | Fan-out: a completes, then b, c, d run in parallel |
538
+ | `a -> [b, c] [when x]` | Conditional fan-out: condition applies to the fan-out as a whole |
539
+ | `[when x.y == z]` | Conditional: edge taken only when condition is true |
540
+ | `[max N]` | Bounded loop: back-edge limited to N iterations |
541
+ | `[per ticket]` | One instance per unit (currently only `ticket` is valid) |
542
+
543
+ **Chained flows:** `a -> b -> c [when x]` desugars to `a -> b` + `b -> c [when x]`. Edge attributes apply to the **last edge only**.
544
+
545
+ **Edge attribute order:** `[when <condition>, max <N>, per <unit>]`. Parser error on wrong order.
546
+
547
+ **Condition format:** `<source>.<output> <op> <value>`
548
+ - Source must be an agent name or `orchestrator`
549
+ - Output must be declared in that source's `outputs` block
550
+ - Operators: `==` `!=` `>=` `<=` `>` `<`
551
+
552
+ **Exhaustive conditions:** When a node has ONLY conditional outgoing edges, the conditions must be exhaustive over the output's enum values that are **reachable** at that node. If no condition matches at runtime, it is a **runtime error** (flow halts). Validation rule 15 checks for exhaustiveness at parse time, accounting for upstream routing that eliminates certain values.
553
+
554
+ **Unconditional edges:** An edge without `[when]` is always taken. If a node has both unconditional and conditional edges, the unconditional edge is always taken regardless of conditions.
555
+
556
+ **Flow nodes** must resolve to one of:
557
+ - An `agent` name
558
+ - An `action` name
559
+ - A `gate` name (inside `gates` block)
560
+
561
+ ---
562
+
563
+ ### 4.11 `gates`
564
+
565
+ ```ebnf
566
+ gates = 'gates' '{' gate-decl* '}'
567
+ gate-decl = 'gate' identifier '{' gate-fields '}'
568
+ ```
569
+
570
+ Gates are auto-injected checkpoints. A gate with `after: X` and `before: Y` is inserted between X and Y in the flow graph. The edge `X -> Y` becomes `X -> gate -> Y`.
571
+
572
+ If `before` is omitted, the gate runs after `after` completes as a side-effect -- not a flow graph node, but a triggered hook.
573
+
574
+ Gates execute in declaration order when multiple gates share the same `after`/`before` pair.
575
+
576
+ ```agenttopology
577
+ gates {
578
+ gate validation {
579
+ after: builder
580
+ before: deployer
581
+ run: "scripts/validate.sh"
582
+ checks: [typecheck, lint, test, build]
583
+ retry: 3
584
+ on-fail: bounce-back
585
+ }
586
+
587
+ gate security-scan {
588
+ after: builder
589
+ run: "scripts/security-scan.sh"
590
+ behavior: advisory
591
+ }
592
+ }
593
+ ```
594
+
595
+ | Field | Type | Required | Default | Description |
596
+ |-------|------|----------|---------|-------------|
597
+ | `after` | identifier | yes | -- | Predecessor node |
598
+ | `before` | identifier | no | -- | Successor node (omit for side-effect) |
599
+ | `run` | string | yes | -- | Script to execute |
600
+ | `checks` | name-list | no | `[]` | Named check items |
601
+ | `retry` | number | no | `0` | Max retry attempts |
602
+ | `on-fail` | `bounce-back` / `halt` | no | `halt` | Failure behavior |
603
+ | `behavior` | `advisory` / `blocking` | no | `blocking` | Advisory gates never block |
604
+
605
+ ---
606
+
607
+ ### 4.12 `depth`
608
+
609
+ ```ebnf
610
+ depth = 'depth' '{' 'factors' ':' name-list level-decl+ '}'
611
+ level-decl = 'level' number string '{' 'omit' ':' name-list '}'
612
+ ```
613
+
614
+ ```agenttopology
615
+ depth {
616
+ factors: [file-count, domain-count, complexity]
617
+
618
+ level 1 "Simple" {
619
+ omit: [researcher, planner]
620
+ }
621
+
622
+ level 2 "Medium" {
623
+ omit: [planner]
624
+ }
625
+
626
+ level 3 "Complex" {
627
+ omit: []
628
+ }
629
+ }
630
+ ```
631
+
632
+ | Field | Type | Required | Default |
633
+ |-------|------|----------|---------|
634
+ | `factors` | name-list | yes | -- |
635
+ | `level` | sub-block: `level <number> <string> { omit: <name-list> }` | 1+ required | -- |
636
+
637
+ `level` sub-block syntax: `level <number> <string> { omit: <name-list> }`. The number is the depth level. The string is a human label. `omit` lists agents to skip at this level.
638
+
639
+ ---
640
+
641
+ ### 4.13 `batch`
642
+
643
+ ```agenttopology
644
+ batch {
645
+ parallel: true
646
+ per: ticket
647
+ conflicts: {
648
+ detect: ["metrics.jsonl", "INDEX.md"]
649
+ resolve: sequential-rebase
650
+ }
651
+ workspace: "runs/_batch/{BATCH_ID}/"
652
+ }
653
+ ```
654
+
655
+ | Field | Type | Required | Default |
656
+ |-------|------|----------|---------|
657
+ | `parallel` | boolean | no | `false` |
658
+ | `per` | identifier | yes | -- |
659
+ | `conflicts` | nested-object | no | -- |
660
+ | `conflicts.detect` | string-list | no | `[]` |
661
+ | `conflicts.resolve` | `sequential-rebase` | no | -- |
662
+ | `workspace` | string | no | -- |
663
+
664
+ ---
665
+
666
+ ### 4.14 `environments`
667
+
668
+ ```ebnf
669
+ environments = 'environments' '{' env-decl* '}'
670
+ env-decl = identifier '{' (identifier ':' (identifier | string))* '}'
671
+ ```
672
+
673
+ ```agenttopology
674
+ environments {
675
+ staging {
676
+ target-branch: develop
677
+ url: "staging.example.com"
678
+ }
679
+
680
+ production {
681
+ target-branch: main
682
+ url: "app.example.com"
683
+ }
684
+ }
685
+ ```
686
+
687
+ Environment blocks have no fixed schema -- fields are identifier/string key-value pairs. The environment name is the identifier.
688
+
689
+ ---
690
+
691
+ ### 4.15 `triggers`
692
+
693
+ ```ebnf
694
+ triggers = 'triggers' '{' command-decl* '}'
695
+ command-decl = 'command' identifier '{' command-fields '}'
696
+ command-fields = 'pattern' ':' string ('argument' ':' template-var)?
697
+ ```
698
+
699
+ ```agenttopology
700
+ triggers {
701
+ command process {
702
+ pattern: "/process <TICKET>"
703
+ argument: TICKET
704
+ }
705
+
706
+ command deploy {
707
+ pattern: "/deploy"
708
+ }
709
+ }
710
+ ```
711
+
712
+ | Field | Type | Required | Default |
713
+ |-------|------|----------|---------|
714
+ | `pattern` | string | yes | -- |
715
+ | `argument` | template-var | no | -- |
716
+
717
+ ---
718
+
719
+ ### 4.16 `hooks`
720
+
721
+ ```ebnf
722
+ hooks = 'hooks' '{' hook-decl* '}'
723
+ hook-decl = 'hook' identifier '{' hook-fields '}'
724
+ hook-fields = ('on' ':' hook-event | 'matcher' ':' string | 'run' ':' string
725
+ | 'type' ':' hook-type | 'timeout' ':' number)*
726
+ hook-event = <universal-event> | <platform-event>
727
+ hook-type = 'command' | 'prompt'
728
+ ```
729
+
730
+ **Universal hook events** -- these are defined by the spec and must be supported by all bindings:
731
+
732
+ | Event | When it fires |
733
+ |-------|---------------|
734
+ | `AgentStart` | When an agent spawns |
735
+ | `AgentStop` | When an agent finishes |
736
+ | `ToolUse` | Before or after a tool runs |
737
+ | `Error` | When an error occurs |
738
+ | `SessionStart` | When a session begins |
739
+ | `SessionEnd` | When a session ends |
740
+
741
+ **Platform-specific events** -- bindings may define additional events. For example:
742
+
743
+ | Event | Platform | Description |
744
+ |-------|----------|-------------|
745
+ | `PreToolUse` | Claude Code | Before a tool runs (maps to universal `ToolUse`) |
746
+ | `PostToolUse` | Claude Code | After a tool runs |
747
+ | `SubagentStart` | Claude Code | When an agent spawns (maps to universal `AgentStart`) |
748
+ | `SubagentStop` | Claude Code | When an agent finishes (maps to universal `AgentStop`) |
749
+ | `Stop` | Claude Code | When the session ends |
750
+ | `UserPromptSubmit` | Claude Code | When user submits a prompt |
751
+ | `PreCompact` | Claude Code | Before context compaction |
752
+
753
+ > **Note for binding authors:** When implementing a binding, map universal events to your platform's event system. You may also accept platform-specific event names directly for convenience. The parser accepts any identifier in PascalCase as a hook event name.
754
+
755
+ ```agenttopology
756
+ hooks {
757
+ hook security-check {
758
+ on: ToolUse
759
+ matcher: "Bash"
760
+ run: "scripts/security-check.sh"
761
+ type: command
762
+ }
763
+
764
+ hook log-agents {
765
+ on: AgentStop
766
+ run: "scripts/log-agent.sh"
767
+ type: command
768
+ }
769
+ }
770
+ ```
771
+
772
+ | Field | Type | Required | Default |
773
+ |-------|------|----------|---------|
774
+ | `on` | hook-event | yes | -- |
775
+ | `matcher` | string | no | -- |
776
+ | `run` | string | yes | -- |
777
+ | `type` | hook-type | no | `command` |
778
+ | `timeout` | number | no | `600` |
779
+
780
+ ---
781
+
782
+ ### 4.17 `settings`
783
+
784
+ ```ebnf
785
+ settings = 'settings' '{' settings-field* '}'
786
+ settings-field = 'allow' ':' string-list
787
+ | 'deny' ':' string-list
788
+ | 'ask' ':' string-list
789
+ ```
790
+
791
+ ```agenttopology
792
+ settings {
793
+ allow: ["Bash(npm run *)", "Read", "Grep", "Glob"]
794
+ deny: ["Bash(rm -rf *)", "Bash(git push --force)"]
795
+ ask: ["Bash(git push)"]
796
+ }
797
+ ```
798
+
799
+ | Field | Type | Required | Default |
800
+ |-------|------|----------|---------|
801
+ | `allow` | string-list | no | `[]` |
802
+ | `deny` | string-list | no | `[]` |
803
+ | `ask` | string-list | no | `[]` |
804
+
805
+ ---
806
+
807
+ ### 4.18 `mcp-servers`
808
+
809
+ ```ebnf
810
+ mcp-servers = 'mcp-servers' '{' mcp-decl* '}'
811
+ mcp-decl = identifier '{' mcp-fields '}'
812
+ mcp-fields = ('type' ':' mcp-type | 'url' ':' string
813
+ | 'command' ':' string | 'args' ':' string-list
814
+ | 'env' ':' env-map)*
815
+ mcp-type = 'http' | 'stdio' | 'sse'
816
+ env-map = '{' (identifier ':' string)* '}'
817
+ ```
818
+
819
+ ```agenttopology
820
+ mcp-servers {
821
+ database {
822
+ type: stdio
823
+ command: "npx"
824
+ args: ["-y", "database-mcp"]
825
+ }
826
+
827
+ monitoring {
828
+ type: http
829
+ url: "https://mcp.monitoring.example.com"
830
+ }
831
+ }
832
+ ```
833
+
834
+ | Field | Type | Required | Default |
835
+ |-------|------|----------|---------|
836
+ | `type` | mcp-type | yes | -- |
837
+ | `url` | string | no | -- |
838
+ | `command` | string | no | -- |
839
+ | `args` | string-list | no | `[]` |
840
+ | `env` | env-map | no | `{}` |
841
+
842
+ `type: http` requires `url`. `type: stdio` requires `command`. Parser error if the required companion field is missing.
843
+
844
+ ---
845
+
846
+ ### 4.19 `metering`
847
+
848
+ Per-phase cost and performance tracking. Produces a breakdown file after each run that can be used for billing, optimization, and cost governance.
849
+
850
+ ```ebnf
851
+ metering = 'metering' '{' metering-field* '}'
852
+ metering-field = 'track' ':' name-list
853
+ | 'per' ':' name-list
854
+ | 'output' ':' string
855
+ | 'format' ':' ('json' | 'jsonl' | 'csv')
856
+ | 'pricing' ':' ('anthropic-current' | 'custom' | 'none')
857
+ ```
858
+
859
+ ```agenttopology
860
+ metering {
861
+ track: [tokens-in, tokens-out, cost, wall-time, agent-count]
862
+ per: [phase, agent, run]
863
+ output: "metrics/"
864
+ format: jsonl
865
+ pricing: none
866
+ }
867
+ ```
868
+
869
+ | Field | Type | Required | Default | Description |
870
+ |-------|------|----------|---------|-------------|
871
+ | `track` | name-list | no | `[tokens-in, tokens-out, cost, wall-time]` | Metrics to collect |
872
+ | `per` | name-list | no | `[phase]` | Aggregation granularity |
873
+ | `output` | string | no | `"metrics/"` | Directory for metering output files |
874
+ | `format` | `json` / `jsonl` / `csv` | no | `jsonl` | Output format |
875
+ | `pricing` | `anthropic-current` / `custom` / `none` | no | `anthropic-current` | How to calculate cost |
876
+
877
+ **Track options:**
878
+ - `tokens-in` -- input tokens consumed
879
+ - `tokens-out` -- output tokens produced
880
+ - `cost` -- calculated cost based on model pricing
881
+ - `wall-time` -- elapsed wall-clock seconds
882
+ - `agent-count` -- number of agent instances spawned (relevant for scaled agents)
883
+
884
+ **Per options:**
885
+ - `phase` -- aggregate by pipeline phase number
886
+ - `agent` -- per-agent breakdown (including individual scale instances)
887
+ - `run` -- single total for the entire run
888
+
889
+ **Pricing modes:**
890
+ - `anthropic-current` -- use current Anthropic API pricing
891
+ - `custom` -- read pricing from a `pricing.json` file in the metering output directory
892
+ - `none` -- track tokens and time but don't calculate cost
893
+
894
+ **Output example** (`format: jsonl`):
895
+ ```json
896
+ {"run_id": "2026-03-11T22:30:00Z", "phase": 3, "agent": "classifier", "instances": 8, "tokens_in": 2570000, "tokens_out": 450000, "cost_usd": 14.50, "wall_time_s": 1080}
897
+ ```
898
+
899
+ **Backward compatibility:** A topology without `metering` simply doesn't collect cost data. No existing behavior changes.
900
+
901
+ ---
902
+
903
+ ### 4.20 `tools`
904
+
905
+ Topology-level declaration of custom script-based tools. Agents reference these by name in their `tools` field. Any runtime maps them to executable tool definitions.
906
+
907
+ ```ebnf
908
+ tools-block = 'tools' '{' tool-decl* '}'
909
+ tool-decl = 'tool' identifier '{' tool-decl-fields '}'
910
+ tool-decl-fields = ('script' ':' string
911
+ | 'args' ':' name-list
912
+ | 'description' ':' string
913
+ | 'lang' ':' tool-lang)*
914
+ tool-lang = 'bash' | 'python' | 'node' | 'auto'
915
+ ```
916
+
917
+ ```agenttopology
918
+ tools {
919
+ tool run-tests {
920
+ script: "scripts/run-tests.sh"
921
+ args: [test-dir, coverage]
922
+ lang: bash
923
+ description: "Run test suite with optional coverage reporting"
924
+ }
925
+
926
+ tool extract-data {
927
+ script: "scripts/extract-data.py"
928
+ args: [input-path, output-path, format]
929
+ lang: python
930
+ description: "Extract structured data from documents"
931
+ }
932
+ }
933
+ ```
934
+
935
+ | Field | Type | Required | Default |
936
+ |-------|------|----------|---------|
937
+ | `script` | string | yes | -- |
938
+ | `args` | name-list | no | `[]` |
939
+ | `description` | string | no | -- |
940
+ | `lang` | tool-lang | no | `auto` |
941
+
942
+ **`script`** -- Path to the script file, relative to the output directory.
943
+
944
+ **`lang`** -- Runtime hint. `auto` (default) infers from file extension (`.sh` -> bash, `.py` -> python, `.js`/`.ts` -> node). Explicit values override inference.
945
+
946
+ **`args`** -- Named arguments the script accepts. These become the tool's input schema when mapped to any platform.
947
+
948
+ **Agents reference custom tools by name:**
949
+ ```agenttopology
950
+ agent processor {
951
+ tools: [Read, Write, run-tests, extract-data]
952
+ }
953
+ ```
954
+
955
+ **LL(2) note:** `tools` is a block keyword. The parser sees `tools` `{` and enters the tools-block production. Inside, each `tool` `<identifier>` `{` starts a tool-decl. No ambiguity -- `tools` as a field keyword (in agent-fields) is always followed by `:`, while `tools` as a block keyword is followed by `{`.
956
+
957
+ ---
958
+
959
+ ### 4.21 `skill` (declarations)
960
+
961
+ Topology-level skill declarations. Each `skill` block declares a self-contained application that bundles executable scripts, domain knowledge, reference docs, and instructions into a coherent capability. Agents load skills via their `skills` field.
962
+
963
+ When **no `skill` blocks are declared**, the scaffold generates a single default skill named after the topology. When **one or more `skill` blocks are declared**, each generates its own directory with its own scripts, domains, and references.
964
+
965
+ ```ebnf
966
+ skill-decl = 'skill' identifier '{' skill-fields '}'
967
+ skill-fields = ('description' ':' string
968
+ | 'scripts' ':' name-list
969
+ | 'domains' ':' path-list
970
+ | 'references' ':' path-list
971
+ | 'prompt' ':' string
972
+ | 'disable-model-invocation' ':' boolean
973
+ | 'user-invocable' ':' boolean
974
+ | 'context' ':' identifier
975
+ | 'agent' ':' identifier
976
+ | 'allowed-tools' ':' tool-list
977
+ | skill-extensions)*
978
+ skill-extensions = 'extensions' '{' binding-ext-block* '}'
979
+ ```
980
+
981
+ ```agenttopology
982
+ skill orchestration {
983
+ description: "Pipeline coordination -- phase sequencing, agent spawning, error recovery"
984
+ scripts: [emit-event, trace-start, trace-stop]
985
+ prompt: "prompts/orchestrator.md"
986
+ }
987
+
988
+ skill extraction {
989
+ description: "Multi-format text extraction -- PDF, DOC, images"
990
+ scripts: [extract-pdf, extract-docx, ocr-batch]
991
+ }
992
+
993
+ skill classification {
994
+ description: "Document classification and scoring"
995
+ scripts: [merge-classifications, validate-extraction]
996
+ domains: ["domains/rubric.md", "domains/patterns.md"]
997
+ }
998
+ ```
999
+
1000
+ | Field | Type | Required | Default |
1001
+ |-------|------|----------|---------|
1002
+ | `description` | string | no | -- |
1003
+ | `scripts` | name-list | no | `[]` |
1004
+ | `domains` | path-list | no | `[]` |
1005
+ | `references` | path-list | no | `[]` |
1006
+ | `prompt` | string | no | -- |
1007
+ | `disable-model-invocation` | boolean | no | -- |
1008
+ | `user-invocable` | boolean | no | -- |
1009
+ | `context` | identifier | no | -- |
1010
+ | `agent` | identifier | no | -- |
1011
+ | `allowed-tools` | tool-list | no | `[]` |
1012
+ | `extensions` | skill-extensions | no | -- |
1013
+
1014
+ **`disable-model-invocation`** -- When `true`, prevents the skill from automatically invoking the model. Useful for skills that only run scripts.
1015
+
1016
+ **`user-invocable`** -- When `true`, the skill appears in user-facing menus and can be triggered directly.
1017
+
1018
+ **`context`** -- Context mode for the skill. `fork` creates an isolated subagent context.
1019
+
1020
+ **`agent`** -- Agent type to use when `context` is `fork`. References an agent defined in the topology.
1021
+
1022
+ **`allowed-tools`** -- Tools that can be used without permission prompts when this skill is active.
1023
+
1024
+ **`scripts`** -- Names from the `tools` block that this skill owns. The scaffold places these scripts in the skill's scripts directory.
1025
+
1026
+ **`domains`** -- Domain knowledge files bundled with this skill.
1027
+
1028
+ **`references`** -- Reference docs bundled with this skill.
1029
+
1030
+ **`prompt`** -- Path to the skill body content (skill-relative). If omitted, the scaffold generates a default from the description.
1031
+
1032
+ **Agents reference skills by name:**
1033
+ ```agenttopology
1034
+ agent extractor {
1035
+ skills: [extraction, shared-domain]
1036
+ tools: [Read, Write, extract-pdf, extract-docx]
1037
+ }
1038
+ ```
1039
+
1040
+ **How skills and tools relate:**
1041
+ - The `tools` block is a **registry** -- declares all custom scripts at the topology level
1042
+ - The `skill` block is an **application** -- bundles scripts + knowledge + instructions into a deployable capability
1043
+ - Skills own their scripts (via `scripts` field)
1044
+ - Agents load skills like apps (via `skills` field)
1045
+ - An agent's `tools` field controls **which tools it can execute**; `skills` controls **which applications it loads**
1046
+
1047
+ **Backward compatibility:** If no `skill` blocks are declared, the scaffold generates a single skill named after the topology. Existing `.at` files continue to work unchanged.
1048
+
1049
+ **LL(2) note:** `skill` is a block keyword. The parser sees `skill` `<identifier>` `{` and enters the skill-decl production. No ambiguity -- `skills` (the agent field) is always followed by `:`, while `skill` (the declaration) is followed by an identifier.
1050
+
1051
+ ---
1052
+
1053
+ ### 4.22 `context`
1054
+
1055
+ Singleton block. Configures the context/instructions file generated by bindings (e.g., `CLAUDE.md` for Claude Code, `CODEX.md` for Codex, `copilot-instructions.md` for Copilot).
1056
+
1057
+ ```ebnf
1058
+ context = 'context' '{' context-fields '}'
1059
+ context-fields = ('file' ':' string | 'includes' ':' string-list)*
1060
+ ```
1061
+
1062
+ ```agenttopology
1063
+ context {
1064
+ file: "CONTEXT.md"
1065
+ includes: ["docs/architecture.md", "docs/conventions.md"]
1066
+ }
1067
+ ```
1068
+
1069
+ | Field | Type | Required | Default | Description |
1070
+ |-------|------|----------|---------|-------------|
1071
+ | `file` | string | no | binding-dependent | Filename for the context/instructions file |
1072
+ | `includes` | string-list | no | `[]` | Additional files to include in context |
1073
+
1074
+ **Binding behavior:** If `file` is omitted, the binding generates a default context file using its platform convention (e.g., `CLAUDE.md`, `CODEX.md`). The `includes` list is referenced from the generated context file.
1075
+
1076
+ **Backward compatibility:** A topology without `context` generates a default context file. Existing `.at` files continue to work unchanged.
1077
+
1078
+ ---
1079
+
1080
+ ### 4.23 `env`
1081
+
1082
+ Free-form key-value pairs defining environment variables for the topology. Supports `${VAR}` syntax for referencing external environment variables.
1083
+
1084
+ ```ebnf
1085
+ env = 'env' '{' env-field* '}'
1086
+ env-field = identifier ':' string
1087
+ ```
1088
+
1089
+ ```agenttopology
1090
+ env {
1091
+ NODE_ENV: "development"
1092
+ API_KEY: "${API_KEY}"
1093
+ LOG_LEVEL: "debug"
1094
+ }
1095
+ ```
1096
+
1097
+ | Field | Type | Required | Default | Description |
1098
+ |-------|------|----------|---------|-------------|
1099
+ | `<identifier>` | string | -- | -- | Environment variable name and value |
1100
+
1101
+ **`${VAR}` syntax:** Values containing `${...}` reference external environment variables. These are resolved at runtime by the binding, not at parse time. This allows secrets and deployment-specific values to remain outside the `.at` file.
1102
+
1103
+ **Binding behavior:** How env variables are applied depends on the binding. Claude Code merges them into `settings.json` under an `env` key. Other bindings may write `.env` files or pass them directly to processes.
1104
+
1105
+ **Backward compatibility:** A topology without `env` simply has no environment variable configuration. Existing `.at` files continue to work unchanged.
1106
+
1107
+ ---
1108
+
1109
+ ### 4.24 `extensions`
1110
+
1111
+ Namespaced extension blocks for binding-specific configuration. Each binding reads its own namespace; unknown namespaces are silently ignored. This enables platform-specific features without polluting the core language.
1112
+
1113
+ ```ebnf
1114
+ extensions = 'extensions' '{' binding-ext-block* '}'
1115
+ binding-ext-block = identifier '{' (identifier ':' value)* '}'
1116
+ ```
1117
+
1118
+ ```agenttopology
1119
+ agent explorer {
1120
+ model: opus
1121
+ permissions: autonomous
1122
+ description: "Deep codebase analysis"
1123
+ max-turns: 25
1124
+ extensions {
1125
+ claude-code {
1126
+ memory: project
1127
+ custom-field: "value"
1128
+ }
1129
+ codex {
1130
+ sandbox: true
1131
+ }
1132
+ }
1133
+ }
1134
+ ```
1135
+
1136
+ **Scope:** `extensions` can appear inside `agent`, `skill`, `gate`, and `hook` blocks.
1137
+
1138
+ **Semantics:** Each named sub-block inside `extensions` corresponds to a binding name. The binding reads only its own namespace and ignores all others. Fields inside a binding namespace are opaque to the core parser -- they are stored as `Record<string, unknown>` and passed through to the binding.
1139
+
1140
+ **Why extensions:** The core `.at` language defines universal constructs that work across all platforms. Extensions allow bindings to expose platform-specific features (e.g., Claude Code's `memory: project`, Codex's `sandbox: true`) without adding fields to the core spec that only one platform uses. This keeps the language clean while remaining fully extensible.
1141
+
1142
+ **LL(2) note:** `extensions` is a block keyword. The parser sees `extensions` `{` and enters the extensions production. Inside, each `<identifier>` `{` starts a binding namespace block. No ambiguity -- `extensions` only appears inside other blocks, never at the top level.
1143
+
1144
+ ---
1145
+
1146
+ ### 4.25 `providers`
1147
+
1148
+ Declares API provider credentials, base URLs, and model routing. The `providers` block is **optional** — omitting it means the topology uses subscription-based authentication (the default for CLI tools like Claude Code and Gemini CLI).
1149
+
1150
+ ```ebnf
1151
+ providers-block = 'providers' '{' { provider-def } '}' ;
1152
+ provider-def = IDENT '{' { provider-field } '}' ;
1153
+ provider-field = 'api-key' ':' string-value
1154
+ | 'base-url' ':' string-value
1155
+ | 'models' ':' '[' model-id { ',' model-id } ']'
1156
+ | 'default' ':' boolean
1157
+ | IDENT ':' value ;
1158
+ ```
1159
+
1160
+ ```agenttopology
1161
+ providers {
1162
+ anthropic {
1163
+ api-key: "${ANTHROPIC_API_KEY}"
1164
+ models: [opus, sonnet, haiku]
1165
+ default: true
1166
+ }
1167
+ openai {
1168
+ api-key: "${OPENAI_API_KEY}"
1169
+ base-url: "https://api.openai.com/v1"
1170
+ models: [gpt-4o, o4-mini]
1171
+ }
1172
+ ollama {
1173
+ base-url: "http://localhost:11434/v1"
1174
+ models: [llama-3.1-70b, mistral-7b]
1175
+ }
1176
+ }
1177
+ ```
1178
+
1179
+ **Security:** `api-key` values MUST be `${ENV_VAR}` references. Literal API keys are a validation error (V16).
1180
+
1181
+ **Semantics:**
1182
+ - `api-key` — environment variable reference for the provider's API key. Must use `${VAR_NAME}` syntax.
1183
+ - `base-url` — custom endpoint URL. Optional; providers like Anthropic and OpenAI have well-known defaults.
1184
+ - `models` — model identifiers this provider can serve. Used for routing and validation.
1185
+ - `default` — at most one provider may be marked as default. When a model is available from multiple providers, the default provider is preferred.
1186
+ - Extra fields are stored in `ProviderDef.extra` for forward compatibility.
1187
+
1188
+ **LL(2) note:** `providers` is a top-level block keyword. The parser sees `providers` `{` and enters the providers production. Inside, each `<identifier>` `{` starts a named provider sub-block. No ambiguity.
1189
+
1190
+ ---
1191
+
1192
+ ### 4.26 `schedule`
1193
+
1194
+ Declares scheduled jobs -- recurring tasks that trigger agents or actions on a cron schedule. This is a **first-class capability section**: bindings that support scheduling (e.g., OpenClaw, GitHub Actions) generate native cron configuration. Bindings that don't support scheduling emit documentation or skip the section with a warning.
1195
+
1196
+ ```ebnf
1197
+ schedule = 'schedule' '{' schedule-entry* '}'
1198
+ schedule-entry = 'job' identifier '{' schedule-fields '}'
1199
+ schedule-fields = ('cron' ':' string)
1200
+ | ('every' ':' string)
1201
+ | ('agent' ':' identifier)
1202
+ | ('action' ':' identifier)
1203
+ | ('enabled' ':' boolean)
1204
+ ```
1205
+
1206
+ ```agenttopology
1207
+ schedule {
1208
+ job daily-digest {
1209
+ cron: "0 9 * * *"
1210
+ agent: summarizer
1211
+ }
1212
+ job weekly-report {
1213
+ every: "monday 9:00"
1214
+ agent: reporter
1215
+ enabled: true
1216
+ }
1217
+ }
1218
+ ```
1219
+
1220
+ | Field | Type | Required | Default | Description |
1221
+ |-------|------|----------|---------|-------------|
1222
+ | `cron` | string | one of cron/every | -- | Standard cron expression (5-field) |
1223
+ | `every` | string | one of cron/every | -- | Human-readable schedule ("daily", "monday 9:00") |
1224
+ | `agent` | identifier | one of agent/action | -- | Agent to trigger |
1225
+ | `action` | identifier | one of agent/action | -- | Action to trigger |
1226
+ | `enabled` | boolean | no | `true` | Whether the job is active |
1227
+
1228
+ **Mutual exclusivity:** A job must have exactly one of `cron` or `every` -- not both (V20). A job must reference exactly one of `agent` or `action`.
1229
+
1230
+ **Validation:** Every `agent` or `action` referenced must be declared in the topology (V20).
1231
+
1232
+ **Binding behavior:** OpenClaw maps jobs to `cronJobs` in `openclaw.json`. GitHub Copilot maps `cron` expressions to `schedule:` triggers in GitHub Actions workflows. Other bindings document the schedules in their instructions files.
1233
+
1234
+ ---
1235
+
1236
+ ### 4.27 `interfaces`
1237
+
1238
+ Declares external interfaces -- how the agent topology connects to the outside world. Interfaces define input/output surfaces such as webhooks, HTTP APIs, messaging channels, and email endpoints.
1239
+
1240
+ ```ebnf
1241
+ interfaces = 'interfaces' '{' interface-entry* '}'
1242
+ interface-entry = identifier '{' interface-fields '}'
1243
+ interface-fields = ('type' ':' identifier)?
1244
+ | (identifier ':' value)*
1245
+ ```
1246
+
1247
+ ```agenttopology
1248
+ interfaces {
1249
+ slack {
1250
+ type: webhook
1251
+ webhook: "${SLACK_WEBHOOK_URL}"
1252
+ }
1253
+ api {
1254
+ type: http
1255
+ port: 8080
1256
+ auth: "${API_TOKEN}"
1257
+ }
1258
+ discord {
1259
+ type: webhook
1260
+ webhook: "${DISCORD_WEBHOOK_URL}"
1261
+ channel: "agent-updates"
1262
+ }
1263
+ }
1264
+ ```
1265
+
1266
+ | Field | Type | Required | Default | Description |
1267
+ |-------|------|----------|---------|-------------|
1268
+ | `type` | identifier | no | -- | Interface type (webhook, http, sse, email) |
1269
+ | `*` | value | no | -- | Additional config fields (opaque, passed to binding) |
1270
+
1271
+ **Security:** Fields named `webhook`, `auth`, `token`, or `secret` that contain literal values (not `${ENV_VAR}` references) are a validation error (V21). This reuses the same secret detection pattern as provider `api-key` fields (V16).
1272
+
1273
+ **Binding behavior:** OpenClaw maps interfaces to `channels` in `openclaw.json`. Other bindings document interfaces in their instructions files. The interface block is intentionally open-ended -- bindings may interpret or ignore any field.
1274
+
1275
+ ---
1276
+
1277
+ ### 4.28 `sandbox` (settings and agent field)
1278
+
1279
+ The `sandbox` field specifies execution sandboxing for the topology or individual agents. It appears as a field inside `settings {}` (topology-wide default) or directly on an `agent {}` block (per-agent override).
1280
+
1281
+ ```agenttopology
1282
+ settings {
1283
+ sandbox: docker
1284
+ allow: [Read, Write, Bash]
1285
+ }
1286
+
1287
+ agent analyzer {
1288
+ model: sonnet
1289
+ sandbox: network-only
1290
+ }
1291
+ ```
1292
+
1293
+ **Valid values:** `true`, `false`, `docker`, `none`, `network-only`
1294
+
1295
+ **Binding behavior:** Codex maps to `[sandbox] type` in `codex.toml`. OpenClaw maps to `sandboxDefaults` in `openclaw.json`. Gemini CLI maps to the `sandbox` field in `.gemini/settings.json`. Other bindings include it in agent frontmatter or documentation.
1296
+
1297
+ **Agent override:** When `sandbox` appears on an agent, it overrides the topology-level default for that agent only.
1298
+
1299
+ ---
1300
+
1301
+ ### 4.29 `fallback-chain` (settings and agent field)
1302
+
1303
+ The `fallback-chain` field specifies an ordered list of model identifiers to try when the primary model is unavailable. It appears as a field inside `settings {}` (topology-wide default) or directly on an `agent {}` block (per-agent override).
1304
+
1305
+ ```agenttopology
1306
+ settings {
1307
+ fallback-chain: [opus, sonnet, haiku]
1308
+ }
1309
+
1310
+ agent expensive-thinker {
1311
+ model: opus
1312
+ fallback-chain: [sonnet, haiku]
1313
+ }
1314
+ ```
1315
+
1316
+ **Semantics:** When the primary model (from the `model` field) fails or is unavailable, the binding should attempt models in the fallback chain in order. This is distinct from `providers` -- providers define WHERE to route, fallback-chain defines WHAT to try.
1317
+
1318
+ **Validation:** When providers are declared, every model in a fallback chain should exist in at least one provider's `models` list (V22, warning level).
1319
+
1320
+ **Binding behavior:** OpenClaw maps to `modelFallbackChain` in `openclaw.json`. Other bindings document the chain in their instructions or agent files.
1321
+
1322
+ ---
1323
+
1324
+ ## 5. Validation Rules
1325
+
1326
+ See `validation.md` for the complete list of 29 validation rules with examples.
1327
+
1328
+ ---
1329
+
1330
+ ## 6. Reserved Keywords
1331
+
1332
+ See `reserved-keywords.md` for the complete keyword list.
1333
+
1334
+ ---
1335
+
1336
+ ## 7. Defaults Table
1337
+
1338
+ All optional fields and their defaults when omitted:
1339
+
1340
+ | Block | Field | Default |
1341
+ |-------|-------|---------|
1342
+ | `orchestrator` | `outputs` | `{}` |
1343
+ | `agent` | `role` | -- (none) |
1344
+ | `agent` | `permissions` | `autonomous` |
1345
+ | `agent` | `prompt` | -- (none) |
1346
+ | `agent` | `phase` | -- (none, unordered) |
1347
+ | `agent` | `tools` | all (no restriction) |
1348
+ | `agent` | `disallowed-tools` | `[]` |
1349
+ | `agent` | `reads` | `[]` |
1350
+ | `agent` | `writes` | `[]` |
1351
+ | `agent` | `outputs` | `{}` |
1352
+ | `agent` | `skip` | -- (never skip) |
1353
+ | `agent` | `retry` | `0` |
1354
+ | `agent` | `isolation` | -- (no isolation) |
1355
+ | `agent` | `invocation` | `auto` |
1356
+ | `agent` | `behavior` | `blocking` |
1357
+ | `agent` | `memory` | -- (none) |
1358
+ | `agent` | `skills` | `[]` |
1359
+ | `agent` | `mcp-servers` | `[]` |
1360
+ | `agent` | `background` | `false` |
1361
+ | `agent` | `hooks` | -- (none) |
1362
+ | `agent` | `max-turns` | -- (none) |
1363
+ | `agent` | `sandbox` | -- (none) |
1364
+ | `agent` | `fallback-chain` | `[]` |
1365
+ | `agent` | `extensions` | -- (none) |
1366
+ | `action` | `source` | -- (none) |
1367
+ | `action` | `description` | -- (none) |
1368
+ | `action` | `commands` | `[]` |
1369
+ | `orchestrator` | `generates` | -- (none) |
1370
+ | `gate` | `before` | -- (side-effect only) |
1371
+ | `gate` | `checks` | `[]` |
1372
+ | `gate` | `retry` | `0` |
1373
+ | `gate` | `on-fail` | `halt` |
1374
+ | `gate` | `behavior` | `blocking` |
1375
+ | `batch` | `parallel` | `false` |
1376
+ | `batch` | `conflicts.detect` | `[]` |
1377
+ | `batch` | `conflicts.resolve` | -- (none) |
1378
+ | `batch` | `workspace` | -- (none) |
1379
+ | `triggers.command` | `argument` | -- (none) |
1380
+ | `hook` | `matcher` | -- (none, matches all) |
1381
+ | `hook` | `type` | `command` |
1382
+ | `hook` | `timeout` | `600` |
1383
+ | `settings` | `allow` | `[]` |
1384
+ | `settings` | `deny` | `[]` |
1385
+ | `settings` | `ask` | `[]` |
1386
+ | `settings` | `sandbox` | -- (none) |
1387
+ | `settings` | `fallback-chain` | `[]` |
1388
+ | `schedule.job` | `enabled` | `true` |
1389
+ | `mcp-server` | `url` | -- (none) |
1390
+ | `mcp-server` | `command` | -- (none) |
1391
+ | `mcp-server` | `args` | `[]` |
1392
+ | `mcp-server` | `env` | `{}` |
1393
+ | `tool` | `args` | `[]` |
1394
+ | `tool` | `lang` | `auto` |
1395
+ | `tool` | `description` | -- (none) |
1396
+ | `context` | `file` | binding-dependent |
1397
+ | `context` | `includes` | `[]` |
1398
+ | `skill` | `disable-model-invocation` | -- (none) |
1399
+ | `skill` | `user-invocable` | -- (none) |
1400
+ | `skill` | `context` | -- (none) |
1401
+ | `skill` | `agent` | -- (none) |
1402
+ | `skill` | `allowed-tools` | `[]` |
1403
+ | `skill` | `extensions` | -- (none) |
1404
+ | `gate` | `extensions` | -- (none) |
1405
+ | `hook` | `extensions` | -- (none) |
1406
+ | `meta` | `domain` | -- (none) |
1407
+
1408
+ ---
1409
+
1410
+ ## 8. Bindings
1411
+
1412
+ The `.at` spec defines **WHAT** to generate. Bindings define **HOW** for each platform.
1413
+
1414
+ A binding is a code module that reads a parsed `.at` file and produces the platform-specific configuration files, agent definitions, and orchestration logic for a given runtime. Each binding implements the `BindingTarget` interface.
1415
+
1416
+ ### What a binding does
1417
+
1418
+ 1. **Reads** the parsed AST from the `.at` compiler
1419
+ 2. **Maps** each block type to platform-specific output (agent configs, permission files, MCP configs, orchestrator scripts)
1420
+ 3. **Generates** the directory structure the platform expects
1421
+ 4. **Resolves** abstract concepts (model identifiers, permissions, hook events) to platform-specific values
1422
+
1423
+ ### Model mapping
1424
+
1425
+ Bindings map model identifiers to their platform's model format:
1426
+
1427
+ | `.at` identifier | Claude Code | OpenAI | Google |
1428
+ |-----------------|-------------|--------|--------|
1429
+ | `opus` | `claude-opus-4-20250514` | -- | -- |
1430
+ | `sonnet` | `claude-sonnet-4-20250514` | -- | -- |
1431
+ | `gpt-4o` | -- | `gpt-4o` | -- |
1432
+ | `gemini-pro` | -- | -- | `gemini-1.5-pro` |
1433
+
1434
+ ### Permission mapping
1435
+
1436
+ Bindings map universal permissions to platform-specific modes:
1437
+
1438
+ | Universal | Claude Code | Description |
1439
+ |-----------|-------------|-------------|
1440
+ | `autonomous` | `auto` | Read-write without confirmation |
1441
+ | `supervised` | `plan` | Read-only, proposes changes |
1442
+ | `interactive` | `confirm` | Asks before each action |
1443
+ | `unrestricted` | `bypass` | No permission checks |
1444
+
1445
+ ### Available bindings
1446
+
1447
+ | Binding | Status | Target |
1448
+ |---------|--------|--------|
1449
+ | `claude-code` | Stable | Claude Code agent files, settings, MCP config |
1450
+ | `agent-sdk` | Planned | Anthropic Agent SDK tool definitions |
1451
+ | `e2b` | Planned | E2B sandbox configuration |
1452
+ | `openai-swarm` | Planned | OpenAI Swarm agent definitions |
1453
+
1454
+ See `docs/bindings.md` for details on creating new bindings.
1455
+
1456
+ ---
1457
+
1458
+ ## 9. Roadmap
1459
+
1460
+ - `extend` -- topology inheritance
1461
+ - `template` -- parameterized topologies
1462
+ - `cost` -- per-agent budgets
1463
+ - `sla` -- time constraints
1464
+ - `test` -- inline topology tests
1465
+ - `variables` -- runtime parameters
1466
+ - `event` -- event-driven triggers (pub-sub)