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.
- package/CLAUDE.md +68 -0
- package/NOW.md +170 -21
- package/README.md +226 -2
- package/demos/agent-workbench/README.md +156 -0
- package/demos/agent-workbench/agents.nornagent +87 -0
- package/demos/agent-workbench/contracts/domain-answer.schema.json +28 -0
- package/demos/agent-workbench/contracts/domain-question.schema.json +34 -0
- package/demos/agent-workbench/contracts/router-verdict.schema.json +43 -0
- package/demos/agent-workbench/contracts/ticket.schema.json +29 -0
- package/demos/agent-workbench/contracts/verdict.schema.json +48 -0
- package/demos/agent-workbench/fake-openai-server.js +128 -0
- package/demos/agent-workbench/fixtures/aligned-ticket.json +10 -0
- package/demos/agent-workbench/fixtures/cross-domain-ticket.json +10 -0
- package/demos/agent-workbench/fixtures/invalid-output-ticket.json +8 -0
- package/demos/agent-workbench/norn.config.json +15 -0
- package/demos/agent-workbench/prompts/ticket-router.md +11 -0
- package/demos/agent-workbench/workbench.norn +32 -0
- package/demos/mcp-ticket-testing/README.md +114 -0
- package/demos/mcp-ticket-testing/agents.nornagent +77 -0
- package/demos/mcp-ticket-testing/contracts/test-run.schema.json +31 -0
- package/demos/mcp-ticket-testing/expectations/proj-142.md +12 -0
- package/demos/mcp-ticket-testing/fixtures/proj-142.json +13 -0
- package/demos/mcp-ticket-testing/prompts/backend-tester.md +12 -0
- package/demos/mcp-ticket-testing/prompts/frontend-tester.md +14 -0
- package/demos/mcp-ticket-testing/prompts/reporter.md +10 -0
- package/demos/mcp-ticket-testing/servers/browser-server.js +133 -0
- package/demos/mcp-ticket-testing/servers/house-server.js +125 -0
- package/demos/mcp-ticket-testing/tickets.norn +32 -0
- package/dist/cli.js +185429 -111460
- package/package.json +71 -4
- package/playground/ai.norn +15 -0
- package/playground/ai_orchastration.nornagent +29 -0
- package/playground/knowedge_base/nexus_system_prompt.md +1 -0
- package/schemas/norn.config.schema.json +223 -0
- 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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
-
|
|
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
|
|