agentopology 0.2.5 → 0.4.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 (162) hide show
  1. package/README.md +121 -7
  2. package/dist/analyzer/__tests__/analyzer.test.js +36 -9
  3. package/dist/analyzer/__tests__/analyzer.test.js.map +1 -1
  4. package/dist/analyzer/__tests__/observability-gap.test.js +40 -10
  5. package/dist/analyzer/__tests__/observability-gap.test.js.map +1 -1
  6. package/dist/analyzer/index.d.ts +2 -1
  7. package/dist/analyzer/index.d.ts.map +1 -1
  8. package/dist/analyzer/index.js +1 -1
  9. package/dist/analyzer/index.js.map +1 -1
  10. package/dist/bindings/__tests__/bindings.test.js +20 -5
  11. package/dist/bindings/__tests__/bindings.test.js.map +1 -1
  12. package/dist/bindings/__tests__/claude-workflow.test.js +12 -3
  13. package/dist/bindings/__tests__/claude-workflow.test.js.map +1 -1
  14. package/dist/bindings/__tests__/defaults-agreement.test.d.ts +16 -0
  15. package/dist/bindings/__tests__/defaults-agreement.test.d.ts.map +1 -0
  16. package/dist/bindings/__tests__/defaults-agreement.test.js +80 -0
  17. package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -0
  18. package/dist/bindings/__tests__/observability.test.js +8 -2
  19. package/dist/bindings/__tests__/observability.test.js.map +1 -1
  20. package/dist/bindings/__tests__/stub-generation.test.js +8 -2
  21. package/dist/bindings/__tests__/stub-generation.test.js.map +1 -1
  22. package/dist/bindings/claude-code.d.ts.map +1 -1
  23. package/dist/bindings/claude-code.js +102 -3
  24. package/dist/bindings/claude-code.js.map +1 -1
  25. package/dist/bindings/claude-workflow.d.ts.map +1 -1
  26. package/dist/bindings/claude-workflow.js +2 -1
  27. package/dist/bindings/claude-workflow.js.map +1 -1
  28. package/dist/bindings/codex.d.ts.map +1 -1
  29. package/dist/bindings/codex.js +6 -2
  30. package/dist/bindings/codex.js.map +1 -1
  31. package/dist/bindings/gemini-cli.d.ts.map +1 -1
  32. package/dist/bindings/gemini-cli.js +2 -1
  33. package/dist/bindings/gemini-cli.js.map +1 -1
  34. package/dist/bindings/kiro.d.ts.map +1 -1
  35. package/dist/bindings/kiro.js +2 -1
  36. package/dist/bindings/kiro.js.map +1 -1
  37. package/dist/bindings/openclaw.d.ts.map +1 -1
  38. package/dist/bindings/openclaw.js +2 -1
  39. package/dist/bindings/openclaw.js.map +1 -1
  40. package/dist/cli/__tests__/cli.test.d.ts +12 -0
  41. package/dist/cli/__tests__/cli.test.d.ts.map +1 -0
  42. package/dist/cli/__tests__/cli.test.js +171 -0
  43. package/dist/cli/__tests__/cli.test.js.map +1 -0
  44. package/dist/cli/index.d.ts +1 -0
  45. package/dist/cli/index.d.ts.map +1 -1
  46. package/dist/cli/index.js +344 -5
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/docs/content.d.ts.map +1 -1
  49. package/dist/docs/content.js +112 -0
  50. package/dist/docs/content.js.map +1 -1
  51. package/dist/exporters/ascii.d.ts +13 -0
  52. package/dist/exporters/ascii.d.ts.map +1 -0
  53. package/dist/exporters/ascii.js +26 -0
  54. package/dist/exporters/ascii.js.map +1 -0
  55. package/dist/exporters/brief.d.ts +14 -0
  56. package/dist/exporters/brief.d.ts.map +1 -0
  57. package/dist/exporters/brief.js +37 -0
  58. package/dist/exporters/brief.js.map +1 -0
  59. package/dist/exporters/index.d.ts +2 -0
  60. package/dist/exporters/index.d.ts.map +1 -1
  61. package/dist/exporters/index.js +6 -0
  62. package/dist/exporters/index.js.map +1 -1
  63. package/dist/exporters/markdown.d.ts.map +1 -1
  64. package/dist/exporters/markdown.js +2 -1
  65. package/dist/exporters/markdown.js.map +1 -1
  66. package/dist/exporters/mermaid.d.ts.map +1 -1
  67. package/dist/exporters/mermaid.js +2 -1
  68. package/dist/exporters/mermaid.js.map +1 -1
  69. package/dist/import/__tests__/import.test.js +8 -2
  70. package/dist/import/__tests__/import.test.js.map +1 -1
  71. package/dist/parser/__tests__/parser.test.js +1553 -207
  72. package/dist/parser/__tests__/parser.test.js.map +1 -1
  73. package/dist/parser/__tests__/spec-sync.test.d.ts +10 -0
  74. package/dist/parser/__tests__/spec-sync.test.d.ts.map +1 -0
  75. package/dist/parser/__tests__/spec-sync.test.js +60 -0
  76. package/dist/parser/__tests__/spec-sync.test.js.map +1 -0
  77. package/dist/parser/ast.d.ts +39 -0
  78. package/dist/parser/ast.d.ts.map +1 -1
  79. package/dist/parser/index.d.ts +50 -6
  80. package/dist/parser/index.d.ts.map +1 -1
  81. package/dist/parser/index.js +241 -30
  82. package/dist/parser/index.js.map +1 -1
  83. package/dist/parser/validator.d.ts +7 -1
  84. package/dist/parser/validator.d.ts.map +1 -1
  85. package/dist/parser/validator.js +143 -5
  86. package/dist/parser/validator.js.map +1 -1
  87. package/dist/plan/__tests__/brief.test.d.ts +2 -0
  88. package/dist/plan/__tests__/brief.test.d.ts.map +1 -0
  89. package/dist/plan/__tests__/brief.test.js +442 -0
  90. package/dist/plan/__tests__/brief.test.js.map +1 -0
  91. package/dist/plan/brief.d.ts +207 -0
  92. package/dist/plan/brief.d.ts.map +1 -0
  93. package/dist/plan/brief.js +342 -0
  94. package/dist/plan/brief.js.map +1 -0
  95. package/dist/plan/render.d.ts +20 -0
  96. package/dist/plan/render.d.ts.map +1 -0
  97. package/dist/plan/render.js +425 -0
  98. package/dist/plan/render.js.map +1 -0
  99. package/dist/render/__tests__/ascii.test.d.ts +2 -0
  100. package/dist/render/__tests__/ascii.test.d.ts.map +1 -0
  101. package/dist/render/__tests__/ascii.test.js +171 -0
  102. package/dist/render/__tests__/ascii.test.js.map +1 -0
  103. package/dist/render/ascii.d.ts +23 -0
  104. package/dist/render/ascii.d.ts.map +1 -0
  105. package/dist/render/ascii.js +178 -0
  106. package/dist/render/ascii.js.map +1 -0
  107. package/dist/resolve/__tests__/defaults.test.d.ts +2 -0
  108. package/dist/resolve/__tests__/defaults.test.d.ts.map +1 -0
  109. package/dist/resolve/__tests__/defaults.test.js +109 -0
  110. package/dist/resolve/__tests__/defaults.test.js.map +1 -0
  111. package/dist/resolve/__tests__/order.test.d.ts +2 -0
  112. package/dist/resolve/__tests__/order.test.d.ts.map +1 -0
  113. package/dist/resolve/__tests__/order.test.js +238 -0
  114. package/dist/resolve/__tests__/order.test.js.map +1 -0
  115. package/dist/resolve/__tests__/phase.test.d.ts +12 -0
  116. package/dist/resolve/__tests__/phase.test.d.ts.map +1 -0
  117. package/dist/resolve/__tests__/phase.test.js +81 -0
  118. package/dist/resolve/__tests__/phase.test.js.map +1 -0
  119. package/dist/resolve/defaults.d.ts +51 -0
  120. package/dist/resolve/defaults.d.ts.map +1 -0
  121. package/dist/resolve/defaults.js +109 -0
  122. package/dist/resolve/defaults.js.map +1 -0
  123. package/dist/resolve/order.d.ts +73 -0
  124. package/dist/resolve/order.d.ts.map +1 -0
  125. package/dist/resolve/order.js +208 -0
  126. package/dist/resolve/order.js.map +1 -0
  127. package/dist/resolve/phase.d.ts +57 -0
  128. package/dist/resolve/phase.d.ts.map +1 -0
  129. package/dist/resolve/phase.js +60 -0
  130. package/dist/resolve/phase.js.map +1 -0
  131. package/dist/scaffold/__tests__/scaffold.test.js +8 -2
  132. package/dist/scaffold/__tests__/scaffold.test.js.map +1 -1
  133. package/dist/scaffold/__tests__/settings-merge.test.js +95 -0
  134. package/dist/scaffold/__tests__/settings-merge.test.js.map +1 -1
  135. package/dist/scaffold/incremental.d.ts.map +1 -1
  136. package/dist/scaffold/incremental.js +13 -2
  137. package/dist/scaffold/incremental.js.map +1 -1
  138. package/dist/visualizer/__tests__/brain.test.d.ts +2 -0
  139. package/dist/visualizer/__tests__/brain.test.d.ts.map +1 -0
  140. package/dist/visualizer/__tests__/brain.test.js +138 -0
  141. package/dist/visualizer/__tests__/brain.test.js.map +1 -0
  142. package/dist/visualizer/__tests__/topology-brain-link.test.d.ts +2 -0
  143. package/dist/visualizer/__tests__/topology-brain-link.test.d.ts.map +1 -0
  144. package/dist/visualizer/__tests__/topology-brain-link.test.js +56 -0
  145. package/dist/visualizer/__tests__/topology-brain-link.test.js.map +1 -0
  146. package/dist/visualizer/brain.d.ts +97 -0
  147. package/dist/visualizer/brain.d.ts.map +1 -0
  148. package/dist/visualizer/brain.js +473 -0
  149. package/dist/visualizer/brain.js.map +1 -0
  150. package/dist/visualizer/index.d.ts +19 -1
  151. package/dist/visualizer/index.d.ts.map +1 -1
  152. package/dist/visualizer/index.js +26 -11
  153. package/dist/visualizer/index.js.map +1 -1
  154. package/examples/code-review.at +32 -4
  155. package/examples/company-brain-team.at +241 -0
  156. package/examples/company-brain.at +104 -0
  157. package/examples/data-processing.at +33 -4
  158. package/examples/logos/gmail.svg +1 -0
  159. package/examples/logos/slack.svg +1 -0
  160. package/package.json +1 -1
  161. package/spec/grammar.md +55 -3
  162. package/spec/validation.md +103 -35
package/spec/grammar.md CHANGED
@@ -43,6 +43,30 @@ template-var = [A-Z] [A-Z0-9_-]*
43
43
 
44
44
  **Rule:** Quote strings. Don't quote identifiers, numbers, or booleans.
45
45
 
46
+ **Rule: one field per line.** Key-value fields are newline-separated. Writing two
47
+ on one line makes the first key take the rest of the line as its value:
48
+
49
+ ```
50
+ # WRONG — `model` becomes the string "sonnet retry: 3"
51
+ agent a { model: sonnet retry: 3 }
52
+
53
+ # RIGHT
54
+ agent a {
55
+ model: sonnet
56
+ retry: 3
57
+ }
58
+ ```
59
+
60
+ This is not a parser limitation to route around — some unquoted values are
61
+ legitimately multi-token (`on-fail: fallback reviewer`), so whitespace could not
62
+ separate fields without ambiguity. V90 rejects the collapsed form, in `meta` and in every node block.
63
+
64
+ **Exception:** free-prose fields (`description`, `role`, `generates`,
65
+ `termination`) are exempt once their quotes have been stripped by the parser,
66
+ because a colon in English is ordinary — `description: "Demo: agent pipeline"`.
67
+ Where quotes survive parsing the check is still sharp: one well-formed quoted
68
+ token followed by more content means a field was eaten.
69
+
46
70
  ### Composite Types
47
71
 
48
72
  ```ebnf
@@ -207,6 +231,7 @@ perm-enum = perm-identifier
207
231
  | `fan-out` | Parallel slices |
208
232
  | `event-driven` | Pub-sub |
209
233
  | `human-gate` | Human approval points |
234
+ | `brain` | Agent-maintained, Obsidian-portable markdown knowledge graph |
210
235
 
211
236
  ---
212
237
 
@@ -346,7 +371,7 @@ scale-field = 'mode' ':' ('auto' | 'fixed' | 'config')
346
371
  | `role` | identifier | no | -- | Maps to a role defined in the `roles` block |
347
372
  | `model` | model-id | yes | -- | LLM model identifier (e.g., `opus`, `gpt-4o`, `gemini-pro`) |
348
373
  | `permissions` | perm-enum | no | `autonomous` | Permission mode |
349
- | `prompt` | block | no | -- | Inline agent instructions (multi-line text block) |
374
+ | `prompt` | block | no | -- | Inline agent instructions (multi-line text **block**). A key-value `prompt: "path"` is NOT valid on an agent — that form belongs to `skill` — and is rejected by V89. |
350
375
  | `phase` | number | no | -- | Pipeline position (ordering by numeric value) |
351
376
  | `tools` | tool-list | no | all | Tool allowlist |
352
377
  | `disallowed-tools` | tool-list | no | `[]` | Explicit deny-list |
@@ -360,6 +385,7 @@ scale-field = 'mode' ':' ('auto' | 'fixed' | 'config')
360
385
  | `behavior` | `advisory` / `blocking` | no | `blocking` | Advisory never blocks flow |
361
386
  | `memory` | name-list | no | `[]` | Store IDs this agent can access (V35) |
362
387
  | `retrieval` | identifier | no | -- | Retrieval strategy ID for memory queries (V35) |
388
+ | `custodian-of` | name-list `{ does: name-list }?` | no | -- | Store IDs this agent **maintains** (not just reads). The primitive behind the `brain` pattern. Optional `does:` declares maintenance verbs (e.g. `link, tag, index, dedupe`). Refs validated by V88. |
363
389
  | `skills` | name-list | no | `[]` | Skills to preload |
364
390
  | `mcp-servers` | name-list | no | `[]` | MCP servers available |
365
391
  | `background` | boolean | no | `false` | Run in background |
@@ -531,7 +557,7 @@ store-field = 'type' ':' store-type
531
557
  | lifecycle-block
532
558
  | backend-config-block
533
559
  store-type = 'semantic' | 'episodic' | 'procedural' | 'entity'
534
- | 'graph' | 'user' | 'session' | 'temporal'
560
+ | 'graph' | 'user' | 'session' | 'temporal' | 'brain'
535
561
  scope-enum = 'agent' | 'user' | 'session' | 'org' | 'global'
536
562
  isolation-enum = 'strict' | 'soft' | 'none'
537
563
  backend-enum = 'lancedb' | 'sqlite-vec' | 'chroma' | 'kuzu' | 'falkordb'
@@ -639,10 +665,12 @@ memory {
639
665
  | `description` | string | no | -- | Human-readable purpose |
640
666
  | `scope` | scope-enum | no | -- | Access scope (V32 warning if omitted) |
641
667
  | `isolation` | isolation-enum | no | -- | Isolation level between scopes |
642
- | `backend` | backend-enum | yes | -- | Storage backend (V30 enum check) |
668
+ | `backend` | backend-enum | yes | -- | Storage backend (V30 enum check). For `type: brain`, defaults to `files` and is exempt from the enum check (V81). |
643
669
  | `path` | string | no | -- | File system path for local backends |
644
670
  | `connection` | secret-string | no | -- | Connection URI for remote backends (V34) |
645
671
  | `extraction` | extraction-enum | no | -- | Entity/fact extraction method |
672
+ | `format` | string | no | -- | File format/convention for file-native stores. For `brain`, typically `obsidian` ([[wikilinks]], #tags, YAML frontmatter) — a promise the store round-trips as a real Obsidian vault. |
673
+ | `sources` | sources-block | no | -- | **Presentation only** — provenance styling for the brain graph view. Maps a note's `source:` frontmatter to a color/icon. Visualizer-only; bindings ignore it. See below. |
646
674
  | `embedding` | embedding-block | no | -- | Embedding configuration (V31 warning if omitted for semantic/episodic/procedural) |
647
675
  | `index` | index-block | no | -- | Index/collection configuration |
648
676
  | `ingestion` | ingestion-block | no | -- | Data ingestion configuration |
@@ -654,6 +682,30 @@ memory {
654
682
 
655
683
  **`backend-config`:** A passthrough block for backend-specific key-value pairs not covered by the standard fields. Stored as `Record<string, unknown>` on the AST. Same pattern as `ProviderDef.extra`.
656
684
 
685
+ **The `brain` store type (file-native).** A `brain` store is the file-native memory primitive behind the `brain` pattern: a folder of Obsidian-format markdown where the graph is a *projection* of the files, not a separate database. It is deliberately backend-less — its `backend` defaults to `files` and is exempt from the backend-enum check (V81), it needs no embedding (V82), and it compiles to direct file access (no MCP server). Edges come from inline `[[wikilinks]]` and `#tags`; backlinks are computed. Because the format is Obsidian, a `brain` store round-trips as a real Obsidian vault — agents build it, a human opens it in Obsidian for the graph view, and an existing vault becomes an agent-readable brain with zero migration. Heavyweight stores (`semantic`, `graph`, `entity`) remain available as an opt-in power-up alongside a brain. See `docs/company-brain-design.md`.
686
+
687
+ **Custody (`custodian-of`).** Custody is the one new primitive the brain pattern needs. `memory:` grants an agent *read* access to a store; `custodian-of:` makes the agent *responsible for the store's upkeep* — wiring dropped files into the graph (resolving mentions into `[[wikilinks]]`, assigning `#tags`, updating index/hub notes, flagging ghost nodes and duplicates). The optional `does:` sub-list declares the maintenance verbs as visible architecture (like an optional type annotation); omitting it lets the agent infer its duties from its prompt. The verb *names* are language vocabulary; *how* each verb is performed well is an implementation/skill concern, not part of the spec. V88 validates that every `custodian-of` reference resolves to a declared store and warns when custody targets a non-brain store.
688
+
689
+ **Provenance styling (`sources`).** A presentation-only sub-block on a brain store that colors the graph view by where each note came from. Each named entry maps a source key (matching a note's `source:` frontmatter, stamped by its ingester) to a `color` (hex) and/or `icon` (a file path):
690
+
691
+ ```
692
+ store brain {
693
+ type: brain
694
+ path: "brain/"
695
+ sources {
696
+ gmail {
697
+ color: "#EA4335"
698
+ icon: "./logos/gmail.svg"
699
+ }
700
+ slack {
701
+ color: "#4A154B"
702
+ }
703
+ }
704
+ }
705
+ ```
706
+
707
+ This is **strictly cosmetic and affects only the `visualize-brain` rendering** — it never touches ingestion, agents, files, or binding output. Bindings emit nothing for it; delete it and the brain behaves identically (the graph just falls back to category coloring). The language stores **no image bytes** — only the path string; the visualizer reads the icon file at generate-time and inlines it as a `data:` URI, keeping the output a single self-contained HTML file. When a node's `source` matches a `sources` key, it is colored/iconed accordingly; otherwise it falls back to category-based coloring (person/org/topic inferred from tag namespaces).
708
+
657
709
  **Local vs remote backends:** Backends `lancedb`, `sqlite-vec`, `sqlite`, `kuzu`, and `chroma` are local-first (use `path`). Backends `pinecone`, `qdrant`, `pgvector`, `neo4j`, `mongodb`, and `falkordb` are remote (require `connection`, V34).
658
710
 
659
711
  ##### `embedding` sub-block
@@ -665,40 +665,108 @@ agent researcher {
665
665
 
666
666
  ## Summary Table
667
667
 
668
+ All 92 rules, generated from `src/parser/validator.ts` so the spec cannot drift
669
+ from the implementation. Rules V8, V12, V23, V24, V89 and V90 are collected at
670
+ **parse time** — the misuse they catch leaves no trace in the AST — and surfaced
671
+ through the validator like any other rule.
672
+
673
+ `error` fails `agentopology validate` with exit 1. `warning` reports and exits 0.
674
+
668
675
  | Rule | Severity | Description |
669
676
  |------|----------|-------------|
670
- | V1 | error | Unique names — all agent, action, and gate names must be globally unique |
671
- | V2 | error | No keyword names — names cannot match reserved keywords |
672
- | V3 | error | Flow resolves — every flow reference must be a declared node |
673
- | V4 | error | No orphans — every agent must appear in flow or have `invocation: manual` |
674
- | V5 | error | Outputs exist — `[when x.y]` must reference a declared output |
675
- | V6 | error | Bounded loops — every back-edge must have `[max N]` |
676
- | V7 | error | Model required — every agent and orchestrator must have a model |
677
- | V8 | error | Imports resolve — import paths must point to existing files |
678
- | V9 | error | Actions handled — flow actions must appear in `orchestrator.handles` |
679
- | V10 | warning | Prompts exist — prompt blocks should not be empty |
680
- | V11 | error | Reads/writes consistent — data dependencies must match flow order |
681
- | V12 | error | Edge attribute order — must be `[when, max, per]` |
682
- | V13 | error | Gate placement — `after` and `before` must reference declared nodes |
683
- | V14 | error | Tool exclusivity — cannot have both `tools` and `disallowed-tools` |
684
- | V15 | error | Exhaustive conditions — conditional edges must cover all output values |
685
- | V16 | error | API key env vars — provider `api-key` must use `${ENV_VAR}` syntax |
686
- | V17 | error | Single default provider — at most one provider may be default |
687
- | V18 | warning | Model in provider — agent models should exist in a provider's list |
688
- | V19 | error | Unique provider names — no duplicate provider names |
689
- | V20 | error | Schedule job references — jobs must reference declared nodes; cron/every exclusive |
690
- | V21 | error | Interface secret detection — sensitive fields must use `${ENV_VAR}` syntax |
691
- | V22 | warning | Fallback chain models — fallback models should exist in a provider's list |
692
- | V23 | warning | Duplicate sections — singleton sections should appear at most once |
693
- | V24 | warning | Unknown memory sub-blocks — only known sub-blocks are expected |
694
- | V25 | warning | Bounce-back advisory — `on-fail: bounce-back` is advisory on CLI bindings |
695
- | V26 | error | Action kind enum — must be external, git, decision, inline, or report |
696
- | V27 | warning | Agent permissions enum — should be a recognized permission mode |
697
- | V28 | error | Metering format enum — must be json, jsonl, or csv |
698
- | V29 | warning | Metering pricing enum — should be a recognized pricing model |
699
- | V30 | error | Store backend enum — must be lancedb, sqlite-vec, chroma, kuzu, falkordb, mongodb, pinecone, qdrant, pgvector, neo4j, or sqlite |
700
- | V31 | warning | Embedding recommended — embedding should be present for semantic, episodic, and procedural stores |
701
- | V32 | warning | Store scope recommended — every store should have a scope field |
702
- | V33 | error | Retrieval sources resolve — retrieval sources must reference declared store IDs |
703
- | V34 | error | Connection required for remote backends — pinecone, qdrant, pgvector, neo4j, mongodb, falkordb require connection |
704
- | V35 | error | Agent memory/retrieval references — agent memory and retrieval fields must reference valid IDs |
677
+ | V1 | error | All agent, action, and gate names must be globally unique |
678
+ | V2 | error | No name may match a reserved keyword |
679
+ | V3 | error | Every node referenced in flow must be a declared agent, action, or gate |
680
+ | V4 | error | Every agent must appear in flow unless it has `invocation: manual` or is a group member |
681
+ | V5 | error/warning | Every `[when x.y]` must reference a declared output |
682
+ | V6 | error/warning | Every back-edge must have `max N` |
683
+ | V7 | error/warning | Every agent and orchestrator must have a model |
684
+ | V8 | error/warning | Import references should resolve (warning only -- we cannot check the filesystem) |
685
+ | V9 | error/warning | Every action referenced in flow must appear in orchestrator.handles |
686
+ | V10 | error/warning | Prompt content should not be empty if a prompt block is declared |
687
+ | V11 | error | If agent A writes X and agent B reads X, a path A -> B must exist in flow |
688
+ | V12 | error | Edge attribute order must be [when, max, per] |
689
+ | V13 | error | Gate `after` and `before` must reference declared nodes |
690
+ | V14 | error | `tools` and `disallowed-tools` cannot both appear on the same agent |
691
+ | V15 | error/warning | When a node has ONLY conditional outgoing edges, the conditions must |
692
+ | V16 | error/warning | `api-key` must be a `${...}` env-var reference (no literal secrets) |
693
+ | V17 | error/warning | At most one provider may have `default: true` |
694
+ | V18 | error/warning | Every model referenced by an agent should exist in at least one provider's models list |
695
+ | V19 | error/warning | Provider names must be unique |
696
+ | V20 | error/warning | Every schedule job must reference a declared agent or action; cron and every are mutually exclusive |
697
+ | V21 | error/warning | Interface webhook/auth values containing literal secrets (not `${ENV_VAR}`) should error |
698
+ | V22 | error/warning | Every model in a fallback-chain should exist in at least one provider's models list |
699
+ | V23 | error/warning | Duplicate top-level singleton sections |
700
+ | V24 | error/warning | Unknown sub-blocks in the `memory` section |
701
+ | V25 | error/warning | `on-fail: bounce-back` enforcement depends on the gate's `after` target |
702
+ | V26 | error/warning | `action.kind` must be one of the allowed values |
703
+ | V27 | error/warning | `agent.permissions` should be one of the known values |
704
+ | V28 | error/warning | `metering.format` must be one of the allowed values |
705
+ | V29 | error/warning | `metering.pricing` should be one of the known values |
706
+ | V30 | error | Validate `timeout` format is a valid duration string (matches /^\d+[smhd]$/) |
707
+ | V31 | error | Validate `on-fail` is one of: halt, retry, skip, continue, or starts with "fallback " |
708
+ | V32 | error | If `on-fail: fallback <id>`, validate that the referenced agent exists |
709
+ | V33 | error | Validate retry block fields (backoff enum, interval format, non-retryable is list) |
710
+ | V34 | error | Validate `temperature` is between 0 and 2 (on agents and defaults) |
711
+ | V35 | error | Validate `thinking` is one of: off, low, medium, high, max |
712
+ | V36 | error | Validate `output-format` is one of: text, json, json-schema |
713
+ | V37 | error | Validate `log-level` is one of: debug, info, warn, error |
714
+ | V38 | error | Validate `max-tokens` is a positive integer |
715
+ | V39 | error | Validate `join` is one of: all, any, all-done, none-failed, or N-of-M |
716
+ | V40 | error | Error edge target must be a declared node |
717
+ | V41 | error | `[race]` is only valid on fan-out edges (node has multiple outgoing edges) |
718
+ | V42 | error | `[tolerance]` format is valid (integer or percentage string matching /^\d+%?$/) |
719
+ | V43 | error | `[wait]` format is a valid duration string (reuse timeout validation pattern) |
720
+ | V44 | error | Topology-level `error-handler` must reference a declared node |
721
+ | V45 | error | Topology-level `timeout` must be a valid duration string |
722
+ | V46 | error | Schema type names must be valid (primitive, array of X, enum, or ref) |
723
+ | V47 | error | Schema `ref` names must resolve to a declared schema in the top-level `schemas` block |
724
+ | V48 | error/warning | Validate `observability.level` is one of: debug, info, warn, error |
725
+ | V49 | error/warning | Validate `observability.exporter` is one of: otlp, langsmith, datadog, stdout, none |
726
+ | V50 | error/warning | Validate `observability.sample-rate` is between 0 and 1 (inclusive) |
727
+ | V51 | error/warning | When `sensitive` is used with a literal string (not a `${...}` env var |
728
+ | V52 | error | Validate that secret URI schemes are one of the supported providers |
729
+ | V53 | error | Param type must be one of: `string`, `number`, `boolean` |
730
+ | V54 | error | Interface entry and exit must reference declared node ids |
731
+ | V55 | error | No two imports may share the same alias |
732
+ | V56 | error | Import source must be a syntactically valid path (starts with `./`, |
733
+ | V57 | error | Validate circuit-breaker fields — threshold must be a positive integer, |
734
+ | V58 | error/warning | `compensates` must reference a declared agent node |
735
+ | V59 | error | Human node `on-timeout` must be one of: halt, skip, or start with "fallback " |
736
+ | V60 | error/warning | When `join` uses quorum syntax `N-of-M`, validate: |
737
+ | V61 | error/warning | Validate `[weight N]` edge attributes: |
738
+ | V62 | error/warning | `[reflection]` is only valid on back-edges (edges that form cycles) |
739
+ | V63 | error | `members` in a group node must reference declared agent nodes |
740
+ | V64 | error | `speaker-selection` must be one of: auto, round-robin, random, manual |
741
+ | V65 | error | `max-rounds` must be a positive integer |
742
+ | V66 | error/warning | `rate-limit` must match N/unit where N >= 1 and unit is sec\|min\|hour\|day |
743
+ | V67 | error/warning | checkpoint `backend` must be one of the known values |
744
+ | V68 | error/warning | checkpoint `strategy` must be one of the known values, |
745
+ | V69 | error/warning | replay requires strategy "every-node"; max-history must be a positive |
746
+ | V70 | error | All artifact IDs in the artifacts block must be unique |
747
+ | V71 | error | depends-on, produces, and consumes must reference declared artifact IDs |
748
+ | V72 | error | Artifact dependency graph must be acyclic |
749
+ | V73 | error/warning | Registry package names must match `[a-z0-9-]+(/[a-z0-9-]+)*` |
750
+ | V74 | error/warning | Registry package version must be valid semver or "latest" |
751
+ | V75 | error/warning | If sha256 is present on an import, it must be a valid 64-char hex string |
752
+ | V76 | error/warning | Variant ids must be unique within each agent |
753
+ | V77 | error/warning | Variant weights must sum to approximately 1.0 (within 0.01 tolerance) |
754
+ | V78 | error/warning | `encrypted` values must match SOPS envelope format ENC[METHOD,data:BASE64] |
755
+ | V79 | error/warning | provider auth.type must be one of the known auth types |
756
+ | V80 | error/warning | OIDC and OAuth2 auth types require an issuer field |
757
+ | V81 | error/warning | StoreNode.backend must be one of the known values |
758
+ | V82 | error/warning | When StoreNode.type is "semantic", "episodic", or "procedural", |
759
+ | V83 | error/warning | Every store should have a scope defined. Warning level |
760
+ | V84 | error | Every source in RetrievalNode.sources must match a store ID in ast.stores |
761
+ | V85 | error | Remote backends (pinecone, neo4j, qdrant, pgvector) require a connection field |
762
+ | V86 | error | Every ID in AgentNode.memory must match a store ID in ast.stores |
763
+ | V87 | error/warning | `orchestrator.delegation` must be "subagent" or "inline" |
764
+ | V88 | error/warning | Every store in an agent's `custodian-of` must reference a declared store |
765
+ | V89 | error | `agent.prompt` must be a `prompt { }` block, not a `prompt: "path"` key-value pair (the string form belongs to `skill`) |
766
+ | V90 | error | A field value must not swallow the next field — fields are one per line (`spec/grammar.md` §2) |
767
+ | V91 | error | A required field must not be satisfied by a parser-injected placeholder — `meta.version` and `orchestrator.model` are required, and their sentinels (`0.0.0`, `unknown`) satisfied every downstream check |
768
+ | V92 | error | An edge's attributes go in ONE bracket, comma-separated — `[when ...] [max N]` produced a condition string containing brackets, silently |
769
+
770
+ > Sections above document the first 35 rules with worked examples. The rest are
771
+ > single-line entries here plus their doc comment in the source. Adding an
772
+ > example section for a rule is always welcome.