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.
- package/LICENSE +191 -0
- package/README.md +450 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts +2 -0
- package/dist/analyzer/__tests__/analyzer.test.d.ts.map +1 -0
- package/dist/analyzer/__tests__/analyzer.test.js +258 -0
- package/dist/analyzer/__tests__/analyzer.test.js.map +1 -0
- package/dist/analyzer/index.d.ts +44 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +308 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/bindings/__tests__/bindings.test.d.ts +2 -0
- package/dist/bindings/__tests__/bindings.test.d.ts.map +1 -0
- package/dist/bindings/__tests__/bindings.test.js +2089 -0
- package/dist/bindings/__tests__/bindings.test.js.map +1 -0
- package/dist/bindings/claude-code.d.ts +12 -0
- package/dist/bindings/claude-code.d.ts.map +1 -0
- package/dist/bindings/claude-code.js +2063 -0
- package/dist/bindings/claude-code.js.map +1 -0
- package/dist/bindings/codex.d.ts +18 -0
- package/dist/bindings/codex.d.ts.map +1 -0
- package/dist/bindings/codex.js +1633 -0
- package/dist/bindings/codex.js.map +1 -0
- package/dist/bindings/copilot-cli.d.ts +20 -0
- package/dist/bindings/copilot-cli.d.ts.map +1 -0
- package/dist/bindings/copilot-cli.js +1515 -0
- package/dist/bindings/copilot-cli.js.map +1 -0
- package/dist/bindings/cursor.d.ts +17 -0
- package/dist/bindings/cursor.d.ts.map +1 -0
- package/dist/bindings/cursor.js +1715 -0
- package/dist/bindings/cursor.js.map +1 -0
- package/dist/bindings/gemini-cli.d.ts +18 -0
- package/dist/bindings/gemini-cli.d.ts.map +1 -0
- package/dist/bindings/gemini-cli.js +1327 -0
- package/dist/bindings/gemini-cli.js.map +1 -0
- package/dist/bindings/index.d.ts +20 -0
- package/dist/bindings/index.d.ts.map +1 -0
- package/dist/bindings/index.js +33 -0
- package/dist/bindings/index.js.map +1 -0
- package/dist/bindings/kiro.d.ts +17 -0
- package/dist/bindings/kiro.d.ts.map +1 -0
- package/dist/bindings/kiro.js +1624 -0
- package/dist/bindings/kiro.js.map +1 -0
- package/dist/bindings/openclaw.d.ts +18 -0
- package/dist/bindings/openclaw.d.ts.map +1 -0
- package/dist/bindings/openclaw.js +1578 -0
- package/dist/bindings/openclaw.js.map +1 -0
- package/dist/bindings/types.d.ts +33 -0
- package/dist/bindings/types.d.ts.map +1 -0
- package/dist/bindings/types.js +23 -0
- package/dist/bindings/types.js.map +1 -0
- package/dist/cli/index.d.ts +19 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +679 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/docs/__tests__/docs.test.d.ts +2 -0
- package/dist/docs/__tests__/docs.test.d.ts.map +1 -0
- package/dist/docs/__tests__/docs.test.js +438 -0
- package/dist/docs/__tests__/docs.test.js.map +1 -0
- package/dist/docs/content.d.ts +16 -0
- package/dist/docs/content.d.ts.map +1 -0
- package/dist/docs/content.js +3273 -0
- package/dist/docs/content.js.map +1 -0
- package/dist/docs/index.d.ts +17 -0
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +117 -0
- package/dist/docs/index.js.map +1 -0
- package/dist/exporters/__tests__/exporters.test.d.ts +2 -0
- package/dist/exporters/__tests__/exporters.test.d.ts.map +1 -0
- package/dist/exporters/__tests__/exporters.test.js +731 -0
- package/dist/exporters/__tests__/exporters.test.js.map +1 -0
- package/dist/exporters/index.d.ts +13 -0
- package/dist/exporters/index.d.ts.map +1 -0
- package/dist/exporters/index.js +18 -0
- package/dist/exporters/index.js.map +1 -0
- package/dist/exporters/json.d.ts +8 -0
- package/dist/exporters/json.d.ts.map +1 -0
- package/dist/exporters/json.js +25 -0
- package/dist/exporters/json.js.map +1 -0
- package/dist/exporters/markdown.d.ts +9 -0
- package/dist/exporters/markdown.d.ts.map +1 -0
- package/dist/exporters/markdown.js +686 -0
- package/dist/exporters/markdown.js.map +1 -0
- package/dist/exporters/mermaid.d.ts +11 -0
- package/dist/exporters/mermaid.d.ts.map +1 -0
- package/dist/exporters/mermaid.js +161 -0
- package/dist/exporters/mermaid.js.map +1 -0
- package/dist/exporters/types.d.ts +22 -0
- package/dist/exporters/types.d.ts.map +1 -0
- package/dist/exporters/types.js +10 -0
- package/dist/exporters/types.js.map +1 -0
- package/dist/import/__tests__/import.test.d.ts +2 -0
- package/dist/import/__tests__/import.test.d.ts.map +1 -0
- package/dist/import/__tests__/import.test.js +781 -0
- package/dist/import/__tests__/import.test.js.map +1 -0
- package/dist/import/claude-code.d.ts +38 -0
- package/dist/import/claude-code.d.ts.map +1 -0
- package/dist/import/claude-code.js +897 -0
- package/dist/import/claude-code.js.map +1 -0
- package/dist/import/index.d.ts +18 -0
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +28 -0
- package/dist/import/index.js.map +1 -0
- package/dist/import/serializer.d.ts +11 -0
- package/dist/import/serializer.d.ts.map +1 -0
- package/dist/import/serializer.js +960 -0
- package/dist/import/serializer.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/__tests__/parser.test.d.ts +2 -0
- package/dist/parser/__tests__/parser.test.d.ts.map +1 -0
- package/dist/parser/__tests__/parser.test.js +6514 -0
- package/dist/parser/__tests__/parser.test.js.map +1 -0
- package/dist/parser/ast.d.ts +708 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +12 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/index.d.ts +253 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +1985 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/lexer.d.ts +100 -0
- package/dist/parser/lexer.d.ts.map +1 -0
- package/dist/parser/lexer.js +304 -0
- package/dist/parser/lexer.js.map +1 -0
- package/dist/parser/validator.d.ts +42 -0
- package/dist/parser/validator.d.ts.map +1 -0
- package/dist/parser/validator.js +2568 -0
- package/dist/parser/validator.js.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts +7 -0
- package/dist/scaffold/__tests__/scaffold.test.d.ts.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.js +295 -0
- package/dist/scaffold/__tests__/scaffold.test.js.map +1 -0
- package/dist/scaffold/incremental.d.ts +43 -0
- package/dist/scaffold/incremental.d.ts.map +1 -0
- package/dist/scaffold/incremental.js +185 -0
- package/dist/scaffold/incremental.js.map +1 -0
- package/dist/scaffold/index.d.ts +13 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +13 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/manifest.d.ts +30 -0
- package/dist/scaffold/manifest.d.ts.map +1 -0
- package/dist/scaffold/manifest.js +71 -0
- package/dist/scaffold/manifest.js.map +1 -0
- package/dist/scaffold/merge.d.ts +52 -0
- package/dist/scaffold/merge.d.ts.map +1 -0
- package/dist/scaffold/merge.js +134 -0
- package/dist/scaffold/merge.js.map +1 -0
- package/dist/scaffold/types.d.ts +51 -0
- package/dist/scaffold/types.d.ts.map +1 -0
- package/dist/scaffold/types.js +10 -0
- package/dist/scaffold/types.js.map +1 -0
- package/dist/sync/index.d.ts +34 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +392 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/visualizer/index.d.ts +17 -0
- package/dist/visualizer/index.d.ts.map +1 -0
- package/dist/visualizer/index.js +1976 -0
- package/dist/visualizer/index.js.map +1 -0
- package/examples/code-review.at +167 -0
- package/examples/data-processing.at +296 -0
- package/examples/openclaw-assistant.at +142 -0
- package/examples/scheduled-monitor.at +193 -0
- package/examples/simple-pipeline.at +163 -0
- package/package.json +77 -0
- package/skill/SKILL.md +403 -0
- package/skill/scripts/emit.ts +853 -0
- package/skill/scripts/visualize.ts +89 -0
- package/spec/grammar.md +1466 -0
- package/spec/reserved-keywords.md +147 -0
- package/spec/validation.md +535 -0
package/spec/grammar.md
ADDED
|
@@ -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)
|