@vimhead.dev/norn-cli 0.1.0-tip.35242809332.1 → 0.1.0-tip.35347037721.1

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 (88) hide show
  1. package/assets/README.md +2 -24
  2. package/assets/docs/README.md +3 -1
  3. package/assets/docs/agents.md +3 -3
  4. package/assets/docs/cli.md +7 -7
  5. package/assets/docs/composition.md +64 -34
  6. package/assets/docs/persistence.md +4 -2
  7. package/assets/docs/projects.md +3 -12
  8. package/assets/{setup → docs}/providers.md +4 -3
  9. package/assets/docs/resources.md +7 -7
  10. package/assets/docs/schemas.md +130 -0
  11. package/assets/docs/workflows.md +4 -3
  12. package/assets/examples/agent-then-analysis/README.md +1 -1
  13. package/assets/examples/agent-then-analysis/plugin.ts +16 -15
  14. package/assets/examples/caller-selected-continuation/README.md +75 -0
  15. package/assets/examples/caller-selected-continuation/caller.ts +47 -0
  16. package/assets/examples/caller-selected-continuation/input.json +9 -0
  17. package/assets/examples/caller-selected-continuation/norn.project.json +4 -0
  18. package/assets/examples/caller-selected-continuation/producer.ts +32 -0
  19. package/assets/examples/coordinating-multiple-agents/README.md +1 -1
  20. package/assets/examples/coordinating-multiple-agents/plugin.ts +10 -11
  21. package/assets/examples/coordinating-multiple-agents/queue-adapter.ts +3 -4
  22. package/assets/examples/coordinating-multiple-agents/work-queue.ts +18 -18
  23. package/assets/examples/minimal-workflow/plugin.ts +2 -2
  24. package/assets/examples/shared-state/README.md +1 -1
  25. package/assets/examples/shared-state/plugin.ts +6 -6
  26. package/assets/examples/worktree-development-loop/README.md +105 -44
  27. package/assets/examples/worktree-development-loop/norn.project.json +1 -1
  28. package/assets/examples/worktree-development-loop/state.ts +6 -6
  29. package/assets/examples/worktree-development-loop/workflows/development-loop/execute.ts +1 -1
  30. package/assets/examples/worktree-development-loop/workflows/development-loop/schema.ts +9 -9
  31. package/assets/examples/worktree-development-loop/workflows/implementation/execute.ts +1 -1
  32. package/assets/examples/worktree-development-loop/workflows/implementation/schema.ts +7 -7
  33. package/assets/examples/worktree-development-loop/workflows/planning/execute.ts +1 -1
  34. package/assets/examples/worktree-development-loop/workflows/planning/schema.ts +7 -7
  35. package/assets/examples/worktree-development-loop/workflows/review/execute.ts +1 -1
  36. package/assets/examples/worktree-development-loop/workflows/review/schema.ts +11 -11
  37. package/assets/examples/worktree-development-loop/workflows/review-router/execute.ts +1 -1
  38. package/assets/examples/worktree-development-loop/workflows/review-router/schema.ts +5 -5
  39. package/assets/package.json +1 -1
  40. package/assets/packages/cli/src/cli.ts +15 -32
  41. package/assets/packages/cli/src/client.ts +0 -7
  42. package/assets/packages/cli/src/generated-build-info.ts +2 -2
  43. package/assets/packages/cli/src/internal/agent-response-tool.ts +9 -10
  44. package/assets/packages/cli/src/internal/agents.ts +17 -16
  45. package/assets/packages/cli/src/internal/engine.ts +20 -18
  46. package/assets/packages/cli/src/internal/errors.ts +4 -10
  47. package/assets/packages/cli/src/internal/run-state.ts +4 -0
  48. package/assets/packages/cli/src/internal/run.ts +4 -7
  49. package/assets/packages/cli/src/internal/state-store.ts +22 -14
  50. package/assets/packages/cli/src/internal/workflow-registry.ts +27 -21
  51. package/assets/packages/cli/src/plugin-loader.ts +33 -45
  52. package/assets/packages/cli/src/resources.ts +14 -12
  53. package/assets/packages/core/src/workflow-transition.ts +4 -0
  54. package/assets/packages/sdk/src/api.ts +87 -131
  55. package/assets/packages/sdk/src/files.ts +11 -10
  56. package/assets/packages/sdk/src/index.ts +0 -1
  57. package/assets/packages/sdk/src/resources.ts +2 -2
  58. package/assets/packages/sdk/src/schema.ts +53 -29
  59. package/assets/packages/sdk/src/state-adapter.ts +4 -4
  60. package/assets/tests/workflow-ref.test.ts +121 -77
  61. package/dist/cli.js +20 -36
  62. package/dist/client.d.ts +0 -4
  63. package/dist/client.js +0 -3
  64. package/dist/generated-build-info.d.ts +2 -2
  65. package/dist/generated-build-info.js +2 -2
  66. package/dist/internal/agent-response-tool.d.ts +2 -2
  67. package/dist/internal/agent-response-tool.js +7 -7
  68. package/dist/internal/agents.d.ts +2 -2
  69. package/dist/internal/agents.js +10 -10
  70. package/dist/internal/engine.d.ts +1 -1
  71. package/dist/internal/engine.js +14 -12
  72. package/dist/internal/errors.d.ts +2 -1
  73. package/dist/internal/errors.js +5 -8
  74. package/dist/internal/run-state.js +4 -0
  75. package/dist/internal/run.d.ts +2 -2
  76. package/dist/internal/run.js +10 -6
  77. package/dist/internal/state-store.d.ts +8 -7
  78. package/dist/internal/state-store.js +15 -9
  79. package/dist/internal/workflow-registry.d.ts +14 -6
  80. package/dist/internal/workflow-registry.js +20 -15
  81. package/dist/plugin-loader.d.ts +15 -21
  82. package/dist/plugin-loader.js +35 -47
  83. package/dist/resources.d.ts +1 -1
  84. package/dist/resources.js +15 -13
  85. package/package.json +3 -4
  86. package/assets/packages/sdk/src/seer/config.ts +0 -62
  87. package/assets/packages/sdk/src/seer/index.ts +0 -7
  88. package/assets/setup/releases.md +0 -77
package/assets/README.md CHANGED
@@ -63,7 +63,7 @@ Inside the interactive session:
63
63
 
64
64
  Model-free workflows need no provider authentication. For other credential
65
65
  methods, custom providers, and Norn's configuration directory, see
66
- [providers and authentication](setup/providers.md).
66
+ [providers and authentication](docs/providers.md).
67
67
 
68
68
  ### 3. Optionally connect your harness
69
69
 
@@ -104,11 +104,6 @@ NORN_EXECUTABLE=/absolute/path/to/norn cursor .
104
104
  Need an adapter for another harness? [Open an issue](https://github.com/vimhead/norn/issues/new)
105
105
  with the harness name.
106
106
 
107
- ## Setting up a Norn project
108
-
109
- [Projects and loading](docs/projects.md) covers initialization, registration,
110
- reusable configuration, dependencies, source reload, and discovery diagnostics.
111
-
112
107
  ## Build workflows with the Norn SDK
113
108
 
114
109
  Install SDK types and helpers for TypeScript/editor support:
@@ -121,22 +116,6 @@ Use the SDK version reported by `norn version` for an exact runtime match. Runti
121
116
  execution also supplies [virtual SDK imports](docs/projects.md#import-and-reload),
122
117
  so standalone examples need no local SDK installation.
123
118
 
124
- [Workflow authoring](docs/workflows.md) covers declarations, implementations,
125
- commands, and run outcomes. Focused companion references:
126
-
127
- - [Norn agents](docs/agents.md)
128
- - [State, artifacts, and workspaces](docs/persistence.md)
129
- - [Composition and reuse](docs/composition.md)
130
- - [Recovery and gates](docs/recovery.md)
131
-
132
- The larger [worktree development loop](examples/worktree-development-loop/README.md)
133
- is an optional composition example, not a required workflow architecture.
134
-
135
- ## Run workflows with Norn
136
-
137
- [CLI and client](docs/cli.md) covers live contract discovery, JSON invocation,
138
- launch/wait semantics, results, and use from other harnesses.
139
-
140
119
  ## Development
141
120
 
142
121
  ```bash
@@ -153,5 +132,4 @@ published dependency. No separate core build is needed.
153
132
 
154
133
  Checks cover package builds, TypeScript (including examples, adapters, scripts,
155
134
  and tests), runtime regressions, isolated npm installations, and standalone binaries.
156
- `pack:dry` creates local tarballs without publishing. See
157
- [release setup](setup/releases.md) for bootstrap and trusted publishing.
135
+ `pack:dry` creates local tarballs without publishing.
@@ -7,12 +7,14 @@ Norn capabilities are ordinary TypeScript plugins: an agent can write one during
7
7
  | Task | Documentation | Runnable example |
8
8
  |---|---|---|
9
9
  | Build, register, or diagnose workflows with the Norn SDK | [Projects and loading](projects.md), [Workflow authoring](workflows.md) | [Create → run → change](../examples/minimal-workflow/README.md) |
10
+ | Define TypeBox schemas, constraints, or codecs | [TypeBox schemas](schemas.md) | — |
10
11
  | Discover contracts or invoke Norn from another harness | [CLI and client](cli.md) | [Create → run → change](../examples/minimal-workflow/README.md) |
12
+ | Configure providers, models, and authentication for Norn agents | [Providers and authentication](providers.md) | — |
11
13
  | Delegate work with explicit inputs and structured results | [Norn agents](agents.md) | [Norn agent → saved artifact → analysis](../examples/agent-then-analysis/README.md) |
12
14
  | Initialize shared resources, attach state tools, or coordinate file mutations | [Resources and locking](resources.md) | [Explicit shared state](../examples/shared-state/README.md) |
13
15
  | Implement a custom resource and adapter to coordinate concurrent agents | [Resource contracts](resources.md) | [Example-local work queue](../examples/coordinating-multiple-agents/README.md) |
14
16
  | Retain evidence or choose a filesystem boundary | [State, artifacts, and workspaces](persistence.md) | [Norn agent → saved artifact → analysis](../examples/agent-then-analysis/README.md) |
15
- | Reuse a workflow with a caller-selected continuation | [Composition](composition.md) | [Worktree development loop](../examples/worktree-development-loop/README.md) — larger, optional |
17
+ | Reuse a workflow with a caller-selected continuation | [Composition](composition.md) | [Caller-selected continuation](../examples/caller-selected-continuation/README.md) |
16
18
  | Repair a failed run without repeating earlier work | [Recovery and gates](recovery.md) | [Analysis-only repair](../examples/agent-then-analysis/README.md#repair-only-the-analysis-step) |
17
19
 
18
20
  [Public types](../packages/sdk/src/api.ts) define the Norn SDK's authoring interface. CLI discovery exposes the currently loaded project, not a documentation-time workflow catalogue. See [installation](../README.md#installation) for runtime setup.
@@ -6,7 +6,7 @@ The [Norn agent → saved artifact → analysis example](../examples/agent-then-
6
6
 
7
7
  ## One prompt or a retained session
8
8
 
9
- `run.agents.prompt({ label, prompt, response, ...sessionOptions })` creates a Pi session, prompts it, validates its response, and disposes it in `finally`. It returns the parsed response itself, not `{ response, raw }`.
9
+ `run.agents.prompt({ label, prompt, response, ...sessionOptions })` creates a Pi session for one prompt and returns the value described by the response schema, including any codec transformations—not `{ response, raw }`. You do not need to dispose this one-prompt session. See [schema input/output types](schemas.md#codecs-and-inputoutput-types).
10
10
 
11
11
  For follow-up turns in the same conversation:
12
12
 
@@ -34,7 +34,7 @@ try {
34
34
  }
35
35
  ```
36
36
 
37
- `task` and both Zod schemas are capability-specific inputs in this fragment. [Provider setup](../setup/providers.md) is a prerequisite for Norn agents; detached execution cannot conduct an interactive login.
37
+ `task` and both TypeBox schemas are capability-specific inputs in this fragment. [Provider setup](providers.md) is a prerequisite for Norn agents; detached execution cannot conduct an interactive login.
38
38
 
39
39
  ## Response contract and evidence
40
40
 
@@ -52,7 +52,7 @@ Successful results and raw attempts are written under `current/logs/agents/`; Pi
52
52
 
53
53
  The default tool allowlist is `read`, `bash`, `edit`, `write`, plus the response tool. An explicit `tools: []` requests no built-in task tools, but still includes the response tool and any explicitly attached [resource-adapter tools](resources.md#explicit-agent-attachment). `resourceAdapters` is accepted by both session creation and one-shot prompting; omitting it attaches no workflow state.
54
54
 
55
- Each session constructs Pi runtime services at its `cwd`, using the engine's explicit `agentDir`, then `NORN_AGENT_DIR`, then `~/.norn/agent`. An inherited `PI_CODING_AGENT_DIR` does not select the Norn agent's global configuration. Installed provider extensions register before default-model selection. Model selection uses the session's explicit `model`, then the engine's model, then Pi's model configuration. `thinkingLevel` similarly falls through from session to engine to Pi configuration. Discoverable settings, skills, context files, and extensions can therefore affect it. It does **not** inherit the outer conversation or its in-memory tool registrations. Loaded extensions may change active tools; the requested tool list alone is not an adversarial restriction.
55
+ Each session loads resources for its `cwd` and [Norn configuration](providers.md#norn-configuration). Installed provider extensions register before default-model selection. Both `run.agents.createSession` and `run.agents.prompt` accept per-session `model` and `thinkingLevel` overrides; omitted values use Pi's configured selection and defaults. Discoverable settings, skills, context files, and extensions can therefore affect it. It does **not** inherit the outer conversation or its in-memory tool registrations. Loaded extensions may change active tools; the requested tool list alone is not an adversarial restriction.
56
56
 
57
57
  `systemPrompt` replaces the base prompt; `appendSystemPrompt` adds to resource-loader append content. Pi's default self-documentation block is absent with a custom base prompt. Context files and applicable skill advertisements can still be appended by Pi. Norn currently does not automatically inject a Norn authoring bootstrap.
58
58
 
@@ -45,7 +45,7 @@ npm/source installations resolve their built `assets/` tree directly from the CL
45
45
  package, not the current directory or another executable on PATH. A version mismatch
46
46
  fails rather than advertising another build's documentation. Compiled binaries embed the docs,
47
47
  examples, and source references, preserving relative links. The first
48
- inspection publishes a complete extracted tree into a build/content-specific
48
+ inspection makes a complete copy available in a build/content-specific
49
49
  cache; later calls verify and reuse it without rewriting files. Different asset
50
50
  contents or build commits use separate entries. `version` and help do not extract
51
51
  anything. No prompt augmentation or agent-context delivery is performed.
@@ -57,9 +57,9 @@ Cache roots:
57
57
  - Windows: `%LOCALAPPDATA%/norn/docs/`, or `~/AppData/Local/norn/docs/`
58
58
  - Explicit override: `NORN_DOCS_CACHE_DIR`
59
59
 
60
- Extraction uses staging directories and atomic publication. Existing cache entries
61
- are checked for exact file bytes, missing/extra files, and symlinks. Corrupt entries
62
- are not silently overwritten; the error names the entry to remove before retrying.
60
+ Existing cache entries must match the bundled files exactly, with no missing or
61
+ extra files or symlinks. Corrupt entries are not silently overwritten; the error
62
+ names the entry to remove before retrying.
63
63
  This is accidental-corruption detection, not a sandbox against another process
64
64
  with access to the same user's cache. Old build entries are not automatically
65
65
  removed. The extracted tree is a documentation snapshot, not a separate runtime
@@ -82,7 +82,7 @@ norn docs intro
82
82
  Returns `{ "intro": "..." }`: a compact authoring introduction with runtime
83
83
  version/commit, invocation, and pointers to the documentation index and examples.
84
84
  Topic routing remains in the index; the command does not copy manuals or enumerate
85
- workflows. It uses the same asset resolver/cache as `docs inspect`
85
+ workflows. It returns locations for the same installed documentation as `docs inspect`
86
86
  and works without a valid project.
87
87
 
88
88
  The invocation is a JSON argument array, not a shell command string. Its first
@@ -113,7 +113,7 @@ Default workflow listing shows entrypoints; `--all` includes internal steps. Wor
113
113
  Help is text; ordinary results are JSON. `runs logs` emits JSONL events.
114
114
  `norn pi [arguments...]` is a passthrough to bundled Pi, preserving Pi's native
115
115
  output and exit status rather than wrapping them in Norn JSON. Use `norn pi --help`
116
- for Pi's options and [provider setup](../setup/providers.md) for operator instructions.
116
+ for Pi's options and [provider setup](providers.md) for operator instructions.
117
117
  Commands and schemas from the invoked executable are authoritative when a checkout and installation differ.
118
118
 
119
119
  ## Start, wait, inspect
@@ -178,6 +178,6 @@ console.log(finished.outcome?.metadata);
178
178
 
179
179
  The client defaults to its own package's `bin/norn.mjs`. Its optional `executablePath` is a script launched through `process.execPath`, not an arbitrary standalone binary or shell command. Other languages can invoke the CLI directly with cwd, JSON stdin, and parsed stdout.
180
180
 
181
- `workflows.list()` and `inspect()` use fresh CLI discovery. `workflows.entries()` and `client.state` use a cached in-process project load; that state is registration memory, not a chosen run's persisted state. A new client is needed to refresh that in-process catalogue after source edits.
181
+ `workflows.list()` and `inspect()` use fresh CLI discovery. `workflows.entries()` and `client.state` use a cached in-process project load; [persistence](persistence.md#choose-what-survives) defines the `client.state` lifetime. A new client is needed to refresh that in-process catalogue after source edits.
182
182
 
183
183
  Sources: [CLI declarations and handlers](../packages/cli/src/cli.ts), [client API](../packages/cli/src/client.ts).
@@ -3,45 +3,42 @@
3
3
  ## Transfer, not a returning call
4
4
 
5
5
  ```ts
6
- return run.next(manifest.workflows.analyze, { draftArtifact });
6
+ return manifest.workflows.analyze({ draftArtifact });
7
7
  ```
8
8
 
9
- `run.next` constructs a control result. Returning it lets the scheduler persist the transition and execute the target in the **same run**, with that target's isolation mode. It does not suspend the caller and later return a value. Awaiting `run.next` cannot turn it into a subroutine.
9
+ Return a workflow call to select the next step in the same run. Supply its complete input; TypeScript checks it against the declaration's params schema. The target must be registered in the loaded project.
10
10
 
11
- The caller and target share run state and artifacts, not local variables or agent conversations. All targets must be registered in the loaded project. Params are validated at the target; a returned `complete` completes the entire run.
11
+ This transfers control rather than calling a subroutine: awaiting the declaration does not execute the target or return its eventual result. Steps share run state and artifacts, not local variables or agent conversations. `run.complete` completes the whole run.
12
12
 
13
- ## Caller-selected continuation
13
+ For a dynamically selected string ID, use `return run.next(workflowId, params)`. The selected target checks its input at runtime. `run.next` accepts IDs, not declarations or reference functions.
14
14
 
15
- A reusable capability can accept a continuation whose schema describes the values it contributes. The caller owns the remaining params:
15
+ ## Caller-selected workflow reference
16
+
17
+ The [caller-selected continuation example](../examples/caller-selected-continuation/README.md)
18
+ runs a producer with either of two caller-selected consumers, forwarding caller
19
+ context alongside the producer's results. It needs no model or credentials.
20
+
21
+ A reusable capability can accept a workflow reference whose schema describes the values it contributes. The caller supplies the target and captures the remaining params:
16
22
 
17
23
  ```ts
18
24
  import { artifactRefSchema, workflowRefSchema } from "@vimhead.dev/norn";
19
- import { z } from "zod";
20
-
21
- const paramsSchema = z.object({
22
- task: z.string(),
23
- next: workflowRefSchema({
24
- params: z.object({
25
- resultArtifact: artifactRefSchema,
26
- summary: z.string(),
25
+ import { Type } from "typebox";
26
+
27
+ const paramsSchema = Type.Object({
28
+ task: Type.String(),
29
+ next: Type.Union([
30
+ workflowRefSchema({
31
+ params: Type.Object({
32
+ resultArtifact: artifactRefSchema,
33
+ summary: Type.String(),
34
+ }),
27
35
  }),
28
- }).nullable(),
36
+ Type.Null(),
37
+ ]),
29
38
  });
30
39
  ```
31
40
 
32
- After producing `resultArtifact` and `summary`, the reusable implementation returns:
33
-
34
- ```ts
35
- return params.next
36
- ? run.next(params.next.workflow, {
37
- ...params.next.forwardParams,
38
- resultArtifact,
39
- summary,
40
- })
41
- : run.complete({ summary, artifacts: { result: resultArtifact } });
42
- ```
43
-
44
- A caller supplies a registered target and its own context:
41
+ A caller supplies the target and its own parameters:
45
42
 
46
43
  ```json
47
44
  {
@@ -55,22 +52,55 @@ A caller supplies a registered target and its own context:
55
52
  }
56
53
  ```
57
54
 
58
- `importer.deliver` must accept `batchId`, `resultArtifact`, and `summary`. Code can supply a workflow declaration instead of the ID string. A bare ID or `{ id }` reference normalizes to empty `forwardParams`.
55
+ Code uses a declaration's `.id` in the reference payload, not the declaration itself. A bare ID string is shorthand for an object reference with empty `forwardParams`.
56
+
57
+ Inside the workflow, `params.next` is a function. Supply only the result fields declared above:
58
+
59
+ ```ts
60
+ return params.next
61
+ ? params.next({ resultArtifact, summary })
62
+ : run.complete({ summary, artifacts: { result: resultArtifact } });
63
+ ```
64
+
65
+ `importer.deliver` receives `batchId` from the caller plus `resultArtifact` and `summary` from the producer. Its params schema must accept all three. Produced fields replace caller fields with the same name; nested objects are replaced, not deep-merged.
66
+
67
+ Contributions must be JSON objects matching the declared input type. Object-valued records, unions, intersections and codecs are supported. With codecs, supply `StaticEncode` values, just as for a direct workflow call—not transformed `StaticDecode` values.
68
+
69
+ `workflows inspect` shows the contribution contract under `x-norn-workflow-ref.contributedParamsSchema`. Use it alongside the target's params schema to check that the combined input fits.
70
+
71
+ When passing a reference as input to another workflow, supply its JSON form shown above, not the function received in `params.next`.
72
+
73
+ ## Multiple outcomes and direct targets
74
+
75
+ References can be nested under ordinary author-selected names with independent contribution schemas:
76
+
77
+ ```ts
78
+ const next = Type.Object({
79
+ success: workflowRefSchema({
80
+ params: Type.Object({ resultArtifact: artifactRefSchema }),
81
+ }),
82
+ failure: workflowRefSchema({
83
+ params: Type.Object({ reason: Type.String() }),
84
+ }),
85
+ });
86
+ ```
87
+
88
+ An implementation can return `params.next.success({ resultArtifact })`, `params.next.failure({ reason })`, or select its own known target with `manualReviewWorkflow({ task, reason })`. A dynamic target still uses `run.next(selectedId, input)`.
59
89
 
60
- `workflows inspect` exposes `x-norn-workflow-ref.contributedParamsSchema` at the reference's JSON Schema node. This is the producer's contribution contract, not extra fields required in the caller's reference payload. It does not automatically merge params, verify the target exists, or prove the target accepts the combination.
90
+ These are alternative transitions, not fan-out. `success` and `failure` are not reserved names, and a failure reference does not catch unhandled exceptions automatically.
61
91
 
62
92
  | Decision | GOOD | BAD |
63
93
  |---|---|---|
64
- | IF a caller selects the next step, THEN forward its opaque params and add the declared contribution explicitly. ELSE transition to a known declaration with explicit params. | Spread `forwardParams`, then add the producer-owned result fields. | Hardcode a task-specific next workflow into a supposedly reusable producer. |
65
- | IF resuming the caller requires additional work, THEN represent that work as the supplied continuation. ELSE let the producer complete. | `assess → caller.deliver`. | Expect execution to return to the line following `run.next`. |
66
- | IF a target schema changes, THEN inspect and exercise the combined params contract. ELSE preserve its existing input contract. | Verify the target accepts both batch context and result ref. | Treat the contributed schema annotation as automatic end-to-end compatibility checking. |
94
+ | IF the caller selects the next step, THEN invoke its reference with the declared contribution. ELSE call a known declaration or use a dynamic ID. | `params.next({ resultArtifact })` | Manually reconstruct captured forwarding input. |
95
+ | IF additional caller work follows the result, THEN represent it as the supplied reference. ELSE complete the run. | `assess → caller.deliver` | Expect execution to return to the line following a workflow call. |
96
+ | IF a target schema changes, THEN exercise the assembled input contract. ELSE preserve its existing input contract. | Verify the target accepts captured context and contributed results. | Treat contribution metadata as end-to-end compatibility proof. |
67
97
 
68
- The [worktree development loop](../examples/worktree-development-loop/README.md) is a larger continuation-based example, not a required planner/reviewer architecture.
98
+ The [worktree development loop](../examples/worktree-development-loop/README.md) is a larger multi-step example using known workflow declarations, not a required planner/reviewer architecture.
69
99
 
70
100
  ## Another project or harness
71
101
 
72
102
  Reuse source by explicitly registering it, directly or through [included config](projects.md). There is no required package layout. Plugin IDs must remain unique within a project; run state and artifacts belong to the invoking project/run, not the plugin source directory.
73
103
 
74
- An external shell, Python program, or agent harness can call the [CLI](cli.md) from the target project directory. The JavaScript client offers the same lifecycle without inventing another orchestration layer. Separate CLI starts create separate runs; connecting their artifact content is a caller responsibility, unlike same-run continuation refs.
104
+ An external shell, Python program, or agent harness can call the [CLI](cli.md) from the target project directory. The JavaScript client offers the same lifecycle without inventing another orchestration layer. Separate CLI starts create separate runs; connecting their artifact content is a caller responsibility, unlike same-run references.
75
105
 
76
106
  Sources: [reference schemas and controls](../packages/sdk/src/api.ts), [scheduler](../packages/cli/src/internal/engine.ts), [reference contract tests](../tests/workflow-ref.test.ts).
@@ -4,13 +4,15 @@
4
4
 
5
5
  | Value | Lifetime and access |
6
6
  |---|---|
7
- | Local variables / plugin factory memory | Current executor only; not a resume contract. Factory context state is in-memory registration state. |
7
+ | Local variables / plugin factory memory | Current invocation or executor only; not a resume contract. |
8
+ | Factory context `state` / `client.state` | In-memory registration state for the loaded project, not a selected run's persisted state. |
8
9
  | `run.state` | Per-run, schema-validated JSON shared across steps. `get` requires a value; `getOptional` permits absence; `set` persists it. |
10
+ | `run.resources` data | Per-run data included in checkpoint recovery. Resume reopens handles; closures do not survive. |
9
11
  | `run.artifacts` | Text files addressed by `{ path }` relative to this run's artifacts directory. Write content, pass the ref, read and validate at the consumer. |
10
12
  | Outcome metadata | Caller-facing summary, artifact/log refs and small data, exposed by run inspection. |
11
13
  | Workflow params | Explicit input to the current/next step, persisted for recovery. |
12
14
 
13
- Workflow state is the automatically initialized built-in [run resource](resources.md). State declarations live in the manifest. The [agent example](../examples/agent-then-analysis/plugin.ts) saves the draft ref in run state and also passes it explicitly to analysis. The state value is a retained run record; the params are the consumer's input contract.
15
+ Workflow state is a built-in [run resource](resources.md), available without an explicit `ensure` call. State declarations live in the manifest; declaring a field, including a schema default, does not set its value. The [agent example](../examples/agent-then-analysis/plugin.ts) saves the draft ref in run state and also passes it explicitly to analysis. The state value is a retained run record; the params are the consumer's input contract.
14
16
 
15
17
  Artifact refs are paths, not content hashes, and a write to the same path replaces its content. State writes are serialized and atomic; an artifact write plus a state update is not a single transaction. An artifact read returns text, so a JSON consumer still needs parsing and schema validation.
16
18
 
@@ -32,24 +32,15 @@ Reusable config files, conventionally `norn.json`, can declare `plugins`, `inclu
32
32
  }
33
33
  ```
34
34
 
35
- Plugin and include paths resolve relative to the file declaring them. `*` matches one directory segment. There is no automatic plugin tree scan, nor automatic inclusion of a sibling `norn.json`. Project config overrides included values; conflicting reusable values and duplicate plugin IDs are rejected. `version` and `seerMode` belong only in the project file.
36
-
37
- Projects using Seer can additionally declare writable project-relative roots:
38
-
39
- ```json
40
- "seerMode": { "writableRoots": ["./workflow-sources"] }
41
- ```
42
-
43
- This is a project-file field, not an OS sandbox. The current helper contract is in
44
- [Seer exports](../packages/sdk/src/seer/index.ts) and [config resolution](../packages/sdk/src/seer/config.ts).
35
+ Plugin and include paths resolve relative to the file declaring them. `*` matches one directory segment. There is no automatic plugin tree scan, nor automatic inclusion of a sibling `norn.json`. Project config overrides included values; conflicting reusable values and duplicate plugin IDs are rejected. `version` belongs only in the project file.
45
36
 
46
37
  ## Import and reload
47
38
 
48
- Each registered module default-exports `definePlugin(manifest, implementation)`. Norn loads TypeScript through jiti without a local build, supplying runtime imports for `@vimhead.dev/norn`, its `/files`, `/schema`, and `/seer` subpaths, `zod`, and `typebox`. Other dependencies need normal package resolution from the plugin's location.
39
+ Each registered module default-exports `definePlugin(manifest, implementation)`. Norn loads TypeScript through jiti without a local build, supplying runtime imports for `@vimhead.dev/norn`, its `/files` and `/schema` subpaths, `typebox`, `typebox/value`, `typebox/compile`, and `typebox/schema`. Other dependencies need normal package resolution from the plugin's location.
49
40
 
50
41
  Runtime virtual imports do not configure TypeScript or an editor. A matching `@vimhead.dev/norn` installation provides the SDK types; the source checkout's examples are checked by its `tsconfig.json`. A successful runtime import alone is not a type check.
51
42
 
52
- New CLI discovery/start/resume invocations load current source; an already executing workflow retains its loaded implementation. Module evaluation and implementation factories run even during discovery. Factory context state is in-memory registration state, **not** durable run state; see [persistence](persistence.md).
43
+ New CLI discovery/start/resume invocations load current source; an already executing workflow retains its loaded implementation. Module evaluation and implementation factories run even during discovery. [Persistence](persistence.md#choose-what-survives) defines the lifetime of factory context state.
53
44
 
54
45
  | Decision | GOOD | BAD |
55
46
  |---|---|---|
@@ -27,8 +27,9 @@ It is independent of the outer Pi harness's global configuration:
27
27
  - `models.json` — custom endpoints, models, and authentication configuration
28
28
 
29
29
  `NORN_AGENT_DIR` selects another directory. Set it consistently for both
30
- `norn pi` and workflow execution. A programmatic runtime caller supplying `agentDir`
31
- must point the setup command at that same directory. Norn does not use an inherited
30
+ `norn pi` and workflow execution. A programmatic runtime caller's explicit
31
+ `agentDir` takes precedence over `NORN_AGENT_DIR`; the setup command still needs
32
+ to target that same directory. Norn does not use an inherited
32
33
  `PI_CODING_AGENT_DIR` to select its global configuration. Existing Pi packages and
33
34
  credentials are not imported or linked: install providers and authenticate through
34
35
  `norn pi`, even when Pi is the outer harness.
@@ -59,7 +60,7 @@ norn pi
59
60
 
60
61
  This installs into Norn's global configuration. Select the new provider using
61
62
  `/model`. Agent resource discovery and model precedence are covered in
62
- [Norn agents](../docs/agents.md#prompts-tools-and-resource-loading).
63
+ [Norn agents](agents.md#prompts-tools-and-resource-loading).
63
64
  Use `norn pi list` to inspect installations and `norn pi remove <source>` to remove
64
65
  one. Upgrade bundled Pi by upgrading Norn, not by using Pi's self-update command.
65
66
  `norn pi update --extensions` updates unpinned extension packages.
@@ -2,18 +2,18 @@
2
2
 
3
3
  ## Initialization is separate from attachment
4
4
 
5
- Every run initializes or reopens `run.resources` before workflow execution and automatically ensures workflow state through that manager. The state handle is exposed as `run.state`, with `get`, `getOptional` and `set`. Creating storage does not populate declared fields, including schemas with defaults. Factory registration state remains in-memory and separate.
5
+ `run.resources` provides shared resource handles, including [workflow state](persistence.md#choose-what-survives).
6
6
 
7
- `run.resources.ensure(definition)` returns a shared handle within that manager. A definition contains `name`, `kind`, JSON `configuration`, and `initialize({directory, files, mode})`. Names are single alphanumeric/underscore/hyphen identifiers starting with an alphanumeric character. Identity/configuration conflicts fail; `configuration` owns format/version compatibility.
7
+ `run.resources.ensure(definition)` returns a shared handle for that resource across workflow contexts in the current executor. A definition contains `name`, `kind`, JSON `configuration`, and `initialize({directory, files, mode})`. Names are single alphanumeric/underscore/hyphen identifiers starting with an alphanumeric character. Identity/configuration conflicts fail; `configuration` owns format/version compatibility.
8
8
 
9
- The manager persists identity before calling the initializer and marks successful initialization afterward. `mode: "create"` also covers retry of an interrupted initialization; `mode: "open"` means an earlier initialization succeeded. Initializers own their data schema and must reject missing/incompatible data when reopening. Failed initialization remains visible and retryable, not a successful empty resource. Definitions have no filesystem effects until ensured.
9
+ `mode: "create"` also covers retry of an interrupted initialization; `mode: "open"` means an earlier initialization succeeded. Initializers own their data schema and must reject missing/incompatible data when reopening. Failed initialization remains visible and retryable, not a successful empty resource. Definitions have no filesystem effects until ensured.
10
10
 
11
11
  | Decision | GOOD | BAD |
12
12
  |---|---|---|
13
13
  | IF implementing an initializer, THEN make create retries preserve existing data and open validate existing storage. ELSE do not register the definition. | Validate a file left by an interrupted create before reusing it. | Truncate the file each time `ensure` calls the initializer. |
14
14
  | IF a resource format changes incompatibly, THEN change its declared configuration and provide an explicit migration. ELSE reopen the same format. | A mismatching `format` fails. | Reinterpret old bytes under an unchanged format declaration. |
15
15
 
16
- Resources are run-scoped and persisted under `current/resources/`; built-in workflow values retain their existing `current/state.json` location. All workflow contexts in one executor share the manager. A resumed executor reopens handles, not closures. Resource data participates in normal [checkpoint recovery](recovery.md). Cross-run storage, queues, ledgers, and scheduler/agent activation are not supplied by this API.
16
+ [Persistence](persistence.md) covers resource lifetimes, storage locations, and recovery. Cross-run storage, queues, ledgers, and scheduler/agent activation are not supplied by this API.
17
17
 
18
18
  ## Explicit agent attachment
19
19
 
@@ -33,7 +33,7 @@ resourceAdapters: [StateAdapter({
33
33
 
34
34
  List/get output is serialized JSON in bounded text pages. Requests specify UTF-16 `offset` and `limit` (1–10000); responses include `text`, `nextOffset` and a content `revision`. Pages are not a pinned snapshot. Set operations persist complete field values; get followed by set is not a transaction.
35
35
 
36
- Custom adapters implement `NornAgentResourceAdapter`: a unique name and `bind({runId, label})` returning a `NornAgentResourceBinding` with tools and async `dispose()`. An adapter can expose one or several resource handles; initializing storage does not construct or attach tools. Session creation and one-shot prompting accept adapters through `resourceAdapters`, not through the resource manager or state handle directly. The runner knows only the adapter contract, not individual resource kinds. Attached tools are activated with the normal response tool. Duplicate adapter names and collisions with built-ins, the response tool, other adapters or already-loaded extension tools fail. Successful bindings are cleaned up in reverse order on session disposal or later creation failure. An initializer/binder that throws before returning its handle owns cleanup of its partial allocations.
36
+ Custom adapters implement `NornAgentResourceAdapter`: a unique name and `bind({runId, label})` returning a `NornAgentResourceBinding` with tools and async `dispose()`. An adapter can expose one or several resource handles; initializing storage does not construct or attach tools. Session creation and one-shot prompting accept adapters through `resourceAdapters`, not through the resource manager or state handle directly. Attached tools are activated with the normal response tool. Duplicate adapter names and collisions with built-ins, the response tool, other adapters or already-loaded extension tools fail. Successful bindings are cleaned up in reverse order on session disposal or later creation failure. An initializer/binder that throws before returning its handle owns cleanup of its partial allocations.
37
37
 
38
38
  | Decision | GOOD | BAD |
39
39
  |---|---|---|
@@ -47,9 +47,9 @@ The attachment never exposes internal scheduler/checkpoint control state. It is
47
47
 
48
48
  `run.resources.files` supplies `readText`, `writeText`, and `withExclusiveLock(path, async lockedPath => ...)`. Standalone callers can construct `NornFileCoordinator({lockRoot, waitTimeoutMs})` from `@vimhead.dev/norn`. Coordinating callers must use the same lock namespace. Target parents must exist before a raw `withExclusiveLock` call; `writeText` creates them. Existing symbolic links resolve to their canonical target; dangling links fail.
49
49
 
50
- The lock spans the complete callback, including read/validate/modify/persist. Atomic replacement remains underneath managed writes. A live owner is never expired by a TTL; confirmed dead local owners can be reclaimed. Invalid or foreign-host ownership fails closed, and contention has a bounded wait. PID reuse can delay reclamation rather than permit two owners. This is a local-filesystem, same-host protocol, not a distributed lock.
50
+ The lock spans the complete callback, including read/validate/modify/persist. `writeText` replaces complete file contents atomically. A live owner is never expired by a TTL; confirmed dead local owners can be reclaimed. Invalid or foreign-host ownership fails closed, and contention has a bounded wait. PID reuse can delay reclamation rather than permit two owners. Locks support local filesystems on one host, not distributed storage.
51
51
 
52
- Workflow state, event manifests, scheduler-state writes, replaceable artifacts and whole-value logs use this coordination. Scheduler state remains executor-owned. Dedicated command-output streams retain their single-writer protocol; observability reads may see partial live streams. Immutable snapshot objects and run execution leases retain their own protocols. File locks do not make multi-file snapshots or external side effects transactional.
52
+ Reads of live command-output logs may return partial streams. File locks do not make multi-file snapshots or external side effects transactional.
53
53
 
54
54
  | Decision | GOOD | BAD |
55
55
  |---|---|---|
@@ -0,0 +1,130 @@
1
+ # TypeBox schemas
2
+
3
+ The [Norn SDK](../packages/sdk/src/api.ts) accepts native `typebox` 1.x schemas for workflow params, config, state and agent responses. This reference is checked against 1.3.33. [Project loading](projects.md#import-and-reload) describes runtime-provided imports and editor dependency resolution.
4
+
5
+ ## TypeScript → TypeBox
6
+
7
+ ```ts
8
+ import { Type, type Static } from "typebox";
9
+
10
+ const Person = Type.Object({ name: Type.String() });
11
+ type Person = Static<typeof Person>;
12
+ ```
13
+
14
+ Schemas are runtime values; `Static` derives the TypeScript type. In the table, `S`, `A` and `B` are schemas, with corresponding inferred types `T`, `TA` and `TB`.
15
+
16
+ | TypeScript type | TypeBox schema |
17
+ |---|---|
18
+ | `string` | `Type.String()` |
19
+ | `number` | `Type.Number()` |
20
+ | `boolean` | `Type.Boolean()` |
21
+ | `null` | `Type.Null()` |
22
+ | `undefined` | `Type.Undefined()` |
23
+ | `unknown` | `Type.Unknown()` |
24
+ | `any` | `Type.Any()` |
25
+ | `never` | `Type.Never()` |
26
+ | `"ready"` | `Type.Literal("ready")` |
27
+ | `string[]` | `Type.Array(Type.String())` |
28
+ | `[string, number]` | `Type.Tuple([Type.String(), Type.Number()])` |
29
+ | `{ name: string }` | `Type.Object({ name: Type.String() })` |
30
+ | `{ name?: string }` | `Type.Object({ name: Type.Optional(Type.String()) })` |
31
+ | `{ readonly name: string }` | `Type.Object({ name: Type.Readonly(Type.String()) })` |
32
+ | `string \| null` | `Type.Union([Type.String(), Type.Null()])` |
33
+ | `TA \| TB` | `Type.Union([A, B])` |
34
+ | `TA & TB` | `Type.Intersect([A, B])` |
35
+ | `Record<string, number>` | `Type.Record(Type.String(), Type.Number())` |
36
+ | `Partial<T>` | `Type.Partial(S)` |
37
+ | `Required<T>` | `Type.Required(S)` |
38
+ | `Pick<T, "name">` | `Type.Pick(S, ["name"])` |
39
+ | `Omit<T, "name">` | `Type.Omit(S, ["name"])` |
40
+
41
+ `Type.Integer()` also infers `number`; integrality is a runtime constraint. Optional properties may be absent; accepting `null` is a separate union. `Type.Readonly` affects static typing, not runtime freezing. `Type.Unknown` and `Type.Any` accept any value at runtime but infer different TypeScript types; neither guarantees JSON serializability. JavaScript-only values such as `undefined`, `bigint`, functions and symbols are not JSON data contracts.
42
+
43
+ ## Constraints and validation
44
+
45
+ ```ts
46
+ import { Type } from "typebox";
47
+ import { Value } from "typebox/value";
48
+ import { Compile } from "typebox/compile";
49
+
50
+ const Task = Type.Object({
51
+ title: Type.String({ minLength: 1 }),
52
+ attempts: Type.Integer({ minimum: 1, maximum: 10 }),
53
+ }, { additionalProperties: false });
54
+
55
+ const valid = Value.Check(Task, { title: "Build", attempts: 3 });
56
+ const invalid = Value.Check(Task, { title: "Build", attempts: "3" });
57
+ const issues = Value.Errors(Task, { title: "", attempts: 0 });
58
+ const validator = Compile(Task);
59
+ const compiledValid = validator.Check({ title: "Build", attempts: 3 });
60
+ ```
61
+
62
+ `valid` and `compiledValid` are `true`; `invalid` is `false`. `Value.Check` tests without repairing the input. `Value.Assert` throws on failure. `Value.Parse` returns validated input with the default `correctiveParse: false` setting; enabling that setting adds repair operations. `Value.Errors` returns issues with fields including `keyword`, `instancePath`, `schemaPath` and `message`. `Compile` creates a reusable validator.
63
+
64
+ Objects permit additional properties by default. `additionalProperties: false` rejects extras during validation; it does not strip them. A `default` annotation neither makes a property optional nor fills it during `Value.Check`. `Value.Default` is a separate operation; `Value.Convert` and `Value.Clean` likewise explicitly convert and clean values rather than merely validate them.
65
+
66
+ ## Tagged unions and refinements
67
+
68
+ ```ts
69
+ import { Type, type Static } from "typebox";
70
+ import { Value } from "typebox/value";
71
+
72
+ const Outcome = Type.Union([
73
+ Type.Object({ kind: Type.Literal("done"), summary: Type.String() }),
74
+ Type.Object({ kind: Type.Literal("retry"), reason: Type.String() }),
75
+ ]);
76
+ type Outcome = Static<typeof Outcome>;
77
+
78
+ const UniqueNames = Type.Refine(
79
+ Type.Array(Type.String()),
80
+ names => new Set(names).size === names.length,
81
+ () => "Names must be unique",
82
+ );
83
+ const distinct = Value.Check(UniqueNames, ["build", "review"]);
84
+ const repeated = Value.Check(UniqueNames, ["build", "build"]);
85
+ ```
86
+
87
+ The literal `kind` supports TypeScript narrowing and distinguishes runtime branches. `Type.Refine` adds a predicate to an existing schema: `distinct` is `true`, `repeated` is `false`. The predicate executes in TypeBox; serializing the schema as JSON does not carry that function into another validator.
88
+
89
+ ## Recursive data
90
+
91
+ ```ts
92
+ import { Type, type Static } from "typebox";
93
+ import { Value } from "typebox/value";
94
+
95
+ const Tree = Type.Cyclic({
96
+ Node: Type.Object({
97
+ name: Type.String(),
98
+ children: Type.Array(Type.Ref("Node")),
99
+ }),
100
+ }, "Node");
101
+ type Tree = Static<typeof Tree>;
102
+
103
+ const tree: Tree = { name: "root", children: [{ name: "leaf", children: [] }] };
104
+ const validTree = Value.Check(Tree, tree);
105
+ ```
106
+
107
+ `Type.Cyclic` supplies definitions and selects the root; `Type.Ref` resolves a definition within that context. This describes recursive data shapes, not circular JavaScript object identities.
108
+
109
+ ## Codecs and input/output types
110
+
111
+ ```ts
112
+ import { Type, type StaticEncode, type StaticDecode } from "typebox";
113
+ import { Value } from "typebox/value";
114
+
115
+ const CountText = Type.Codec(Type.String({ pattern: "^[0-9]+$" }))
116
+ .Decode(text => Number(text))
117
+ .Encode(count => String(count));
118
+
119
+ type CountInput = StaticEncode<typeof CountText>;
120
+ type CountOutput = StaticDecode<typeof CountText>;
121
+
122
+ const count = Value.Decode(CountText, "42");
123
+ const text = Value.Encode(CountText, 42);
124
+ ```
125
+
126
+ `CountInput` is `string`, `CountOutput` is `number`; the values are `42` and `"42"`. `Static` uses the encoded/input direction. `Type.Decode(schema, callback)` defines a decode-only codec instead of a bidirectional one.
127
+
128
+ `Value.Decode` is not a validation-only operation: in this version it clones, applies defaults, converts, cleans, validates, then runs decode callbacks. `Value.Check` and `Value.Assert` do not run codec callbacks. Callback behavior remains executable TypeBox code, not portable JSON Schema validation.
129
+
130
+ Further API reference: [TypeBox documentation](https://sinclairzx81.github.io/typebox/).
@@ -6,15 +6,16 @@ Start with the complete [minimal plugin](../examples/minimal-workflow/plugin.ts)
6
6
 
7
7
  ## Declaration and implementation
8
8
 
9
- `definePluginManifest` qualifies workflow keys as `pluginId.workflowKey`, binds Zod params, optional plugin config, and optional state declarations. `definePlugin` binds every declared key to an implementation. Entrypoints need nonempty caller-facing `instructions`; internal steps may omit them. `isEntrypoint` controls default catalogue visibility, not an authorization boundary: the CLI can start a known internal workflow ID directly.
9
+ `definePluginManifest` qualifies workflow keys as `pluginId.workflowKey`, binds TypeBox params, optional plugin config, and optional state declarations. `definePlugin` binds every declared key to an implementation. Entrypoints need nonempty caller-facing `instructions`; internal steps may omit them. `isEntrypoint` controls default catalogue visibility, not an authorization boundary: the CLI can start a known internal workflow ID directly.
10
10
 
11
- `instructions` describe selection, inputs, effects, and outputs. They are neither a Norn agent system prompt nor a gate decision. Params and plugin config are parsed before execution. Public contracts must support JSON Schema inspection; JSON params must survive persistence and later parsing.
11
+ `instructions` describe selection, inputs, effects, and outputs. They are neither a Norn agent system prompt nor a gate decision. Declare params and config with [TypeBox schemas](schemas.md). Workflow inputs must be JSON data; `execute` receives the values after schema defaults and conversions. Public schemas must support `workflows inspect`.
12
12
 
13
13
  The implementation's `execute(run, params, config)` returns one control result:
14
14
 
15
15
  | Control | Meaning |
16
16
  |---|---|
17
- | `run.next(target, params)` | Transfer to another registered workflow in the same run. See [composition](composition.md). |
17
+ | `target(params)` / `params.next(contribution)` | Select a known workflow or a caller-supplied next step. See [composition](composition.md). |
18
+ | `run.next(workflowId, params)` | Select a workflow by string ID; its input is checked at execution. |
18
19
  | `run.complete(metadata)` | Complete the whole run, optionally exposing `summary`, `artifacts`, `logs`, and `data`. |
19
20
  | `run.fail({ summary, ...metadata })` | Record failure with an actionable explanation and optional evidence. |
20
21
 
@@ -17,7 +17,7 @@ small application, not a prescribed development pipeline.
17
17
 
18
18
  [Select the matching runtime](../../docs/cli.md#select-the-runtime), copy this
19
19
  directory to a writable task directory, and `cd` into the copy. Configure
20
- [Norn agent authentication and a default model](../../setup/providers.md)
20
+ [Norn agent authentication and a default model](../../docs/providers.md)
21
21
  beforehand; this example makes live model calls and the detached executor cannot
22
22
  prompt for login.
23
23