@navels/neal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +527 -0
  3. package/SECURITY.md +91 -0
  4. package/config.yml +104 -0
  5. package/dist/neal/activity-footer.js +177 -0
  6. package/dist/neal/activity-reporting.js +1 -0
  7. package/dist/neal/adjudicator/artifacts.js +58 -0
  8. package/dist/neal/adjudicator/blocked-adjudicator.js +223 -0
  9. package/dist/neal/adjudicator/contracts.js +139 -0
  10. package/dist/neal/adjudicator/execute.js +611 -0
  11. package/dist/neal/adjudicator/final-completion.js +104 -0
  12. package/dist/neal/adjudicator/planning.js +145 -0
  13. package/dist/neal/adjudicator/specs.js +453 -0
  14. package/dist/neal/agents/prompts.js +120 -0
  15. package/dist/neal/agents/rounds.js +706 -0
  16. package/dist/neal/agents/schemas.js +832 -0
  17. package/dist/neal/agents/structured-coder.js +82 -0
  18. package/dist/neal/agents/structured-json.js +528 -0
  19. package/dist/neal/agents.js +4 -0
  20. package/dist/neal/atomic-write.js +18 -0
  21. package/dist/neal/blocked-guidance.js +406 -0
  22. package/dist/neal/cli.js +471 -0
  23. package/dist/neal/commands/check.js +401 -0
  24. package/dist/neal/commands/compat.js +807 -0
  25. package/dist/neal/commands/interactive-activity.js +57 -0
  26. package/dist/neal/commands/new-run.js +79 -0
  27. package/dist/neal/commands/plan-and-execute.js +44 -0
  28. package/dist/neal/commands/recovery-guidance.js +217 -0
  29. package/dist/neal/commands/resume-run.js +395 -0
  30. package/dist/neal/commands/review.js +21 -0
  31. package/dist/neal/commands/runtime.js +557 -0
  32. package/dist/neal/commands/setup.js +596 -0
  33. package/dist/neal/commands/squash.js +113 -0
  34. package/dist/neal/commands/status.js +33 -0
  35. package/dist/neal/commands/writer-exit-codes.js +42 -0
  36. package/dist/neal/commit-message.js +17 -0
  37. package/dist/neal/config.js +432 -0
  38. package/dist/neal/context/artifacts.js +140 -0
  39. package/dist/neal/context/context.js +324 -0
  40. package/dist/neal/context/inline-review-context.js +131 -0
  41. package/dist/neal/context/reviewer-context.js +166 -0
  42. package/dist/neal/context/shared.js +117 -0
  43. package/dist/neal/context/types.js +1 -0
  44. package/dist/neal/diagnostic.js +208 -0
  45. package/dist/neal/execute-finalization.js +5 -0
  46. package/dist/neal/final-completion-review.js +188 -0
  47. package/dist/neal/final-completion.js +229 -0
  48. package/dist/neal/git.js +339 -0
  49. package/dist/neal/index.js +135 -0
  50. package/dist/neal/interactive-controls.js +85 -0
  51. package/dist/neal/logger.js +102 -0
  52. package/dist/neal/manual-gates.js +121 -0
  53. package/dist/neal/orchestrator/artifacts.js +70 -0
  54. package/dist/neal/orchestrator/completion.js +531 -0
  55. package/dist/neal/orchestrator/failures.js +31 -0
  56. package/dist/neal/orchestrator/notifications.js +175 -0
  57. package/dist/neal/orchestrator/phases/coder.js +516 -0
  58. package/dist/neal/orchestrator/phases/planning.js +540 -0
  59. package/dist/neal/orchestrator/phases/recovery.js +798 -0
  60. package/dist/neal/orchestrator/phases/review.js +136 -0
  61. package/dist/neal/orchestrator/phases/shared.js +279 -0
  62. package/dist/neal/orchestrator/run-loop.js +113 -0
  63. package/dist/neal/orchestrator/split-plan.js +235 -0
  64. package/dist/neal/orchestrator/transitions.js +309 -0
  65. package/dist/neal/orchestrator.js +215 -0
  66. package/dist/neal/phase-display.js +27 -0
  67. package/dist/neal/plan-doc.js +154 -0
  68. package/dist/neal/plan-queue.js +1092 -0
  69. package/dist/neal/plan-refinement.js +39 -0
  70. package/dist/neal/plan-validation.js +525 -0
  71. package/dist/neal/progress.js +237 -0
  72. package/dist/neal/prompts/assert-builder.js +13 -0
  73. package/dist/neal/prompts/execute.js +290 -0
  74. package/dist/neal/prompts/guidance.js +70 -0
  75. package/dist/neal/prompts/planning.js +313 -0
  76. package/dist/neal/prompts/review-doctrine.js +142 -0
  77. package/dist/neal/prompts/shared.js +101 -0
  78. package/dist/neal/prompts/specialized.js +212 -0
  79. package/dist/neal/prompts/specs.js +572 -0
  80. package/dist/neal/providers/anthropic-claude.js +1599 -0
  81. package/dist/neal/providers/detection.js +139 -0
  82. package/dist/neal/providers/generic-agentic-tools.js +586 -0
  83. package/dist/neal/providers/generic-agentic.js +1238 -0
  84. package/dist/neal/providers/liveness.js +151 -0
  85. package/dist/neal/providers/openai-codex.js +1014 -0
  86. package/dist/neal/providers/openai-compatible.js +654 -0
  87. package/dist/neal/providers/registry.js +389 -0
  88. package/dist/neal/providers/telemetry.js +208 -0
  89. package/dist/neal/providers/types.js +21 -0
  90. package/dist/neal/recovery-artifacts.js +50 -0
  91. package/dist/neal/resume-decision.js +220 -0
  92. package/dist/neal/resume-planner.js +265 -0
  93. package/dist/neal/retrospective.js +391 -0
  94. package/dist/neal/review-debt.js +18 -0
  95. package/dist/neal/review-findings/artifacts.js +173 -0
  96. package/dist/neal/review-findings/prompts.js +172 -0
  97. package/dist/neal/review-findings/provider.js +330 -0
  98. package/dist/neal/review-findings/run.js +373 -0
  99. package/dist/neal/review-findings/types.js +1 -0
  100. package/dist/neal/review-mode.js +67 -0
  101. package/dist/neal/review.js +137 -0
  102. package/dist/neal/run-lock.js +334 -0
  103. package/dist/neal/run-metrics.js +355 -0
  104. package/dist/neal/run-narrative-types.js +1 -0
  105. package/dist/neal/run-narrative.js +1374 -0
  106. package/dist/neal/run-registry.js +218 -0
  107. package/dist/neal/run-status.js +25 -0
  108. package/dist/neal/scopes.js +451 -0
  109. package/dist/neal/sensitive-text.js +8 -0
  110. package/dist/neal/squash-message.js +379 -0
  111. package/dist/neal/squash.js +591 -0
  112. package/dist/neal/state-invariants.js +496 -0
  113. package/dist/neal/state-views.js +344 -0
  114. package/dist/neal/state.js +887 -0
  115. package/dist/neal/status-footer.js +258 -0
  116. package/dist/neal/status.js +1260 -0
  117. package/dist/neal/storage-paths.js +57 -0
  118. package/dist/neal/support.js +58 -0
  119. package/dist/neal/terminal-narrator.js +435 -0
  120. package/dist/neal/types.js +1 -0
  121. package/dist/neal/verification-events.js +81 -0
  122. package/dist/neal/version.js +37 -0
  123. package/dist/neal/worktree-status.js +137 -0
  124. package/dist/notifier.js +44 -0
  125. package/docs/ADJUDICATOR_INVENTORY.md +310 -0
  126. package/docs/PROMPT_SPECS.md +266 -0
  127. package/docs/README.md +22 -0
  128. package/docs/architecture.md +113 -0
  129. package/docs/assets/neal-execution-flow.png +0 -0
  130. package/docs/automation.md +65 -0
  131. package/docs/comparison.md +105 -0
  132. package/docs/compat.md +269 -0
  133. package/docs/compatible-models.md +135 -0
  134. package/docs/demo.md +55 -0
  135. package/docs/maintenance.md +64 -0
  136. package/docs/plan-format.md +213 -0
  137. package/docs/providers.md +751 -0
  138. package/docs/release.md +147 -0
  139. package/docs/state-machine.md +266 -0
  140. package/docs/storage.md +207 -0
  141. package/docs/troubleshooting.md +152 -0
  142. package/examples/compat/add-edit-verify/PLAN.md +29 -0
  143. package/examples/compat/add-edit-verify/broken.diff +8 -0
  144. package/examples/compat/add-edit-verify/good.diff +8 -0
  145. package/examples/compat/add-edit-verify/package.json +5 -0
  146. package/examples/compat/add-edit-verify/src/add.js +2 -0
  147. package/examples/compat/add-edit-verify/test/add.test.js +9 -0
  148. package/examples/compat/is-even-add-test/PLAN.md +30 -0
  149. package/examples/compat/is-even-add-test/broken.diff +11 -0
  150. package/examples/compat/is-even-add-test/good.diff +11 -0
  151. package/examples/compat/is-even-add-test/package.json +5 -0
  152. package/examples/compat/is-even-add-test/src/is-even.js +3 -0
  153. package/examples/compat/is-even-add-test/test/is-even.test.js +9 -0
  154. package/examples/compat/manifest.json +60 -0
  155. package/examples/compat/plan-greeting/ISSUE.md +25 -0
  156. package/examples/compat/plan-greeting/package.json +5 -0
  157. package/examples/compat/plan-greeting/src/greet.js +2 -0
  158. package/examples/compat/plan-greeting/test/greet.test.js +8 -0
  159. package/examples/compat/reverse-grep-edit/PLAN.md +32 -0
  160. package/examples/compat/reverse-grep-edit/broken.diff +12 -0
  161. package/examples/compat/reverse-grep-edit/good.diff +12 -0
  162. package/examples/compat/reverse-grep-edit/package.json +5 -0
  163. package/examples/compat/reverse-grep-edit/src/strings.js +10 -0
  164. package/examples/compat/reverse-grep-edit/test/strings.test.js +15 -0
  165. package/examples/issue-triage-js/PLAN.md +83 -0
  166. package/examples/issue-triage-js/README.md +76 -0
  167. package/examples/issue-triage-js/package.json +9 -0
  168. package/examples/issue-triage-js/src/issue-triage.js +87 -0
  169. package/examples/issue-triage-js/test/issue-triage.test.js +107 -0
  170. package/package.json +70 -0
@@ -0,0 +1,751 @@
1
+ # Provider Adapters
2
+
3
+ Neal's writer-run provider layer is an internal extension point for built-in
4
+ agent SDK adapters. A provider adapter maps one SDK's sessions, tools,
5
+ structured-output behavior, errors, usage data, and stream/message events into
6
+ Neal-owned contracts.
7
+
8
+ Neal currently ships built-in adapters for:
9
+
10
+ - `openai-codex`
11
+ - `anthropic-claude`
12
+ - `openai-compatible` (structured-advisor/reviewer roles only, backed by the
13
+ AI SDK's OpenAI-compatible chat provider; see
14
+ [OpenAI-Compatible Endpoints](#openai-compatible-endpoints))
15
+ - `generic-agentic` (Neal-owned agentic writer for any OpenAI-compatible
16
+ tool-calling endpoint; coder-first, with a tool-capable read-only
17
+ structured-advisor/reviewer path; see
18
+ [Generic Agentic Writer](#generic-agentic-writer))
19
+
20
+ Neal does not currently load external provider plugins or discover
21
+ npm-installed providers. Public `neal review` is a separate read-only findings
22
+ flow that uses configured provider adapters; it is not a writer-run mode and is
23
+ not a registered provider id.
24
+
25
+ `neal setup` can inspect local built-in provider runtime availability while
26
+ writing explicit writer-run defaults. That detection is intentionally local and
27
+ non-connectivity-based: it checks runtime surfaces such as the OpenAI Codex SDK
28
+ entrypoint and Claude SDK-bundled or standalone executable paths, but it does
29
+ not authenticate, send prompts, collect secrets, or verify account state. Use
30
+ `neal check` after setup for the live provider connectivity prompt.
31
+
32
+ ## Contract Overview
33
+
34
+ Provider contracts live in `src/neal/providers/types.ts`. A built-in provider
35
+ module should export a `NealProviderDefinition` plus any focused test hooks it
36
+ needs.
37
+
38
+ The provider definition includes:
39
+
40
+ - `id`: the provider id used in config and run state.
41
+ - `displayName`: a human-readable provider name.
42
+ - `capabilities`: role-by-role capability declarations.
43
+ - `createCoderAdapter`: optional factory for coder turns.
44
+ - `createStructuredAdvisorAdapter`: optional factory for structured-advisor
45
+ turns.
46
+
47
+ Registered provider definitions are owned by `src/neal/providers/registry.ts`.
48
+ Config parsing, run-state hydration, adapter lookup, and writer-run capability
49
+ checks all resolve provider identity through that registry.
50
+
51
+ ## Roles
52
+
53
+ Neal exposes three effective agent roles for writer runs:
54
+
55
+ - `planner`: writes and revises plan artifacts through the coder adapter surface.
56
+ It inherits the coder provider/model unless configured explicitly.
57
+ - `coder`: runs ordinary implementation turns and may receive an optional
58
+ structured response schema.
59
+ - `reviewer`: runs schema-oriented rounds for review, plan review, support, and
60
+ final completion review work through the structured-advisor adapter surface.
61
+
62
+ Provider capability roles remain `coder` and `structured-advisor`; `planner` is
63
+ a Neal role label backed by the configured provider's coder adapter. The same
64
+ provider may support both capability roles, or only one. The checked-in config
65
+ template and first-run setup examples use `openai-codex` for the coder role and
66
+ `anthropic-claude` for the reviewer role, but fresh writer runs still require
67
+ explicit coder and reviewer provider config from `neal setup` or config files.
68
+ Final completion summary currently uses the configured coder provider through
69
+ the structured-advisor adapter, so a writer-ready coder provider must support
70
+ both coder and structured-advisor paths.
71
+
72
+ ## Capabilities
73
+
74
+ Each provider capability role declares:
75
+
76
+ - whether the role is supported
77
+ - read, write, and shell tool access
78
+ - session resume support
79
+ - model override support
80
+ - Neal structured control protocol support
81
+ - usage reporting support
82
+
83
+ Capabilities are enforced before writer work starts or resumes. Neal requires
84
+ the planner and coder effective roles to resolve to providers with the coder
85
+ capability, write and shell access, and Neal structured control protocol support.
86
+ The reviewer effective role must resolve to a provider with the
87
+ structured-advisor capability and either read tool access or structured-output
88
+ support that lets Neal inline the review context (full diff, plan document, and
89
+ run artifacts) directly into reviewer prompts. Session resume support is
90
+ required when a persisted session handle is present.
91
+
92
+ Current built-in capabilities are intentionally conservative:
93
+
94
+ - OpenAI Codex supports coder and structured-advisor roles. The coder role is
95
+ read, write, and shell capable because the current SDK configuration uses
96
+ broad local access. The structured-advisor (reviewer) role is read capable
97
+ but never write or shell capable (see
98
+ [The Read-Only Reviewer Invariant](#the-read-only-reviewer-invariant)).
99
+ - Anthropic Claude supports coder and structured-advisor roles. The coder role
100
+ is read, write, and shell capable. The structured-advisor (reviewer) role is
101
+ read capable but never write or shell capable.
102
+ - OpenAI-Compatible supports only the structured-advisor role, with no read,
103
+ write, or shell tool access and no session resume. Neal compensates by
104
+ inlining reviewer context into prompts; see
105
+ [OpenAI-Compatible Endpoints](#openai-compatible-endpoints).
106
+ - Generic Agentic supports coder and structured-advisor roles. The coder role
107
+ is read, write, and shell capable through Neal-owned tools executed locally.
108
+ The structured-advisor role is read capable (a read-only toolset; no write
109
+ or shell access), so a generic reviewer inspects the repository directly
110
+ with read tools instead of receiving Neal-inlined context. Neither role
111
+ supports session resume. See
112
+ [Generic Agentic Writer](#generic-agentic-writer).
113
+
114
+ The `coder` capability describes adapter paths, not a global promise that every
115
+ writer-run workflow is read-only: writer-run coder turns use provider SDKs with
116
+ broad local permissions. The separate public `neal review` command enforces its
117
+ own read-only boundary in the review command flow.
118
+
119
+ ### Coder Tool Policy Enforcement
120
+
121
+ Plan-authoring rounds pass a `toolPolicy` (`allowedWritePaths` restricted to
122
+ the plan document, `allowRun: false`) to the coder adapter. Enforcement
123
+ strength is adapter-specific and mechanical where each SDK allows it:
124
+
125
+ - `generic-agentic`: full jail. The policy selects the Neal-owned plan-author
126
+ toolset — writes only to the allowlisted paths, no shell tool at all.
127
+ - `anthropic-claude`: tool exclusion plus a path callback. `allowRun: false`
128
+ removes Bash from the turn's tools list, and a PreToolUse hook denies any
129
+ write-class tool call whose resolved path is not allowlisted.
130
+ - `openai-codex`: sandbox downgrade only. The jailed turn's thread runs under
131
+ the `workspace-write` sandbox instead of `danger-full-access`. The Codex SDK
132
+ has no per-tool-call hook, so path-level confinement is not enforceable:
133
+ shell still runs inside the sandbox and can write any workspace path, not
134
+ just the plan document.
135
+
136
+ Rounds without a `toolPolicy` are unaffected on every adapter: ordinary coder
137
+ scope rounds keep full access because verification legitimately runs commands.
138
+
139
+ ### The Read-Only Reviewer Invariant
140
+
141
+ A reviewer exists to *judge*, never to *modify* or *re-execute*. Neal enforces
142
+ this structurally: every *supported* `structured-advisor` capability has
143
+ `write: false` and `shell: false`. The invariant constrains only `write` and
144
+ `shell` — `read` is intentionally left unconstrained, so it stays `true` for
145
+ read-tool reviewers (`openai-codex`, `anthropic-claude`, `generic-agentic`) and
146
+ `false` for the inline-context reviewer (`openai-compatible`, which reviews from
147
+ Neal-inlined context rather than read tools).
148
+
149
+ `src/neal/providers/registry.ts` asserts this over every registered provider
150
+ definition — built-in or test-registered — at the definition-resolution
151
+ chokepoint and at test registration time, so no provider definition can ever
152
+ declare a writing or shell-running reviewer. The assertion checks only the
153
+ `write`/`shell` half; it never constrains `read`, and it never touches the
154
+ `coder` capability (coders keep `write`/`shell`).
155
+
156
+ Two consequences follow from no reviewer holding shell access:
157
+
158
+ - Reviewers never re-run tests or other verification. Verification happens once,
159
+ performed by the coder; the reviewer trusts the coder's reported result plus
160
+ its own reading of the diff and (for read-tool reviewers) the repository.
161
+ - Reviewers never mutate the checkout. A review produces a verdict and findings,
162
+ not edits.
163
+
164
+ Usage reporting is `opportunistic`: providers emit usage only when the SDK
165
+ event or result supplies it.
166
+
167
+ ### Review Doctrine Access Modes
168
+
169
+ Reviewer prompts render Neal's shared review doctrine in one of three access
170
+ modes, derived from the configured reviewer provider's structured-advisor
171
+ tool access:
172
+
173
+ - `read-only` (read access without shell access; `openai-codex`,
174
+ `anthropic-claude`, and `generic-agentic` today): the reviewer is instructed
175
+ to inspect the repository through read tools only — reading changed files and
176
+ searching the tree — and is never instructed to run commands, run tests, or
177
+ use scratch directories. Commit-range visibility depends on whether the
178
+ provider exposes its own range-diff tool:
179
+ - `generic-agentic` declares a read-only `git_diff` commit-range tool
180
+ (`providesRangeDiffTool`), so its reviewer inspects the range with that tool
181
+ and receives no inlined diff.
182
+ - native read-only reviewers such as `openai-codex` and `anthropic-claude`
183
+ have read and search tools but no commit-range diff tool, so Neal inlines
184
+ the commit-range diff into the reviewer prompt as the source of truth for
185
+ what the range changed (including deletions and renames that head-state file
186
+ reads cannot reveal), and the reviewer uses its read/search tools to verify
187
+ the surrounding code.
188
+
189
+ In both branches the doctrine states that absence from a diff is not evidence
190
+ of absence from the repository: the reviewer must open files with read tools
191
+ before claiming a missing import or declaration.
192
+ - `no-read` (no repository read access; `openai-compatible` today): Neal
193
+ inlines the review context — the full diff, plan document content, review
194
+ history, and run artifacts, with per-section truncation caps — directly
195
+ into reviewer prompts, and the doctrine never instructs repository, file,
196
+ tool, or shell access.
197
+ - `tool-access` (read and shell access): a reviewer is instructed to inspect
198
+ and execute against the repository directly. Because the read-only reviewer
199
+ invariant forbids any supported `structured-advisor` capability from holding
200
+ shell access (see
201
+ [The Read-Only Reviewer Invariant](#the-read-only-reviewer-invariant)), no
202
+ built-in reviewer resolves to this mode today; it would require a
203
+ shell-capable reviewer, which the registry assertion rejects.
204
+
205
+ Neither shell-less read-tool reviewers nor the inline-context reviewer are ever
206
+ instructed to run verification: the doctrine reflects that verification is the
207
+ coder's, trusted by the reviewer's reading or inlined context. Write access does
208
+ not affect review doctrine: reviews never instruct repository mutation, and no
209
+ supported reviewer capability declares write access in any case.
210
+
211
+ ## Runtime Turns
212
+
213
+ Provider adapters receive Neal-owned turn arguments rather than reading Neal
214
+ configuration directly.
215
+
216
+ Plain coder turns receive:
217
+
218
+ - `cwd`
219
+ - `prompt`
220
+ - `inactivityTimeoutMs`
221
+ - optional `resumeHandle`
222
+ - optional structured `outputSchema`, retained only for raw coder prompt
223
+ compatibility outside Neal product control paths
224
+ - optional `onSessionStarted` callback
225
+ - optional provider event sink
226
+
227
+ Structured coder turns receive:
228
+
229
+ - `label`
230
+ - `cwd`
231
+ - `prompt`
232
+ - `schema`
233
+ - `inactivityTimeoutMs`
234
+ - optional `resumeHandle`
235
+ - optional `onSessionStarted` callback
236
+ - optional provider event sink
237
+
238
+ Structured-advisor turns receive:
239
+
240
+ - `label`
241
+ - `cwd`
242
+ - `prompt`
243
+ - `schema`
244
+ - `inactivityTimeoutMs`
245
+ - `apiRetryLimit`
246
+ - optional `model`
247
+ - optional `resumeHandle`
248
+ - optional provider event sink
249
+
250
+ The orchestration and round layers own timeout/retry configuration lookup,
251
+ schema validation, state transitions, review decisions, source-control policy,
252
+ and terminal narration policy. Provider adapters own SDK initialization,
253
+ message or stream collection, SDK session mapping, SDK error normalization, and
254
+ SDK telemetry mapping.
255
+
256
+ All turn argument shapes also accept an optional `signal` (`AbortSignal`).
257
+ Adapters must treat an aborted signal as a request to terminate the in-flight
258
+ SDK turn and surface a normalized provider error rather than hanging.
259
+
260
+ ### Turn Liveness
261
+
262
+ Neal layers two independent timeouts over every provider turn:
263
+
264
+ - `neal.agent_turn_startup_timeout_ms` (default `300000`) bounds startup
265
+ silence: the window after a turn starts during which the provider has shown
266
+ no observable progress (no tool use, commands, file changes, assistant text,
267
+ structured output, or usage). `session_started` and `turn_started` keep the
268
+ transport alive but do not count as progress.
269
+ - `neal.inactivity_timeout_ms` (default `600000`) bounds active-work idle time
270
+ and is owned by the provider adapters. Once a turn shows any observable
271
+ progress, the startup timer disarms permanently and only this timeout
272
+ applies.
273
+
274
+ When the startup timer fires, Neal aborts the silent attempt via the turn's
275
+ `signal` and retries the same turn up to `neal.agent_turn_retry_limit` times
276
+ (default `1`). This means Neal may rerun a turn even though the provider never
277
+ reported an error: the provider went silent before doing any work, which is
278
+ indistinguishable from a stalled SDK stream. Retries are safe because zero
279
+ observable progress was made. Keep `agent_turn_retry_limit` low (the default of
280
+ 1 is recommended); persistent startup silence usually indicates a provider or
281
+ network problem that more retries will not fix. Coder turns that resume an
282
+ existing session are never retried by this layer; the orchestrator's existing
283
+ fresh-session recovery owns those.
284
+
285
+ Liveness outcomes are auditable in `events.ndjson` as
286
+ `provider.turn_liveness_timeout`, `provider.turn_liveness_retry`, and
287
+ `provider.turn_liveness_give_up`. Exhausted retries fail the turn with a
288
+ normalized `no_progress_timeout` error kind, distinct from the adapter-owned
289
+ `timeout` kind that covers idle time after active work.
290
+
291
+ ## Sessions
292
+
293
+ Provider session handles are opaque strings. Providers should preserve and
294
+ return the SDK session handle when one exists, including on normalized failures
295
+ where the SDK exposed a session before the failure.
296
+
297
+ Neal persists handles separately for planner, coder, and reviewer work. Resume
298
+ checks fail early if the configured provider role does not support session
299
+ resume and a persisted handle must be resumed.
300
+
301
+ ## Structured Output
302
+
303
+ Neal product control paths use `neal-json-block-v1`. Provider adapters transport
304
+ assistant text, preserve session handles, normalize SDK errors, and delegate
305
+ control-payload extraction, parsing, validation, bounded repair, telemetry, and
306
+ normalized structured-output errors to Neal's shared runtime. The
307
+ `generic-agentic` writer is the one exception: it consumes the same protocol
308
+ spec (schema, validator, labels) but fulfills it through the AI SDK's native
309
+ structured-output channel in a dedicated finalization turn, with no fenced-text
310
+ transport — see [Generic Agentic Writer](#generic-agentic-writer).
311
+
312
+ Coder adapters expose `runStructuredPrompt<TStructured>()` for Neal-owned
313
+ structured coder decisions. That method receives a caller-owned
314
+ `neal-json-block-v1` protocol spec, returns the typed `structured` object plus
315
+ the provider `sessionHandle`, emits `structured_output_received` only after
316
+ Neal validation succeeds, and normalizes missing structured control payloads as
317
+ `structured_output_missing`.
318
+
319
+ `runPrompt(..., outputSchema)` remains supported for compatibility with
320
+ older raw coder harness coverage. `neal check` and Neal-owned coder decision
321
+ rounds use `neal-json-block-v1` through `runStructuredPrompt()` rather than
322
+ parsing provider JSON strings in round code.
323
+
324
+ Neal's shared `neal-json-block-v1` runtime appends provider-neutral transport
325
+ instructions requiring optional useful prose followed by exactly one final
326
+ fenced `neal-json` JSON block. It then extracts, parses, validates, and repairs
327
+ that control object with the caller-supplied schema label, schema, validator,
328
+ and repair limit. Raw whole-response JSON objects are accepted only as a
329
+ compatibility tolerance for older mocks and pre-migration paths; the prompt
330
+ contract remains prose plus one final `neal-json` block. State-facing
331
+ `structured_output_received` telemetry is emitted only after Neal validation
332
+ succeeds.
333
+
334
+ Anthropic Claude and OpenAI Codex structured coder and structured-advisor
335
+ protocol calls do not pass SDK-native `outputFormat` or `outputSchema`. Those
336
+ SDK-native surfaces are compatibility details for non-Neal raw provider calls,
337
+ not the Neal product protocol. Invalid or missing local JSON can recover through
338
+ a bounded repair turn. Repair turns are side-effect-free by prompt; Claude
339
+ repair turns also omit repo tools and session resume, and Codex repair turns run
340
+ through the most restrictive available streamed-turn options.
341
+
342
+ ## Telemetry
343
+
344
+ Provider modules emit provider-neutral `ProviderRuntimeEvent` values through the
345
+ turn's event sink. They do not call terminal diagnostic APIs or `RunLogger`
346
+ directly.
347
+
348
+ Normalized event types include:
349
+
350
+ - `session_started`
351
+ - `turn_started`
352
+ - `turn_completed`
353
+ - `tool_started`
354
+ - `tool_progress`
355
+ - `command_completed`
356
+ - `file_changed`
357
+ - `assistant_text`
358
+ - `structured_output_received`
359
+ - `usage_reported`
360
+ - `provider_error`
361
+
362
+ `src/neal/providers/telemetry.ts` adapts those events into `provider.*`
363
+ `events.ndjson` records and low-level detail output. Command completion events
364
+ carry enough metadata for verification summaries, including command, status,
365
+ exit code, output length, working directory, and git head when available. Raw
366
+ SDK details should remain contained under `providerData` when they are useful
367
+ for debugging.
368
+
369
+ ## Errors
370
+
371
+ Errors leaving provider adapters should be `NealProviderError` instances. Core
372
+ round code should not import SDK-specific provider error classes.
373
+
374
+ Normalized error kinds are:
375
+
376
+ - `timeout`
377
+ - `no_progress_timeout` (emitted by Neal's turn liveness supervisor, not by
378
+ adapters; see Turn Liveness above)
379
+ - `api_error`
380
+ - `structured_output_missing`
381
+ - `structured_output_invalid`
382
+ - `permission_denied`
383
+ - `session_unavailable`
384
+ - `provider_failed`
385
+ - `unknown`
386
+
387
+ Each normalized error carries the provider id, role, optional session handle,
388
+ kind, retryability, and cause. Round code wraps normalized provider errors into
389
+ round-level failures only when it needs round-specific context.
390
+
391
+ ## Status-Visible Errors
392
+
393
+ Provider failures are visible to automation through `neal status --json` and
394
+ `neal status --json --all` as `providerError`. Neal derives that summary from
395
+ the latest `provider.provider_error` event when present, including timestamp,
396
+ provider id, role, label, session handle, normalized kind, bounded message, and
397
+ retryability. If a run failed before a provider event was available, status may
398
+ fall back to the latest `phase.error` as an unclassified run error with
399
+ `provider`, `role`, and `kind` set to `null`.
400
+
401
+ `providerError` is classification metadata, not a raw provider transcript. It
402
+ must not include SDK payloads, full prompts, provider `providerData`, full
403
+ assistant text, raw command output, credentials, or environment dumps. Keep rich
404
+ diagnostics in local support artifacts such as `events.ndjson` and
405
+ `stderr.log`; public wrappers should use the bounded status summary.
406
+
407
+ ## Model Overrides
408
+
409
+ Provider factories receive an optional model override from `agent.*.model`.
410
+ `model: null` means Neal lets the provider choose its default model. A non-null
411
+ model is accepted only when the provider role declares `supportsModelOverride`.
412
+
413
+ Model selection stays provider-local after the registry passes the configured
414
+ override into the adapter factory.
415
+
416
+ ## Reasoning Effort Overrides
417
+
418
+ Each role accepts an optional reasoning-effort override from `agent.*.effort`.
419
+ `effort: null` (or an omitted key) means Neal sends no effort option and the
420
+ provider uses its default reasoning depth. A non-null effort is mapped to the
421
+ closest provider-native option: OpenAI Codex receives
422
+ `ThreadOptions.modelReasoningEffort` and Anthropic Claude receives
423
+ `Options.effort`.
424
+
425
+ Supported values are provider-specific:
426
+
427
+ - `openai-codex`: `minimal`, `low`, `medium`, `high`, `xhigh`.
428
+ - `anthropic-claude`: `low`, `medium`, `high`, `xhigh`, `max`.
429
+ - `openai-compatible`: no supported values; any configured `effort` is rejected
430
+ with the standard effort config error.
431
+ - `generic-agentic`: no supported values; any configured `effort` is rejected
432
+ with the standard effort config error.
433
+
434
+ Effort support is declared per capability role. An effort value the configured
435
+ provider does not support is rejected before a writer run starts or resumes with
436
+ a role-specific config error that lists the supported values.
437
+
438
+ Planner effort inheritance mirrors planner model inheritance: the planner
439
+ inherits the coder effort only when `agent.planner.provider` is not configured
440
+ explicitly and `agent.planner.effort` is omitted. If `agent.planner.provider`
441
+ is set explicitly, an omitted `agent.planner.effort` resolves to provider
442
+ default (`null`) rather than inheriting the coder effort, so set
443
+ `agent.planner.effort` explicitly to override it.
444
+
445
+ ## OpenAI-Compatible Endpoints
446
+
447
+ The `openai-compatible` provider lets the reviewer role run against any
448
+ OpenAI-compatible Chat Completions endpoint (DeepSeek, OpenRouter, vLLM, and
449
+ similar). It is API-only and covers structured-advisor work: reviewer rounds
450
+ for scope review, plan review, support, final completion, provider check, and
451
+ `neal review` findings adjudication.
452
+
453
+ The adapter is implemented on the AI SDK's OpenAI-compatible chat provider —
454
+ the same substrate the `generic-agentic` writer uses — with the external
455
+ contract unchanged: same provider id, same config block, same chat-only
456
+ capabilities, same normalized error kinds and retryability, same provider
457
+ events. Gateway behaviors learned from live failures are preserved through a
458
+ Neal-owned response interceptor: upstream errors embedded as an `error`
459
+ object inside an HTTP 200 body keep their status-based retryability
460
+ (OpenRouter wraps upstream 429s this way), and reasoning-model responses
461
+ whose final text arrives in `message.reasoning` / `message.reasoning_content`
462
+ instead of `message.content` fall back to that text. Usage events report the
463
+ AI SDK's normalized shape (`inputTokens` / `outputTokens` / `totalTokens`
464
+ plus detail fields), with the raw response usage object preserved under
465
+ `usage.raw`.
466
+
467
+ Role restrictions and behavior:
468
+
469
+ - Structured-advisor/reviewer roles only. There is no coder or planner
470
+ support; selecting `openai-compatible` for those roles fails the capability
471
+ check with a clear error before any run starts.
472
+ - No tool access of any kind (`read`, `write`, and `shell` are all declared
473
+ false). Neal inlines the review context — the full diff, plan document
474
+ content, review history, and run artifacts, with per-section truncation
475
+ caps — directly into reviewer prompts, and reviewer prompts render the
476
+ `no-read` review doctrine mode (see
477
+ [Review Doctrine Access Modes](#review-doctrine-access-modes)). The
478
+ tradeoff: an inline-context reviewer cannot independently run verification
479
+ commands or read beyond the inlined sections, so review quality depends on
480
+ the inlined context. For tool-calling models, the `generic-agentic`
481
+ reviewer path avoids that tradeoff; see
482
+ [Generic Agentic Writer](#generic-agentic-writer).
483
+ - No session resume. The adapter never returns a resumable session handle, so
484
+ no reviewer session state is persisted for it.
485
+ - No `effort` support. Any configured `agent.reviewer.effort` for this
486
+ provider is rejected with the standard effort config error.
487
+ - Structured output uses Neal's `neal-json-block-v1` protocol and bounded
488
+ repair loop, not provider-specific `response_format` or strict JSON schema.
489
+ - Requests are non-streaming Chat Completions; `neal.inactivity_timeout_ms`
490
+ acts as the whole-request timeout. Model quality, rate limits, and exact API
491
+ behavior vary by upstream provider.
492
+
493
+ ### Configuration
494
+
495
+ Add a `providers.openai_compatible` block (repo `config.yml` overrides
496
+ `~/.neal/config.yml`, matching normal config precedence) and point the
497
+ reviewer role at the provider. A worked DeepSeek example:
498
+
499
+ ```yaml
500
+ providers:
501
+ openai_compatible:
502
+ base_url: https://api.deepseek.com
503
+ api_key_env: DEEPSEEK_API_KEY
504
+ default_model: deepseek-chat
505
+
506
+ agent:
507
+ coder:
508
+ provider: openai-codex
509
+ model: null
510
+ reviewer:
511
+ provider: openai-compatible
512
+ model: deepseek-chat
513
+ ```
514
+
515
+ Settings resolve config-first with environment fallbacks:
516
+
517
+ - `base_url`: `providers.openai_compatible.base_url`, else
518
+ `OPENAI_COMPATIBLE_BASE_URL`. Required.
519
+ - `api_key_env`: `providers.openai_compatible.api_key_env`, else the default
520
+ `OPENAI_COMPATIBLE_API_KEY`. The API key value is read from that named
521
+ environment variable; Neal never stores the key itself.
522
+ - model: the role-level `agent.reviewer.model` override, else
523
+ `providers.openai_compatible.default_model`, else `OPENAI_COMPATIBLE_MODEL`.
524
+ One of these is required.
525
+ - `headers`: optional string-to-string map of extra HTTP headers (useful for
526
+ OpenRouter attribution headers).
527
+
528
+ `neal setup` offers `openai-compatible` for the reviewer role only and prints
529
+ guidance when the base URL, API key, or model is unresolved. `neal setup`
530
+ detection reports which config keys or environment variables are missing
531
+ without making network calls.
532
+
533
+ ### Live Smoke Check (optional)
534
+
535
+ The default test suite never requires live credentials or network access. To
536
+ verify a real endpoint end to end, set credentials and run `neal check` with
537
+ the reviewer role configured for `openai-compatible` — the `provider-check`
538
+ structured round exercises the adapter against the live endpoint:
539
+
540
+ ```sh
541
+ OPENAI_COMPATIBLE_BASE_URL=https://api.deepseek.com \
542
+ OPENAI_COMPATIBLE_API_KEY=... \
543
+ OPENAI_COMPATIBLE_MODEL=deepseek-chat \
544
+ pnpm start -- check
545
+ ```
546
+
547
+ ## Generic Agentic Writer
548
+
549
+ The `generic-agentic` provider runs the coder role (and the inherited planner
550
+ role) against any OpenAI-compatible Chat Completions endpoint with a strong
551
+ tool-calling model (OpenRouter, vLLM, DeepSeek, and similar). Unlike the
552
+ API-only `openai-compatible` reviewer adapter, this provider is a real writer:
553
+ Neal owns the agentic loop (one model turn per request, with a Neal-owned
554
+ outer loop over the message history) and supplies its own tool set —
555
+ `read_file`, `write_file`, `edit_file`, `list_dir`, `grep`, and `run` — so any
556
+ endpoint whose model can call tools reliably can serve as the coder, and,
557
+ through a read-only subset of the same tools, as the reviewer.
558
+
559
+ Because endpoint quality varies, qualify a generic-agentic model with
560
+ [`neal compat`](compat.md) before trusting it with real work: it drives the
561
+ candidate through complete neal runs against trivial bundled fixtures and emits
562
+ a binary PASS/FAIL per (model, role). `neal check` proves only connectivity and
563
+ basic protocol; `neal compat` proves the full loop.
564
+
565
+ Role support and behavior:
566
+
567
+ - Coder-first. The coder capability role is read, write, and shell capable;
568
+ the planner inherits the coder provider unless configured explicitly.
569
+ - Tool-capable reviewer. The structured-advisor role runs the same
570
+ Neal-owned loop over a read-only toolset — `read_file`, `list_dir`,
571
+ `grep`, and the read-only `git_diff` commit-range inspector — so a generic
572
+ reviewer inspects the repository directly under the `read-only` review
573
+ doctrine mode (see
574
+ [Review Doctrine Access Modes](#review-doctrine-access-modes)) instead of
575
+ receiving Neal-inlined context. `git_diff` is a Neal-owned fixed-argv git
576
+ query (never a shell string) that gives the reviewer actual commit-range
577
+ visibility, including deletions and renames that head-state file reads
578
+ cannot reveal. After the read-tool loop completes with a zero-tool-call
579
+ turn, the structured payload is requested in a single dedicated
580
+ finalization turn that carries no tools. The same adapter also serves the
581
+ final-completion summary gate when `generic-agentic` is the coder.
582
+ - No session resume (`supportsSessionResume: false` on both roles). The
583
+ adapter never persists a provider session handle, so `neal resume` after an
584
+ interruption or failure restarts the interrupted scope from scratch in a
585
+ fresh session rather than resuming mid-conversation. Already-accepted
586
+ scopes stay committed; only the in-flight scope is redone. Top-level
587
+ plan-refinement revision rounds likewise start a fresh planner session per
588
+ round instead of resuming the planning conversation.
589
+ - Step caps: each coder prompt's tool loop is bounded by the exported
590
+ `GENERIC_AGENTIC_MAX_STEPS` constant (currently `48` model turns per
591
+ prompt); structured-advisor/reviewer rounds use the smaller exported
592
+ `GENERIC_AGENTIC_ADVISOR_MAX_STEPS` constant (currently `24` — reviews are
593
+ bounded inspections, not implementations). Reaching either cap fails the
594
+ attempt with a non-retryable `provider_failed` error naming the cap. There
595
+ are no config knobs for the caps.
596
+ - Reviewer telemetry: advisor rounds report cumulative per-tool call and
597
+ error counts plus a `steps` count (model turns consumed) under
598
+ `providerData` on `turn_completed` / `usage_reported` events, so
599
+ tool-turns-per-review is auditable from `events.ndjson` when qualifying a
600
+ model for reviewer duty.
601
+ - No `effort` support. Any configured `effort` for this provider is rejected
602
+ with the standard effort config error.
603
+ - Structured output is SDK-native. After the tool loop's normal
604
+ zero-tool-call completion turn, the adapter makes exactly one dedicated
605
+ finalization turn (no tools) through the AI SDK's `Output.object`
606
+ structured-output channel and validates the result against the protocol
607
+ schema. There is no fenced-JSON text protocol on this provider and no
608
+ second chance for a malformed payload: a finalization failure is a single
609
+ non-retryable error — kind `structured_output_missing` when the model
610
+ produced no parseable object, or `structured_output_invalid` when the
611
+ object failed validation — with a truncated excerpt of the rejected text
612
+ preserved under the `provider_error` event's `providerData.diagnostic`, so
613
+ failed runs stay classifiable. Tool inputs are likewise strict: arguments
614
+ must match the tool's schema as sent, with no coercion of stringified
615
+ JSON. This failure taxonomy is the qualification signal for candidate
616
+ models — a model that cannot pass `neal check` on this provider is not
617
+ pool-eligible.
618
+ - Transient API failures (408/429/5xx, network errors, and empty
619
+ HTTP-200 responses) are retried with bounded backoff and surfaced as
620
+ `api_retry` progress events, matching the `openai-compatible` adapter's
621
+ behavior.
622
+
623
+ Trust level is identical to the vendor writer providers: the `run` tool
624
+ executes arbitrary shell commands in the scope working tree with no sandbox.
625
+ The path jail on file-tool arguments keeps `read_file`, `write_file`,
626
+ `edit_file`, `list_dir`, and `grep` inside the repository (including through
627
+ symlinks), but it does not contain what `run` can do. Apply the same
628
+ disposable-checkout guidance as for the other writer providers.
629
+ Structured-advisor rounds never receive `write_file`, `edit_file`, or `run`;
630
+ the read-only toolset is jailed the same way.
631
+
632
+ Choosing a reviewer adapter for generic endpoints: prefer `generic-agentic`
633
+ when the endpoint's model calls tools reliably — the reviewer reads the
634
+ actual repository and commit range with bounded, targeted tool calls instead
635
+ of judging a truncation-capped inlined diff, and reviewer prompts stay small.
636
+ Prefer `openai-compatible` for genuinely chat-only endpoints or models whose
637
+ tool calling is unreliable; the inline-context path remains correct and fully
638
+ supported.
639
+
640
+ Model guidance: use a paid tool-calling slug for coder duty. Rate-limited
641
+ `:free` pools are acceptable for reviewer-only use but are not suitable as
642
+ coders. Model qualification runs and a tested-models table are future work;
643
+ until that lands, treat models other than `qwen/qwen3-coder` (validated
644
+ end-to-end as the coder) and `openai/gpt-oss-120b` (validated live as the
645
+ read-only reviewer and as the inline-context reviewer) as unqualified and
646
+ trial them on a disposable project first.
647
+
648
+ ### Configuration
649
+
650
+ Settings reuse the `providers.openai_compatible` block — there is no separate
651
+ `providers.generic_agentic` config surface — with the same config-first,
652
+ environment-fallback resolution documented in
653
+ [OpenAI-Compatible Endpoints](#openai-compatible-endpoints). A worked
654
+ OpenRouter example:
655
+
656
+ ```yaml
657
+ providers:
658
+ openai_compatible:
659
+ base_url: https://openrouter.ai/api/v1
660
+ api_key_env: OPENROUTER_API_KEY
661
+ default_model: qwen/qwen3-coder
662
+
663
+ agent:
664
+ coder:
665
+ provider: generic-agentic
666
+ model: null
667
+ reviewer:
668
+ provider: anthropic-claude
669
+ model: null
670
+ ```
671
+
672
+ The planner inherits the coder provider unless configured explicitly, so this
673
+ config routes planning through `generic-agentic` too. Coder model precedence
674
+ is `agent.coder.model`, else `providers.openai_compatible.default_model`,
675
+ else `OPENAI_COMPATIBLE_MODEL`; one of these is required. Structured-advisor
676
+ rounds additionally honor a Neal-internal round-level model override first,
677
+ matching the `openai-compatible` adapter.
678
+
679
+ ## Adding A Built-In Provider
680
+
681
+ To add another built-in provider:
682
+
683
+ 1. Implement a provider module under `src/neal/providers/`.
684
+ 2. Export a `NealProviderDefinition`.
685
+ 3. Register the definition in `src/neal/providers/registry.ts`.
686
+ 4. Map SDK sessions and resume handles to opaque Neal handles.
687
+ 5. Map SDK stream or message events to `ProviderRuntimeEvent` values.
688
+ 6. Normalize SDK errors to `NealProviderError`.
689
+ 7. Add focused tests for event mapping, capabilities, Neal structured control
690
+ protocol behavior, session handles, and normalized errors.
691
+ 8. Document SDK prerequisites, environment assumptions, supported roles,
692
+ capabilities, and limitations.
693
+
694
+ Use `test/helpers/fake-provider.ts` for registry and capability tests that do
695
+ not need a real SDK. SDK adapter tests should stay focused on provider-specific
696
+ event collection, structured-output parsing, retry behavior, and error
697
+ normalization.
698
+
699
+ ## SDK Prerequisites
700
+
701
+ Provider documentation should state the SDK package, required credentials or
702
+ login state, model assumptions, filesystem/tool permission assumptions, and any
703
+ environment variables the SDK needs.
704
+
705
+ Current built-in writer providers run with broad local permissions:
706
+
707
+ - OpenAI Codex uses `approvalPolicy: never` and
708
+ `sandboxMode: danger-full-access`.
709
+ - Anthropic Claude uses `permissionMode: bypassPermissions`.
710
+ - OpenAI-Compatible is the exception: it makes HTTP API calls only and has no
711
+ local filesystem or shell access.
712
+ - Generic Agentic makes HTTP API calls for model turns but executes Neal-owned
713
+ tools locally; the coder role's `run` tool executes shell commands with no
714
+ sandbox, matching the vendor writers' trust level. Structured-advisor
715
+ rounds execute only the read-only toolset locally.
716
+
717
+ Those settings are part of current writer-run behavior. Do not narrow or widen
718
+ them as part of provider registration unless a separate product change requires
719
+ it.
720
+
721
+ ## Review Boundary
722
+
723
+ Public `neal review` uses a separate read-only findings loop:
724
+
725
+ - `src/neal/review-findings/provider.ts`
726
+
727
+ The default adapter uses the configured coder provider to draft structured
728
+ findings and the configured reviewer structured-advisor adapter to adjudicate
729
+ them. The command is still not a writer-run mode: it does not create
730
+ `RUN_STATE.json`, does not mutate `.neal/current.json` or queue pointers, and
731
+ writes isolated artifacts under `.neal/reviews/<review-id>/`.
732
+
733
+ Two distinct read-only guarantees apply here, and they should not be conflated:
734
+
735
+ - The provider-capability invariant (see
736
+ [The Read-Only Reviewer Invariant](#the-read-only-reviewer-invariant)):
737
+ every supported `structured-advisor` capability has `write: false` and
738
+ `shell: false`, so a writer-run reviewer/structured-advisor round cannot
739
+ write or run shell by capability — this holds for both writer runs and
740
+ `neal review`.
741
+ - The `neal review` command's additional artifact-boundary guard: the command
742
+ checks protected writer state and worktree changes after provider rounds and
743
+ fails if anything outside its review artifacts changed. This post-round guard
744
+ is owned by the `neal review` command flow, not by a provider capability, and
745
+ is specific to `neal review`; a writer run does not perform this artifact
746
+ diff.
747
+
748
+ So the capability invariant guarantees reviewers never write or run shell in
749
+ any mode, while `neal review` adds its own command-level enforcement on top.
750
+ Do not treat the `neal review` artifact-boundary guard as a provider capability,
751
+ and do not assume a writer run performs that post-round artifact check.