norn-cli 2.9.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CLAUDE.md +68 -0
  2. package/NOW.md +170 -21
  3. package/README.md +226 -2
  4. package/demos/agent-workbench/README.md +156 -0
  5. package/demos/agent-workbench/agents.nornagent +87 -0
  6. package/demos/agent-workbench/contracts/domain-answer.schema.json +28 -0
  7. package/demos/agent-workbench/contracts/domain-question.schema.json +34 -0
  8. package/demos/agent-workbench/contracts/router-verdict.schema.json +43 -0
  9. package/demos/agent-workbench/contracts/ticket.schema.json +29 -0
  10. package/demos/agent-workbench/contracts/verdict.schema.json +48 -0
  11. package/demos/agent-workbench/fake-openai-server.js +128 -0
  12. package/demos/agent-workbench/fixtures/aligned-ticket.json +10 -0
  13. package/demos/agent-workbench/fixtures/cross-domain-ticket.json +10 -0
  14. package/demos/agent-workbench/fixtures/invalid-output-ticket.json +8 -0
  15. package/demos/agent-workbench/norn.config.json +15 -0
  16. package/demos/agent-workbench/prompts/ticket-router.md +11 -0
  17. package/demos/agent-workbench/workbench.norn +32 -0
  18. package/demos/mcp-ticket-testing/README.md +114 -0
  19. package/demos/mcp-ticket-testing/agents.nornagent +77 -0
  20. package/demos/mcp-ticket-testing/contracts/test-run.schema.json +31 -0
  21. package/demos/mcp-ticket-testing/expectations/proj-142.md +12 -0
  22. package/demos/mcp-ticket-testing/fixtures/proj-142.json +13 -0
  23. package/demos/mcp-ticket-testing/prompts/backend-tester.md +12 -0
  24. package/demos/mcp-ticket-testing/prompts/frontend-tester.md +14 -0
  25. package/demos/mcp-ticket-testing/prompts/reporter.md +10 -0
  26. package/demos/mcp-ticket-testing/servers/browser-server.js +133 -0
  27. package/demos/mcp-ticket-testing/servers/house-server.js +125 -0
  28. package/demos/mcp-ticket-testing/tickets.norn +32 -0
  29. package/dist/cli.js +185429 -111460
  30. package/package.json +71 -4
  31. package/playground/ai.norn +15 -0
  32. package/playground/ai_orchastration.nornagent +29 -0
  33. package/playground/knowedge_base/nexus_system_prompt.md +1 -0
  34. package/schemas/norn.config.schema.json +223 -0
  35. package/CHANGELOG.md +0 -1506
package/CLAUDE.md ADDED
@@ -0,0 +1,68 @@
1
+ # Norn Extension — Claude Code instructions
2
+
3
+ ## Read these first
4
+
5
+ 1. **[`NOW.md`](NOW.md)** — the tool-agnostic active-work board shared by every agent and by
6
+ Peter. Whatever sits under **Active now** is the current priority. This board, not any
7
+ assistant's memory, is the source of truth for what we are working on.
8
+ 2. **[`AGENTS.md`](AGENTS.md)** — the full repo instructions (CLI parity requirement, how to
9
+ run the local CLI, code style, testing). Everything in that file applies here.
10
+
11
+ ## Skills: consult `.github/skills/` — always
12
+
13
+ This repo keeps its accumulated implementation knowledge in **[`.github/skills/`](.github/skills/)**,
14
+ maintained by every agent that works here. Treat it as required reading, not an optional
15
+ reference.
16
+
17
+ **Before touching any area of the codebase, check `.github/skills/` for a skill covering it
18
+ and read that skill.** Do this even for changes that look small — the skills exist precisely
19
+ because these areas have non-obvious rules that are easy to break.
20
+
21
+ Current skills and what they cover:
22
+
23
+ | Skill | Read it when working on |
24
+ |-------|------------------------|
25
+ | `norn-syntax-highlighting` | TextMate grammars in `syntaxes/`, token scopes, highlighting bugs |
26
+ | `norn-agent-workbench` | `.nornagent` sidecars, agent `run` steps, providers, contracts, record/replay, agent graph |
27
+ | `norn-intellisense-preferences` | Completion providers, IntelliSense behaviour |
28
+ | `norn-value-resolution` | Variables, interpolation, `.nornenv`, value precedence |
29
+ | `add-norn-keyword` | Adding a keyword to the `.norn` language |
30
+ | `add-assertion-operator` | Adding an assertion operator |
31
+ | `add-sequence-step-type` | Adding a new sequence step type |
32
+ | `add-imported-sidecar-language` | Adding a new imported sidecar file type |
33
+ | `norn-cli-integration` | `src/cli.ts` and CLI parity |
34
+ | `norn-debugger-integration` | DAP / debugger work |
35
+ | `norn-response-panel` | The response webview panel |
36
+ | `norn-test-explorer` | VS Code Test Explorer integration |
37
+ | `norn-sequence-tags` | Sequence tags and filtering |
38
+ | `norn-api-coverage` | `.nornapi` coverage features |
39
+ | `norn-mcp-tools` | MCP client and tool wiring |
40
+ | `norn-k8s` | Kubernetes features |
41
+ | `norn-terminal` | Norn Terminal / PTY work |
42
+ | `norn-env-templates` | `.nornenv` templates |
43
+ | `norn-publishing` | Packaging and publishing the extension |
44
+ | `norn-starter-catalog` | Starter templates catalog |
45
+ | `norn-title-cards` | Title cards |
46
+ | `demo-task-helper` | Building demos |
47
+ | `readme-sync` | Keeping README/docs in sync with features |
48
+
49
+ The table can go stale — list the directory when the work doesn't obviously map to a row.
50
+
51
+ ### Maintaining skills
52
+
53
+ Per `AGENTS.md`, skills are living documents:
54
+
55
+ - If a skill is **wrong or outdated**, fix it as part of the change.
56
+ - If a skill is **missing** for an area you just learned, write one in Agent Skills format.
57
+ - After creating or editing any skill, run `npm run validate:skills`.
58
+
59
+ Capture the lesson that was hard to learn, not a restatement of the code.
60
+
61
+ ## Verification
62
+
63
+ Never use `npx norn` — that runs the published package, not local changes:
64
+
65
+ ```bash
66
+ npm run compile
67
+ node ./dist/cli.js tests/file.norn --env prelive
68
+ ```
package/NOW.md CHANGED
@@ -25,34 +25,183 @@
25
25
 
26
26
  ## Active now
27
27
 
28
- _Accepted 2026-07-03. Two workstreams: build the paid direction, and market it._
29
-
30
- ### 1. 🔨 Contract & Drift BUILD (anchor)
31
- Turn Norn's existing `matchesSchema` into the job the market will pay for: **catch
32
- breaking API changes on the PR.** Do **Stage 1 → Stage 2 first** (cheap, pure reuse);
33
- Stage 3 (drift-as-PR-verdict) is the sellable milestone; Stage 4 is fenced off until a
34
- Stage-3 buyer pays.
35
- → Plan: [`Docs/contract_drift_build_plan.md`](Docs/contract_drift_build_plan.md) · Evidence: `Docs/market_signals.md` Signal 004
36
- **Next step:** build a thin Stage 1 slice, then Peter QA-dogfoods it.
37
-
38
- ### 2. 📣 Marketing & positioning — Voiden playbook (message + distribution together)
39
- Sharpen the pitch **and** get it into dev-native rooms — one workstream. Channel
40
- correction after the LinkedIn flop: copy Voiden's **distribution, not their business
41
- model**. Phased do → prove → gate: sharpen the drift hook → publish migration essays on
42
- **dev.to / Reddit / Show HN** (does content travel?) → comparison pages + directories →
43
- launch on the Stage-3 drift demo → community/open-core (earned, last).
44
- → Plan: [`Docs/marketing_plan.md`](Docs/marketing_plan.md) · Evidence: `Docs/market_signals.md` Signal 004 · [[norn-reddit-disclosed-selling]] · spine stays locked ([[norn-messaging-locked]])
45
- **Next step:** Phase 0 — write the one-paragraph "why Norn, now" hook (the field-rename-caught-on-the-PR story) + 3 headline angles.
28
+ _Nothing accepted right now._ The MCP-servers item finished 2026-08-12 see **Recently done**.
29
+ Slice 5f is the standing follow-on in the backlog below; it now has a spec and is ready for a
30
+ fresh session the moment Peter accepts it here.
46
31
 
47
32
  ---
48
33
 
49
34
  ## Backlog / accepted-but-not-started
50
35
 
51
- _(empty)_
36
+ ### 🧪 Agent Workbench — Slice 5f: eval fixtures, docs, positioning
37
+ **Postponed 2026-08-11; the MCP work it was waiting on finished 2026-08-12, and it now has a
38
+ spec.** Slice 5 is otherwise complete and reported across every surface (5a, 5c, 5e built; 5b and
39
+ 5d closed). 5f is smaller than its name suggests: the **website already documents `judge` well**,
40
+ and a deterministic judge server already exists as a test fixture — so what is left is promoting
41
+ that fixture to a demo anyone can run with **no API key** (one clearly-good case, one clearly-bad
42
+ one), a README section that catches up with the website, and the explanation of what evals are
43
+ for. **No `src/` change.** The spec leads with the constraint that matters: Norn's positioning is
44
+ locked, so "positioning" here means explaining evals *from* the spine, never a new pitch.
45
+ → **5f spec: [`Docs/to-be-built/agent-workbench-slice-5f.md`](Docs/to-be-built/agent-workbench-slice-5f.md)**
46
+ → Slice 5 spec: [`Docs/to-be-built/agent-workbench-slice-5.md`](Docs/to-be-built/agent-workbench-slice-5.md)
47
+ → Built: [`5a`](Docs/to-be-built/agent-workbench-slice-5a.md) ·
48
+ [`5c`](Docs/to-be-built/agent-workbench-slice-5c.md) ·
49
+ [`5e`](Docs/to-be-built/agent-workbench-slice-5e.md)
50
+
51
+ ## Parked
52
+
53
+ ### Contract & Drift + Voiden-playbook marketing
54
+ The previous direction, parked 2026-08-07 — untested, not failed. Stages 1+2 are built and
55
+ shipped in 2.9.8; the QA dogfood and the "why Norn, now" hook never happened. Full
56
+ narrative and how to resume:
57
+ [`Docs/random/parked_contract_drift_and_marketing.md`](Docs/random/parked_contract_drift_and_marketing.md)
52
58
 
53
59
  ## Recently done
54
60
 
55
- _(empty)_
61
+ - **3.1.0 — MCP authoring parity, and the changelog is gone** (2026-08-13) — **done.**
62
+ Three fixes that all came out of one painful hour authoring a real server by hand. **`header`
63
+ now reads like HTTP**: `header Name: value` is accepted alongside the original no-colon form,
64
+ because the colon form is what a `.norn` request writes and what everyone arrives with —
65
+ rejecting it silently produced a header *named* `Authorization:` and the only complaint came
66
+ from `Headers.append` at dial time, three layers from the line at fault. The name is now
67
+ validated against the RFC 9110 token set at parse time, so an unsendable one is red in the
68
+ editor. The header name and value carry `.norn`'s own `entity.name.tag.http` /
69
+ `string.unquoted.http` scopes, so the identical header is the identical colour in both files,
70
+ and the quoted-string pattern is gone from that rule — it advertised an escape that does not
71
+ exist, and quotes really do end up inside the token. IntelliSense offers the shared
72
+ `commonHeaders` list after `header `, inserting the colon form and chaining into the
73
+ `Bearer {{$env.…}}` value completion.
74
+ **`run mcp` completions now see sidecar-declared servers.** The precedence rule lived in three
75
+ copies and the completion copy was left on config-only, so a project that declared every server
76
+ in its sidecar offered no aliases at all while running perfectly. It is now one shared module,
77
+ `src/mcpAliasScope.ts`, and the alias carries its origin — which matters because the tools cache
78
+ is written beside whatever *declared* the server, so tool-name completions and signature help
79
+ were reading the wrong `.norn-cache` even once an alias resolved. **Config `mcp.servers` stays**:
80
+ supporting both is one function and one line per caller, so retiring it would have been strictly
81
+ more work than keeping it.
82
+ **`CHANGELOG.md` deleted** at Peter's call — the release record is git history plus this board,
83
+ and a changelog was a third place saying the same thing and the likeliest to drift.
84
+ Also fixed: `@vscode/test-electron` 2.5.2 could not launch VS Code 1.133 (the binary was renamed
85
+ from `Electron` to `Code`), which had left the Extension Host suite dark.
86
+ - **Agent Workbench — declared MCP servers** (2026-08-12) — **done, all five items.**
87
+ Agents can now *do* things. A `.nornagent` sidecar declares the servers it uses beside the
88
+ models it uses — `mcp <Alias> … end mcp`, stdio and http, credentials as `.nornenv`
89
+ references — and each agent is granted a whole server with `mcp <Alias>` or named tools with
90
+ `tools <Alias>.<tool>`. The two stay visibly different statements on purpose: a granted tool
91
+ executes when the model asks, so the authored grant *is* the permission boundary and dropping
92
+ four characters must not widen it from one tool to twenty. `session run | agent | call` makes
93
+ shared server state a decision rather than a surprise; `run` stays the default because five
94
+ agents testing one website are one testing session, and `session agent` is how an agent asks
95
+ not to inherit a login it never earned. Config `mcp.servers` keeps working with the sidecar
96
+ winning, `run mcp` steps reach declared servers so a server is provable with no model and no
97
+ cost, and a granted server is a real node on the canvas — **declared servers only**, per
98
+ Peter: the graph draws the program the file describes.
99
+ Four corrections the build forced on the spec, all recorded in it: grant expansion **cannot**
100
+ live above the runner (pure replay never runs that preflight, and a derived root definition
101
+ never reaches sub-agents), so it is an injected hook with a live and a replay implementation;
102
+ drift between record and replay is only knowable where a live listing happens, so the
103
+ acceptance criterion demanding both was impossible as written; preflight needed a session
104
+ boundary of its own, or `session agent` was already broken by the check that proves an agent's
105
+ tools exist; and the spec's own demo sketch used an input expression the language does not
106
+ have. Fixed along the way: a templated URL resolving differently mid-run silently reused the
107
+ old session, and a refused connection reported the SDK's "fetch failed" instead of naming what
108
+ it dialled. 60 focused tests across five items; 256 pass across every runnable suite, plus
109
+ type-check, lint, bundles, skill validation, and regression at 198/200 (two live-SQL-Server
110
+ sequences needing a database).
111
+ → Spec: [`Docs/to-be-built/agent-workbench-mcp-servers.md`](Docs/to-be-built/agent-workbench-mcp-servers.md)
112
+ · Demo: [`demos/mcp-ticket-testing`](demos/mcp-ticket-testing)
113
+ - **Agent Workbench Slice 5e — reporting** (2026-08-10) — **done.**
114
+ A verdict is now visible in the response panel, the canvas, the debugger, and JUnit, derived
115
+ once through `agentHopPresentation` so the surfaces cannot disagree. The load-bearing move was
116
+ putting the verdict on `AgentHopResult` rather than the step: a recording persists the trace
117
+ and never step results, so a step-only verdict vanished the moment a past run was reopened —
118
+ the round-trip is now a test. Two bugs the build surfaced: a judged hop has `success === true`
119
+ even when the verdict failed, so the canvas drew a **green node for a failed run** until the
120
+ verdict was counted explicitly; and the runner emits its `completed` event before the verdict
121
+ exists, so the deep-cloned event log needed patching or replay playback showed no verdict.
122
+ Also: judge statements now draw a sequence handoff edge from whatever produced the value they
123
+ judge, live progress is emitted as `stepType: 'judge'`, and JUnit gets one testcase per
124
+ statement with `ExpectationsUnmet` and `JudgeError` kept distinct. 13 new focused tests; 159
125
+ pass across every runnable suite, plus type-check, lint, bundles, regression, skill validation,
126
+ and a real CLI run producing JUnit.
127
+ → [`Docs/to-be-built/agent-workbench-slice-5e.md`](Docs/to-be-built/agent-workbench-slice-5e.md)
128
+ - **Agent Workbench Slice 5c — judge execution and the result contract** (2026-08-10) — **done,
129
+ one deliverable deferred.** A `judge` statement now runs its judge and produces a verdict.
130
+ The design that carries it: **the model is never asked for an overall result** — it rules on
131
+ each expectation and quotes evidence, and Norn computes `passed`, checks one ruling per
132
+ expectation with indexes in range, and verifies each quote occurs in the judged text. A judge
133
+ is an ordinary agent invocation; everything judge-specific lives in a derived definition, so
134
+ there is no judge runtime and no `if (isJudge)` in the runner. `builtInContracts` generalises
135
+ "a contract that lives in code" and rides the existing generation/validation path.
136
+ A provider or contract failure sets an error and no verdict, so "the judge said no" never
137
+ reads like "the judge never answered". **A failed judge stops the sequence**, the same as a
138
+ failed `assert` — it is the concluding check on an orchestration, so later steps must not run
139
+ on a disproved premise; the run is still recorded and replayable. A malformed reply *shape* is
140
+ corrected up to `contract_retries` via the opt-in `retryReturnsContract` (off for `run
141
+ <Agent>`); the verdict itself is never re-rolled. Evidence that cannot be found in the judged
142
+ text is flagged and still passes — strictness is intended to become per-expectation, not
143
+ global. **Deferred:** the automatic replay drift check —
144
+ `expectationsHash` is recorded and diffable, but pure replay never re-reads the `.norn`, so
145
+ the comparison needs `runLineNumber` plumbing and lands with 5d, which owns the policy.
146
+ Fixed along the way: judge hops reported false prompt drift on every replay. 18 new focused
147
+ tests; 141 pass across every runnable suite, plus type-check, lint, bundles, regression, skill
148
+ validation, and a real CLI run against a deterministic judge server.
149
+ → [`Docs/to-be-built/agent-workbench-slice-5c.md`](Docs/to-be-built/agent-workbench-slice-5c.md)
150
+ - **Agent Workbench Slice 5a — eval authoring** (2026-08-10) — **done.**
151
+ `judge <subject> with <Agent> expects "…"` in `.norn` sequences — **one line, like `assert`**,
152
+ with `expects file <path>` for a list. Built as a `judge … end judge` block first and reworked
153
+ on Peter's correction: the sequence is the orchestrator, so it stays a flat list of steps
154
+ rather than nesting a mini-language. Expectations are ordinary Norn strings, file lists resolve
155
+ at parse time with per-expectation origins, plus eleven `judge-*` diagnostics, grammar,
156
+ completions, hover, and CLI/JSON step output. Driven by Peter's answer that the first eval is a
157
+ checklist — all expectations must be met, exceeding them is not a failure. Reuse held:
158
+ expectation strings go through `agentPromptBlock.ts`'s existing escaping rather than a second
159
+ stack, one shared module serves runner/diagnostics/completions/hover, and statements validate
160
+ in the existing agent preflight so a malformed eval cannot spend a model call. A judge step
161
+ reports as explicitly *not judged* until 5c rather than silently passing. 15 new focused tests;
162
+ 123 pass across every runnable suite, plus type-check, lint, bundles, regression, and skill
163
+ validation.
164
+ → [`Docs/to-be-built/agent-workbench-slice-5a.md`](Docs/to-be-built/agent-workbench-slice-5a.md)
165
+ - **Agent Workbench Slice 4** (2026-08-09) — **done and reviewed.**
166
+ Default-on, secret-safe recordings under `.norn-cache/runs`; fixed 20-run retention;
167
+ byte-identical offline replay with zero provider/MCP side effects; strict CLI replay with JSON,
168
+ environment restoration, graph-drift failure, and live `--from`; agent frames/scopes,
169
+ breakpoints, stepping, and contract-failure navigation in the existing DAP; paused-hop canvas
170
+ highlighting and validated node-to-invocation navigation. Review: replay is a clean
171
+ `AgentProviderAdapter` with no runtime special-casing; the debugger is the existing DAP session
172
+ extended, not a second one; secrets are stable placeholders re-substituted from `.nornenv` at
173
+ replay time behind a preflight that fails naming the missing variables; and the debugger became
174
+ the third surface consuming the shared `agentHopPresentation` helper. 55 focused tests pass
175
+ across all agent suites, plus type-check, lint, and bundle build.
176
+ → [`Docs/to-be-built/agent-workbench-slice-4.md`](Docs/to-be-built/agent-workbench-slice-4.md)
177
+ - **Agent Workbench Slice 3** (2026-08-09) — **done and reviewed.**
178
+ Read-only VS Code agent canvas with parser-first static graphs, redacted live/completed trace
179
+ overlays, deterministic cycle-safe layout, first-class contract states, hover/copy payloads,
180
+ source navigation, command and CodeLens entry points, and shared response-panel presentation.
181
+ Recovery resolves successful correction invocations rather than trusting a flag. Review: the
182
+ shared `agentHopPresentation` helper is consumed by both the canvas and the response panel so
183
+ they cannot drift; the trace is redacted before anything reads it; CSP is `default-src 'none'`
184
+ with nonce'd scripts and no external hosts; no graph library was added; the panel never writes.
185
+ 43 focused tests pass across all agent suites, plus type-check, lint, and bundle build.
186
+ → [`Docs/to-be-built/agent-workbench-slice-3.md`](Docs/to-be-built/agent-workbench-slice-3.md)
187
+ - **Agent Workbench Slice 2** (2026-08-08) — **done and reviewed.**
188
+ Model-directed agent-as-tool delegation, two-way boundary correction, subtree conversations,
189
+ configurable spend guards, recursive CLI/JSON/response-panel traces, language tooling, docs,
190
+ and a deterministic violate → correct → pass demo. Review: sub-agents go through the same tool
191
+ path as MCP tools, config precedence is agent → provider → global → built-in, parallel tool use
192
+ is disabled on both adapters, and the runner was extracted to `src/agents/agentRunner.ts` rather
193
+ than growing `sequenceRunner`. 32 focused tests pass; the language suite needs the VS Code host.
194
+ `max_tokens` default stays 16000 — a real OpenAI model rejected 32000 and reported a 16384
195
+ cap, so the generous default was walked back; raise it per-provider where a model allows it.
196
+ → [`Docs/to-be-built/agent-workbench-slice-2.md`](Docs/to-be-built/agent-workbench-slice-2.md)
197
+ - **Agent Workbench Slice 1** (2026-08-08) — **done and verified running by Peter.**
198
+ `.nornagent` definitions, linear agent `run` handoffs, OpenAI/local/Anthropic/Google adapters,
199
+ MCP tools, automatic boundary contracts, live CLI + JSON traces, language tooling, focused
200
+ tests, and a runnable demo. Reviewed: reuse held (contracts go through `matchesSchema` and the
201
+ existing Contract View path), provider interface held (Google landed later as a pure new file).
202
+ → [`Docs/to-be-built/agent-workbench-slice-1.md`](Docs/to-be-built/agent-workbench-slice-1.md)
203
+ - **Website k8s de-promotion** (2026-08-07) — promo surfaces stripped and deployed; docs
204
+ and functionality untouched. Was Phase 0 of the marketing plan.
56
205
 
57
206
  ---
58
- _Last updated: 2026-07-03_
207
+ _Last updated: 2026-08-12_
package/README.md CHANGED
@@ -27,6 +27,7 @@ That means you can:
27
27
  - `.nornenv` files for environments and secrets
28
28
  - `.nornapi` files for reusable endpoint definitions
29
29
  - `.nornsql` files for database queries and commands
30
+ - `.nornagent` files for contract-checked AI agents and model-directed delegation
30
31
  - syntax highlighting, IntelliSense, and diagnostics
31
32
  - response inspection, JSON diffing, and click-to-generate assertions
32
33
  - tagged and parameterized test execution in VS Code and the CLI
@@ -106,7 +107,14 @@ Every `{{...}}` reference in any Norn file shows its resolved value as a gray in
106
107
 
107
108
  ## Deterministic MCP Tools
108
109
 
109
- Norn can call MCP tools from sequences without leaving the `.norn` runtime. MCP sessions are deterministic and shared across the full sequence run, so nested sequences reuse the same connection for the same resolved server alias.
110
+ Norn can call MCP tools from sequences without leaving the `.norn` runtime. MCP sessions are deterministic and shared across the full sequence run by default, so nested sequences reuse the same connection for the same resolved server alias.
111
+
112
+ A server can be declared in two places, and an alias resolves through them in this order:
113
+
114
+ 1. an `mcp <Alias> ... end mcp` block in a `.nornagent` sidecar the file imports — see
115
+ [Contract-Checked Agent Graphs](#contract-checked-agent-graphs), and prefer this when agents
116
+ use the server too, because the file then says what it connects to;
117
+ 2. `mcp.servers` in the nearest `norn.config.json`.
110
118
 
111
119
  Create a `norn.config.json` in the root of your project:
112
120
 
@@ -149,7 +157,223 @@ Behavior:
149
157
  - `run mcp list <alias>` returns the full tool list and drains paginated `nextCursor` responses automatically.
150
158
  - `run mcp call <alias> <tool>(...)` supports named arguments or positional arguments bound in tool-schema order, and returns a deterministic result envelope with `content`, `structuredContent`, `isError`, `text`, `server`, and `tool`.
151
159
  - Tool `structuredContent` is validated against the MCP tool's advertised `outputSchema` when present.
152
- - Sessions are closed automatically when the outermost sequence finishes or fails.
160
+ - A run-scoped session is closed automatically when the outermost sequence finishes or fails. A
161
+ sidecar-declared server can narrow that with `session agent` or `session call`, which close at
162
+ their own boundary instead.
163
+ - An imported `.norn` helper sequence resolves aliases through **its own** imports, not its
164
+ caller's — the same rule agent scopes follow.
165
+
166
+ ## Contract-Checked Agent Graphs
167
+
168
+ Define agents in an imported `.nornagent` sidecar. An agent can grant another
169
+ agent through `agents`; the callee then appears to the calling model as an
170
+ ordinary tool. Its `accepts` schema constrains and validates the generated tool
171
+ input, and its `returns` schema validates the result before it travels back up
172
+ the graph.
173
+
174
+ A `model` block declares the provider, the provider's model, and where the API key
175
+ comes from. Nothing is implied by convention: `apiKey` points at a `.nornenv`
176
+ variable, so the file says exactly which value it needs and the secret itself never
177
+ leaves the environment.
178
+
179
+ ```nornagent
180
+ model Workbench
181
+ provider openai # openai, anthropic, google, or local
182
+ name gpt-4o
183
+ apiKey {{$env.OPENAI_API_KEY}}
184
+ # baseUrl {{$env.OPENAI_BASE_URL}} # optional; required for local
185
+ end model
186
+
187
+ agent DomainExpert
188
+ model Workbench
189
+ describe "Call for domain questions and include the ticket context."
190
+ accepts contracts/domain-question.schema.json
191
+ returns contracts/domain-answer.schema.json
192
+ system "Answer only the supplied domain question."
193
+ end agent
194
+
195
+ agent TicketRouter
196
+ model Workbench
197
+ agents DomainExpert
198
+ system "Consult the domain expert when the ticket needs it."
199
+ end agent
200
+ ```
201
+
202
+ ### Giving agents hands: declared MCP servers
203
+
204
+ An agent reaches the outside world through MCP servers, and those are declared in the same
205
+ file, the same way — every input the server needs written down, credentials as `.nornenv`
206
+ references:
207
+
208
+ ```nornagent
209
+ mcp Browser
210
+ transport stdio
211
+ command npx playwright-mcp --headless --isolated
212
+ session agent # run (default) | agent | call
213
+ end mcp
214
+
215
+ mcp House
216
+ transport http
217
+ url {{$env.HOUSE_MCP_URL}} # the MCP endpoint, path included
218
+ header Authorization: Bearer {{$env.HOUSE_TOKEN}}
219
+ timeout 60000
220
+ end mcp
221
+
222
+ agent FrontendTester
223
+ model Workbench
224
+ mcp Browser # every tool this server advertises
225
+ tools House.getFixtureUser, House.resetTenant # only these, from this one
226
+ system file prompts/frontend-tester.md
227
+ end agent
228
+ ```
229
+
230
+ - **`transport stdio` spawns the server per run; `transport http` only dials one.** Norn never
231
+ starts an http server, so it must already be listening — a refused connection fails before
232
+ any model spend, naming the URL it tried.
233
+ - **`mcp <Alias>` and `tools <Alias>.<tool>` are deliberately different statements.** A granted
234
+ tool executes when the model asks for it, so the authored grant *is* the permission boundary:
235
+ dropping four characters must not silently widen access from one tool to twenty.
236
+ - **`header` writes an HTTP header the way you would write it anywhere else.** `header Name:
237
+ value`, and the value is the rest of the line taken verbatim — so `Bearer {{token}}` needs no
238
+ quotes, and quotes you add become part of the token. The colon is optional but recommended:
239
+ it is what the request side of Norn uses, and IntelliSense inserts it for you.
240
+ - **`session` decides what state is shared.** The default, `run`, gives every agent in a
241
+ sequence one session — deliberate for an orchestration, and a trap when a backend agent
242
+ inherits a frontend agent's login and passes a test that should have failed. `session agent`
243
+ is how an agent asks for its own.
244
+ - An alias with no block still resolves through `norn.config.json` `mcp.servers`, and a block
245
+ in the sidecar wins over a config entry of the same name.
246
+
247
+ Deterministic `run mcp` steps resolve the same aliases, so a server can be proved reachable
248
+ with no model and no cost:
249
+
250
+ ```norn
251
+ import "./agents.nornagent"
252
+
253
+ test sequence ServersUp
254
+ var tools = run mcp list House
255
+ assert tools.length > 0
256
+ end sequence
257
+ ```
258
+
259
+ The runnable [`demos/mcp-ticket-testing`](./demos/mcp-ticket-testing) example is the whole
260
+ shape end to end: a browser agent, a backend agent narrowed on the same custom server, a
261
+ reporter, and a `judge` over the expected test cases.
262
+
263
+ A prompt that outgrows its sidecar can live in its own file instead. `describe`
264
+ and `system` both accept `file <path>`, resolved relative to the `.nornagent`
265
+ file just like an import:
266
+
267
+ ```nornagent
268
+ agent TicketRouter
269
+ model Workbench
270
+ agents DomainExpert
271
+ system file prompts/ticket-router.md
272
+ end agent
273
+ ```
274
+
275
+ The file's text is the prompt verbatim — no escaping, so quotes and backslashes
276
+ stay as written — and `{{...}}` references in it resolve exactly as they do
277
+ inline. A missing or empty prompt file is a parse error on the directive line.
278
+
279
+ Run the graph from an ordinary sequence; the same nested contract and retry
280
+ trace appears in VS Code and the CLI:
281
+
282
+ ```norn
283
+ import "./agents.nornagent"
284
+
285
+ test sequence RouteTicket
286
+ var ticket = run readJson "./ticket.json"
287
+ var verdict = run TicketRouter ticket
288
+ assert verdict.text exists
289
+ end sequence
290
+ ```
291
+
292
+ Malformed handoffs are returned to the calling model as field-level tool errors
293
+ so it can correct them. The default guardrails are depth 5, 25 total agent
294
+ invocations, and a two-failed-attempt contract cap. Configure them globally,
295
+ per provider, or on an individual agent (most specific wins):
296
+
297
+ ```json
298
+ {
299
+ "version": 1,
300
+ "agents": {
301
+ "max_tokens": 16000,
302
+ "max_depth": 5,
303
+ "max_invocations": 25,
304
+ "contract_retries": 2,
305
+ "recording": { "enabled": true },
306
+ "providers": {
307
+ "local": { "max_tokens": 4096 }
308
+ }
309
+ }
310
+ }
311
+ ```
312
+
313
+ `max_tokens` is an output ceiling, not prepaid usage: raising it does not spend
314
+ tokens by itself. Depth, invocation, and retry limits can permit additional
315
+ model calls, so tighten those three when controlling run cost. See the runnable
316
+ [`demos/agent-workbench`](./demos/agent-workbench) example for delegation and a
317
+ visible contract correction.
318
+
319
+ Every sequence run that reaches an agent is recorded under
320
+ `.norn-cache/runs/` by default, with a rolling cap of 20 files. Recordings hold
321
+ the resolved provider request, response, tool transcript, contracts,
322
+ conversation state, and ordered hop events. Values declared as secrets in
323
+ `.nornenv` are written as stable named placeholders and restored from the
324
+ selected environment when replay starts; a missing value stops replay before a
325
+ provider or tool can run. Set `agents.recording.enabled` to `false` to disable
326
+ automatic recording.
327
+
328
+ Replay a recording without model calls, MCP calls, or other sequence side
329
+ effects using the local CLI:
330
+
331
+ ```bash
332
+ node ./dist/cli.js replay .norn-cache/runs/<recording>.json
333
+ node ./dist/cli.js replay .norn-cache/runs/<recording>.json --json
334
+ node ./dist/cli.js replay .norn-cache/runs/<recording>.json --from CriteriaComparer
335
+ ```
336
+
337
+ Pure replay reproduces the stored trace and exits non-zero for failed hops or
338
+ contracts, so a deliberately copied recording can be used as a no-key CI
339
+ fixture. `--from` accepts a unique agent name or canonical path such as
340
+ `TicketRouter[1]/CriteriaComparer[1]`; the prefix stays replayed and that hop
341
+ onward runs live against the current graph.
342
+
343
+ **Norn: Show Agent Graph**, or the CodeLens on any agent block, draws the graph
344
+ for a `.nornagent` file: who calls whom, the `accepts`/`returns` contract on each
345
+ boundary, and how each one turned out. Hover an agent or a boundary for the full
346
+ detail — models, timings, usage, prompts, payloads, contract issues.
347
+
348
+ The graph's toolbar also lists the project's recorded runs, each labelled with the
349
+ `.norn` file it came from. Pick one to see how it finished, or press **Play run**
350
+ to watch it unfold hop by hop on the canvas. Playback replays the recording's own
351
+ events, so nothing re-executes and no model is called; press **Stop** to jump to
352
+ the end. A run from a file the open one took no part in is drawn from that
353
+ recording alone — the toolbar says which file it came from, and **This file —
354
+ current view** returns you to the open file, ready for its next live run.
355
+ Recording is on by default and keeps the last 20 runs under `.norn-cache/runs/`.
356
+
357
+ To step a recording in VS Code, use the existing `norn` debugger and add the
358
+ artifact to a launch configuration:
359
+
360
+ ```json
361
+ {
362
+ "type": "norn",
363
+ "request": "launch",
364
+ "name": "Replay RouteTicket",
365
+ "file": "${workspaceFolder}/route-ticket.norn",
366
+ "sequence": "RouteTicket",
367
+ "recording": "${workspaceFolder}/fixtures/route-ticket-run.json",
368
+ "stopOnEntry": true
369
+ }
370
+ ```
371
+
372
+ Agent calls appear as nested frames with Request, Response, Contracts, and
373
+ Conversation scopes. Normal step controls navigate the recorded hop timeline;
374
+ **Norn Debug: Run to Next Contract Failure** stops on retry violations as well
375
+ as final contract failures. With Agent Graph open, the paused invocation is
376
+ highlighted and selecting a node moves the replay cursor to that invocation.
153
377
 
154
378
  ## Good Fit For
155
379