@navels/neal 0.1.0 → 0.3.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 (120) hide show
  1. package/README.md +206 -209
  2. package/SECURITY.md +16 -19
  3. package/dist/neal/adjudicator/{blocked-adjudicator.js → consultant.js} +21 -21
  4. package/dist/neal/adjudicator/execute.js +33 -33
  5. package/dist/neal/adjudicator/final-completion.js +2 -31
  6. package/dist/neal/adjudicator/planning.js +264 -24
  7. package/dist/neal/agents/prompts.js +7 -15
  8. package/dist/neal/agents/rounds.js +51 -73
  9. package/dist/neal/agents/schemas.js +615 -502
  10. package/dist/neal/agents/structured-json.js +36 -0
  11. package/dist/neal/blocked-guidance.js +16 -9
  12. package/dist/neal/cli.js +2 -2
  13. package/dist/neal/commands/check.js +2 -2
  14. package/dist/neal/commands/compat.js +100 -36
  15. package/dist/neal/commands/interactive-activity.js +2 -2
  16. package/dist/neal/commands/recovery-guidance.js +20 -8
  17. package/dist/neal/commands/runtime.js +4 -2
  18. package/dist/neal/config.js +40 -7
  19. package/dist/neal/context/inline-review-context.js +13 -28
  20. package/dist/neal/context/reviewer-context.js +19 -14
  21. package/dist/neal/eval/reviewer-eval-manifest.js +86 -0
  22. package/dist/neal/eval/reviewer-eval.js +155 -0
  23. package/dist/neal/git.js +9 -3
  24. package/dist/neal/interactive-controls.js +5 -0
  25. package/dist/neal/orchestrator/completion.js +215 -113
  26. package/dist/neal/orchestrator/failures.js +7 -0
  27. package/dist/neal/orchestrator/notifications.js +9 -9
  28. package/dist/neal/orchestrator/phases/planning.js +124 -112
  29. package/dist/neal/orchestrator/phases/recovery.js +151 -115
  30. package/dist/neal/orchestrator/phases/review.js +28 -1
  31. package/dist/neal/orchestrator/split-plan.js +12 -11
  32. package/dist/neal/orchestrator/transitions.js +36 -71
  33. package/dist/neal/orchestrator.js +3 -0
  34. package/dist/neal/plan-doc.js +24 -1
  35. package/dist/neal/plan-queue.js +29 -1
  36. package/dist/neal/prompts/assert-builder.js +8 -1
  37. package/dist/neal/prompts/execute.js +11 -19
  38. package/dist/neal/prompts/planning.js +64 -55
  39. package/dist/neal/prompts/review-doctrine.js +13 -50
  40. package/dist/neal/prompts/specialized.js +28 -22
  41. package/dist/neal/prompts/specs.js +223 -17
  42. package/dist/neal/providers/agent-settings-isolation.js +35 -0
  43. package/dist/neal/providers/anthropic-claude.js +309 -248
  44. package/dist/neal/providers/detection.js +3 -7
  45. package/dist/neal/providers/git-config-isolation.js +83 -0
  46. package/dist/neal/providers/openai-codex.js +102 -204
  47. package/dist/neal/providers/{generic-agentic-tools.js → openai-compatible-tools.js} +11 -5
  48. package/dist/neal/providers/openai-compatible.js +1079 -403
  49. package/dist/neal/providers/openrouter-routing.js +68 -0
  50. package/dist/neal/providers/pricing.js +124 -0
  51. package/dist/neal/providers/rate-card.js +2275 -0
  52. package/dist/neal/providers/registry.js +6 -17
  53. package/dist/neal/providers/telemetry.js +4 -0
  54. package/dist/neal/providers/types.js +12 -0
  55. package/dist/neal/resume-decision.js +7 -6
  56. package/dist/neal/resume-planner.js +9 -0
  57. package/dist/neal/retrospective.js +36 -7
  58. package/dist/neal/review-debt.js +30 -0
  59. package/dist/neal/review-findings/provider.js +17 -29
  60. package/dist/neal/review.js +17 -1
  61. package/dist/neal/run-metrics.js +74 -9
  62. package/dist/neal/state-invariants.js +11 -3
  63. package/dist/neal/state-views.js +27 -5
  64. package/dist/neal/state.js +59 -15
  65. package/dist/neal/status.js +28 -1
  66. package/dist/neal/support.js +3 -3
  67. package/docs/README.md +22 -18
  68. package/docs/{ADJUDICATOR_INVENTORY.md → adjudicator-inventory.md} +58 -57
  69. package/docs/architecture.md +79 -83
  70. package/docs/automation.md +11 -11
  71. package/docs/compat.md +91 -61
  72. package/docs/compatible-models.md +145 -110
  73. package/docs/demo.md +15 -5
  74. package/docs/maintenance.md +74 -32
  75. package/docs/plan-format.md +61 -33
  76. package/docs/prompt-evals.md +96 -0
  77. package/docs/{PROMPT_SPECS.md → prompt-specs.md} +52 -48
  78. package/docs/providers.md +330 -283
  79. package/docs/release.md +43 -40
  80. package/docs/review-convergence.md +246 -0
  81. package/docs/state-machine.md +61 -59
  82. package/docs/storage.md +40 -31
  83. package/docs/troubleshooting.md +32 -29
  84. package/examples/compat/add-edit-verify/PLAN.md +2 -0
  85. package/examples/compat/add-edit-verify/broken.diff +2 -2
  86. package/examples/compat/add-edit-verify/good.diff +2 -2
  87. package/examples/compat/add-edit-verify/src/add.js +1 -1
  88. package/examples/compat/manifest.json +25 -29
  89. package/examples/compat/sum-grep-edit/PLAN.md +33 -0
  90. package/examples/compat/sum-grep-edit/broken.diff +12 -0
  91. package/examples/compat/sum-grep-edit/good.diff +12 -0
  92. package/examples/compat/sum-grep-edit/package.json +5 -0
  93. package/examples/compat/sum-grep-edit/src/numbers.js +9 -0
  94. package/examples/compat/sum-grep-edit/test/numbers.test.js +15 -0
  95. package/examples/issue-triage-js/README.md +0 -15
  96. package/examples/reviewer-eval/clean-doc/base/src/clamp.ts +3 -0
  97. package/examples/reviewer-eval/clean-doc/change.diff +9 -0
  98. package/examples/reviewer-eval/clean-extract/base/src/greet.ts +3 -0
  99. package/examples/reviewer-eval/clean-extract/change.diff +13 -0
  100. package/examples/reviewer-eval/dropped-error/base/src/load.ts +4 -0
  101. package/examples/reviewer-eval/dropped-error/change.diff +15 -0
  102. package/examples/reviewer-eval/manifest.json +45 -0
  103. package/examples/reviewer-eval/off-by-one/base/src/paginate.ts +5 -0
  104. package/examples/reviewer-eval/off-by-one/change.diff +11 -0
  105. package/{config.yml → neal.yml} +1 -1
  106. package/package.json +20 -15
  107. package/dist/neal/providers/generic-agentic.js +0 -1238
  108. package/docs/comparison.md +0 -105
  109. package/examples/compat/is-even-add-test/PLAN.md +0 -30
  110. package/examples/compat/is-even-add-test/broken.diff +0 -11
  111. package/examples/compat/is-even-add-test/good.diff +0 -11
  112. package/examples/compat/is-even-add-test/package.json +0 -5
  113. package/examples/compat/is-even-add-test/src/is-even.js +0 -3
  114. package/examples/compat/is-even-add-test/test/is-even.test.js +0 -9
  115. package/examples/compat/reverse-grep-edit/PLAN.md +0 -32
  116. package/examples/compat/reverse-grep-edit/broken.diff +0 -12
  117. package/examples/compat/reverse-grep-edit/good.diff +0 -12
  118. package/examples/compat/reverse-grep-edit/package.json +0 -5
  119. package/examples/compat/reverse-grep-edit/src/strings.js +0 -10
  120. package/examples/compat/reverse-grep-edit/test/strings.test.js +0 -15
package/docs/providers.md CHANGED
@@ -1,25 +1,22 @@
1
- # Provider Adapters
1
+ # Provider adapters
2
2
 
3
- Neal's writer-run provider layer is an internal extension point for built-in
3
+ neal's writer-run provider layer is an internal extension point for built-in
4
4
  agent SDK adapters. A provider adapter maps one SDK's sessions, tools,
5
5
  structured-output behavior, errors, usage data, and stream/message events into
6
- Neal-owned contracts.
6
+ neal-owned contracts.
7
7
 
8
- Neal currently ships built-in adapters for:
8
+ neal currently ships built-in adapters for:
9
9
 
10
10
  - `openai-codex`
11
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
12
+ - `openai-compatible`, a neal-owned agent loop for any OpenAI-compatible
13
+ tool-calling endpoint, with writer tools for the coder and read-only tools
14
+ for the reviewer. See
15
+ [OpenAI-compatible provider](#openai-compatible-provider).
16
+
17
+ neal does not currently load external provider plugins or discover
21
18
  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
19
+ flow that uses configured provider adapters. It is not a writer-run mode and is
23
20
  not a registered provider id.
24
21
 
25
22
  `neal setup` can inspect local built-in provider runtime availability while
@@ -29,7 +26,7 @@ entrypoint and Claude SDK-bundled or standalone executable paths, but it does
29
26
  not authenticate, send prompts, collect secrets, or verify account state. Use
30
27
  `neal check` after setup for the live provider connectivity prompt.
31
28
 
32
- ## Contract Overview
29
+ ## Contract overview
33
30
 
34
31
  Provider contracts live in `src/neal/providers/types.ts`. A built-in provider
35
32
  module should export a `NealProviderDefinition` plus any focused test hooks it
@@ -50,7 +47,7 @@ checks all resolve provider identity through that registry.
50
47
 
51
48
  ## Roles
52
49
 
53
- Neal exposes three effective agent roles for writer runs:
50
+ neal exposes three effective agent roles for writer runs:
54
51
 
55
52
  - `planner`: writes and revises plan artifacts through the coder adapter surface.
56
53
  It inherits the coder provider/model unless configured explicitly.
@@ -59,8 +56,8 @@ Neal exposes three effective agent roles for writer runs:
59
56
  - `reviewer`: runs schema-oriented rounds for review, plan review, support, and
60
57
  final completion review work through the structured-advisor adapter surface.
61
58
 
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
59
+ Provider capability roles remain `coder` and `structured-advisor`. `planner` is
60
+ a neal role label backed by the configured provider's coder adapter. The same
64
61
  provider may support both capability roles, or only one. The checked-in config
65
62
  template and first-run setup examples use `openai-codex` for the coder role and
66
63
  `anthropic-claude` for the reviewer role, but fresh writer runs still require
@@ -77,17 +74,16 @@ Each provider capability role declares:
77
74
  - read, write, and shell tool access
78
75
  - session resume support
79
76
  - model override support
80
- - Neal structured control protocol support
77
+ - neal structured control protocol support
81
78
  - usage reporting support
82
79
 
83
- Capabilities are enforced before writer work starts or resumes. Neal requires
80
+ Capabilities are enforced before writer work starts or resumes. neal requires
84
81
  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.
82
+ capability, write and shell access, and neal structured control protocol support.
86
83
  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.
84
+ structured-advisor capability and read tool access: every reviewer inspects
85
+ the repository directly. Session resume support is required when a persisted
86
+ session handle is present.
91
87
 
92
88
  Current built-in capabilities are intentionally conservative:
93
89
 
@@ -95,35 +91,30 @@ Current built-in capabilities are intentionally conservative:
95
91
  read, write, and shell capable because the current SDK configuration uses
96
92
  broad local access. The structured-advisor (reviewer) role is read capable
97
93
  but never write or shell capable (see
98
- [The Read-Only Reviewer Invariant](#the-read-only-reviewer-invariant)).
94
+ [The read-only reviewer invariant](#the-read-only-reviewer-invariant)).
99
95
  - Anthropic Claude supports coder and structured-advisor roles. The coder role
100
96
  is read, write, and shell capable. The structured-advisor (reviewer) role is
101
97
  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).
98
+ - OpenAI-compatible supports coder and structured-advisor roles. The coder
99
+ role is read, write, and shell capable through neal-owned tools executed
100
+ locally. The structured-advisor role is read capable (a read-only toolset,
101
+ no write or shell access), so its reviewer inspects the repository directly
102
+ with read tools. Neither role supports session resume. See
103
+ [OpenAI-compatible provider](#openai-compatible-provider).
113
104
 
114
105
  The `coder` capability describes adapter paths, not a global promise that every
115
106
  writer-run workflow is read-only: writer-run coder turns use provider SDKs with
116
107
  broad local permissions. The separate public `neal review` command enforces its
117
108
  own read-only boundary in the review command flow.
118
109
 
119
- ### Coder Tool Policy Enforcement
110
+ ### Coder tool policy enforcement
120
111
 
121
112
  Plan-authoring rounds pass a `toolPolicy` (`allowedWritePaths` restricted to
122
113
  the plan document, `allowRun: false`) to the coder adapter. Enforcement
123
114
  strength is adapter-specific and mechanical where each SDK allows it:
124
115
 
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.
116
+ - `openai-compatible`: full jail. The policy selects the neal-owned plan-author
117
+ toolset: writes only to the allowlisted paths, no shell tool at all.
127
118
  - `anthropic-claude`: tool exclusion plus a path callback. `allowRun: false`
128
119
  removes Bash from the turn's tools list, and a PreToolUse hook denies any
129
120
  write-class tool call whose resolved path is not allowlisted.
@@ -136,27 +127,26 @@ strength is adapter-specific and mechanical where each SDK allows it:
136
127
  Rounds without a `toolPolicy` are unaffected on every adapter: ordinary coder
137
128
  scope rounds keep full access because verification legitimately runs commands.
138
129
 
139
- ### The Read-Only Reviewer Invariant
130
+ ### The read-only reviewer invariant
140
131
 
141
- A reviewer exists to *judge*, never to *modify* or *re-execute*. Neal enforces
132
+ A reviewer exists to *judge*, never to *modify* or *re-execute*. neal enforces
142
133
  this structurally: every *supported* `structured-advisor` capability has
143
134
  `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).
135
+ `shell`. `read` is not part of the invariant, but every built-in reviewer
136
+ (`openai-codex`, `anthropic-claude`, `openai-compatible`) declares read access,
137
+ and the writer-run capability check requires it.
148
138
 
149
139
  `src/neal/providers/registry.ts` asserts this over every registered provider
150
- definition built-in or test-registered at the definition-resolution
140
+ definition (built-in or test-registered) at the definition-resolution
151
141
  chokepoint and at test registration time, so no provider definition can ever
152
142
  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
143
+ `write`/`shell` half. It never constrains `read`, and it never touches the
154
144
  `coder` capability (coders keep `write`/`shell`).
155
145
 
156
146
  Two consequences follow from no reviewer holding shell access:
157
147
 
158
148
  - 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
149
+ performed by the coder. The reviewer trusts the coder's reported result plus
160
150
  its own reading of the diff and (for read-tool reviewers) the repository.
161
151
  - Reviewers never mutate the checkout. A review produces a verdict and findings,
162
152
  not edits.
@@ -164,23 +154,23 @@ Two consequences follow from no reviewer holding shell access:
164
154
  Usage reporting is `opportunistic`: providers emit usage only when the SDK
165
155
  event or result supplies it.
166
156
 
167
- ### Review Doctrine Access Modes
157
+ ### Review doctrine access modes
168
158
 
169
- Reviewer prompts render Neal's shared review doctrine in one of three access
159
+ Reviewer prompts render neal's shared review doctrine in one of two access
170
160
  modes, derived from the configured reviewer provider's structured-advisor
171
161
  tool access:
172
162
 
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
163
+ - `read-only` (read access without shell access, which every built-in reviewer
164
+ today): the reviewer is instructed to inspect the repository through read
165
+ tools only (reading changed files and searching the tree) and is never
166
+ instructed to run commands, run tests, or use scratch directories.
167
+ Commit-range visibility depends on whether the provider exposes its own
168
+ range-diff tool:
169
+ - `openai-compatible` declares a read-only `git_diff` commit-range tool
180
170
  (`providesRangeDiffTool`), so its reviewer inspects the range with that tool
181
171
  and receives no inlined diff.
182
172
  - 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
173
+ have read and search tools but no commit-range diff tool, so neal inlines
184
174
  the commit-range diff into the reviewer prompt as the source of truth for
185
175
  what the range changed (including deletions and renames that head-state file
186
176
  reads cannot reveal), and the reviewer uses its read/search tools to verify
@@ -189,28 +179,23 @@ tool access:
189
179
  In both branches the doctrine states that absence from a diff is not evidence
190
180
  of absence from the repository: the reviewer must open files with read tools
191
181
  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
182
  - `tool-access` (read and shell access): a reviewer is instructed to inspect
198
183
  and execute against the repository directly. Because the read-only reviewer
199
184
  invariant forbids any supported `structured-advisor` capability from holding
200
185
  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
186
+ [The read-only reviewer invariant](#the-read-only-reviewer-invariant)), no
187
+ built-in reviewer resolves to this mode today. It would require a
203
188
  shell-capable reviewer, which the registry assertion rejects.
204
189
 
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.
190
+ Read-tool reviewers are never instructed to run verification: the doctrine
191
+ reflects that verification is the coder's, trusted by the reviewer's reading.
192
+ Write access does not affect review doctrine: reviews never instruct repository
193
+ mutation, and no supported reviewer capability declares write access in any
194
+ case.
210
195
 
211
- ## Runtime Turns
196
+ ## Runtime turns
212
197
 
213
- Provider adapters receive Neal-owned turn arguments rather than reading Neal
198
+ Provider adapters receive neal-owned turn arguments rather than reading neal
214
199
  configuration directly.
215
200
 
216
201
  Plain coder turns receive:
@@ -220,7 +205,7 @@ Plain coder turns receive:
220
205
  - `inactivityTimeoutMs`
221
206
  - optional `resumeHandle`
222
207
  - optional structured `outputSchema`, retained only for raw coder prompt
223
- compatibility outside Neal product control paths
208
+ compatibility outside neal product control paths
224
209
  - optional `onSessionStarted` callback
225
210
  - optional provider event sink
226
211
 
@@ -257,9 +242,9 @@ All turn argument shapes also accept an optional `signal` (`AbortSignal`).
257
242
  Adapters must treat an aborted signal as a request to terminate the in-flight
258
243
  SDK turn and surface a normalized provider error rather than hanging.
259
244
 
260
- ### Turn Liveness
245
+ ### Turn liveness
261
246
 
262
- Neal layers two independent timeouts over every provider turn:
247
+ neal layers two independent timeouts over every provider turn:
263
248
 
264
249
  - `neal.agent_turn_startup_timeout_ms` (default `300000`) bounds startup
265
250
  silence: the window after a turn starts during which the provider has shown
@@ -271,15 +256,15 @@ Neal layers two independent timeouts over every provider turn:
271
256
  progress, the startup timer disarms permanently and only this timeout
272
257
  applies.
273
258
 
274
- When the startup timer fires, Neal aborts the silent attempt via the turn's
259
+ When the startup timer fires, neal aborts the silent attempt via the turn's
275
260
  `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
261
+ (default `1`). This means neal may rerun a turn even though the provider never
277
262
  reported an error: the provider went silent before doing any work, which is
278
263
  indistinguishable from a stalled SDK stream. Retries are safe because zero
279
264
  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
265
+ 1 is recommended). Persistent startup silence usually indicates a provider or
281
266
  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
267
+ existing session are never retried by this layer. The orchestrator's existing
283
268
  fresh-session recovery owns those.
284
269
 
285
270
  Liveness outcomes are auditable in `events.ndjson` as
@@ -294,48 +279,49 @@ Provider session handles are opaque strings. Providers should preserve and
294
279
  return the SDK session handle when one exists, including on normalized failures
295
280
  where the SDK exposed a session before the failure.
296
281
 
297
- Neal persists handles separately for planner, coder, and reviewer work. Resume
282
+ neal persists handles separately for planner, coder, and reviewer work. Resume
298
283
  checks fail early if the configured provider role does not support session
299
284
  resume and a persisted handle must be resumed.
300
285
 
301
- ## Structured Output
286
+ ## Structured output
302
287
 
303
- Neal product control paths use `neal-json-block-v1`. Provider adapters transport
288
+ neal product control paths use `neal-json-block-v1`. Provider adapters transport
304
289
  assistant text, preserve session handles, normalize SDK errors, and delegate
305
290
  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
291
+ normalized structured-output errors to neal's shared runtime. The
292
+ `openai-compatible` adapter is the one exception: its coder and
293
+ structured-advisor paths consume the same protocol spec (schema, validator,
294
+ labels) but fulfill it through the AI SDK's native structured-output channel in
295
+ a dedicated finalization turn, with no fenced-text transport. See
296
+ [OpenAI-compatible provider](#openai-compatible-provider).
297
+
298
+ Coder adapters expose `runStructuredPrompt<TStructured>()` for neal-owned
313
299
  structured coder decisions. That method receives a caller-owned
314
300
  `neal-json-block-v1` protocol spec, returns the typed `structured` object plus
315
301
  the provider `sessionHandle`, emits `structured_output_received` only after
316
- Neal validation succeeds, and normalizes missing structured control payloads as
302
+ neal validation succeeds, and normalizes missing structured control payloads as
317
303
  `structured_output_missing`.
318
304
 
319
305
  `runPrompt(..., outputSchema)` remains supported for compatibility with
320
- older raw coder harness coverage. `neal check` and Neal-owned coder decision
306
+ older raw coder harness coverage. `neal check` and neal-owned coder decision
321
307
  rounds use `neal-json-block-v1` through `runStructuredPrompt()` rather than
322
308
  parsing provider JSON strings in round code.
323
309
 
324
- Neal's shared `neal-json-block-v1` runtime appends provider-neutral transport
310
+ neal's shared `neal-json-block-v1` runtime appends provider-neutral transport
325
311
  instructions requiring optional useful prose followed by exactly one final
326
312
  fenced `neal-json` JSON block. It then extracts, parses, validates, and repairs
327
313
  that control object with the caller-supplied schema label, schema, validator,
328
314
  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
315
+ compatibility tolerance for older mocks and pre-migration paths. The prompt
330
316
  contract remains prose plus one final `neal-json` block. State-facing
331
- `structured_output_received` telemetry is emitted only after Neal validation
317
+ `structured_output_received` telemetry is emitted only after neal validation
332
318
  succeeds.
333
319
 
334
320
  Anthropic Claude and OpenAI Codex structured coder and structured-advisor
335
321
  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
322
+ SDK-native surfaces are compatibility details for non-neal raw provider calls,
323
+ not the neal product protocol. Invalid or missing local JSON can recover through
324
+ a bounded repair turn. Repair turns are side-effect-free by prompt. Claude
339
325
  repair turns also omit repo tools and session resume, and Codex repair turns run
340
326
  through the most restrictive available streamed-turn options.
341
327
 
@@ -374,8 +360,8 @@ round code should not import SDK-specific provider error classes.
374
360
  Normalized error kinds are:
375
361
 
376
362
  - `timeout`
377
- - `no_progress_timeout` (emitted by Neal's turn liveness supervisor, not by
378
- adapters; see Turn Liveness above)
363
+ - `no_progress_timeout` (emitted by neal's turn liveness supervisor, not by
364
+ adapters, as described under Turn liveness above)
379
365
  - `api_error`
380
366
  - `structured_output_missing`
381
367
  - `structured_output_invalid`
@@ -388,10 +374,10 @@ Each normalized error carries the provider id, role, optional session handle,
388
374
  kind, retryability, and cause. Round code wraps normalized provider errors into
389
375
  round-level failures only when it needs round-specific context.
390
376
 
391
- ## Status-Visible Errors
377
+ ## Status-visible errors
392
378
 
393
379
  Provider failures are visible to automation through `neal status --json` and
394
- `neal status --json --all` as `providerError`. Neal derives that summary from
380
+ `neal status --json --all` as `providerError`. neal derives that summary from
395
381
  the latest `provider.provider_error` event when present, including timestamp,
396
382
  provider id, role, label, session handle, normalized kind, bounded message, and
397
383
  retryability. If a run failed before a provider event was available, status may
@@ -402,21 +388,21 @@ fall back to the latest `phase.error` as an unclassified run error with
402
388
  must not include SDK payloads, full prompts, provider `providerData`, full
403
389
  assistant text, raw command output, credentials, or environment dumps. Keep rich
404
390
  diagnostics in local support artifacts such as `events.ndjson` and
405
- `stderr.log`; public wrappers should use the bounded status summary.
391
+ `stderr.log`. Public wrappers should use the bounded status summary.
406
392
 
407
- ## Model Overrides
393
+ ## Model overrides
408
394
 
409
395
  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
396
+ `model: null` means neal lets the provider choose its default model. A non-null
411
397
  model is accepted only when the provider role declares `supportsModelOverride`.
412
398
 
413
399
  Model selection stays provider-local after the registry passes the configured
414
400
  override into the adapter factory.
415
401
 
416
- ## Reasoning Effort Overrides
402
+ ## Reasoning effort overrides
417
403
 
418
404
  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
405
+ `effort: null` (or an omitted key) means neal sends no effort option and the
420
406
  provider uses its default reasoning depth. A non-null effort is mapped to the
421
407
  closest provider-native option: OpenAI Codex receives
422
408
  `ThreadOptions.modelReasoningEffort` and Anthropic Claude receives
@@ -426,10 +412,13 @@ Supported values are provider-specific:
426
412
 
427
413
  - `openai-codex`: `minimal`, `low`, `medium`, `high`, `xhigh`.
428
414
  - `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.
415
+ - `openai-compatible`: no supported values. Any configured `effort` is rejected
416
+ with the standard effort config error. This is a neal limitation, not a claim
417
+ that compatible endpoints cannot control reasoning. Some expose an effort or
418
+ token-budget setting, but the accepted values and behavior vary by endpoint
419
+ and model. Supporting that safely requires an endpoint-specific pass-through
420
+ or capability contract. Until then, neal leaves reasoning at the upstream
421
+ default.
433
422
 
434
423
  Effort support is declared per capability role. An effort value the configured
435
424
  provider does not support is rejected before a writer run starts or resumes with
@@ -442,20 +431,21 @@ is set explicitly, an omitted `agent.planner.effort` resolves to provider
442
431
  default (`null`) rather than inheriting the coder effort, so set
443
432
  `agent.planner.effort` explicitly to override it.
444
433
 
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`
434
+ ## OpenAI-compatible provider
435
+
436
+ The `openai-compatible` provider runs the planner, coder, and reviewer roles
437
+ against any OpenAI-compatible Chat Completions endpoint with a strong
438
+ tool-calling model (OpenRouter, vLLM, DeepSeek, and similar). The planner uses
439
+ the coder path. neal owns
440
+ the agentic loop (one model turn per request, with a neal-owned outer loop
441
+ over the message history) and supplies its own tool set (`read_file`,
442
+ `write_file`, `edit_file`, `list_dir`, `grep`, and `run`), so any endpoint
443
+ whose model can call tools reliably can serve as the coder, and, through a
444
+ read-only subset of the same tools, as the reviewer.
445
+
446
+ The adapter is implemented on the AI SDK's OpenAI-compatible chat provider.
447
+ Gateway behaviors learned from live failures are preserved through a
448
+ neal-owned response interceptor: upstream errors embedded as an `error`
459
449
  object inside an HTTP 200 body keep their status-based retryability
460
450
  (OpenRouter wraps upstream 429s this way), and reasoning-model responses
461
451
  whose final text arrives in `message.reasoning` / `message.reasoning_content`
@@ -464,132 +454,40 @@ AI SDK's normalized shape (`inputTokens` / `outputTokens` / `totalTokens`
464
454
  plus detail fields), with the raw response usage object preserved under
465
455
  `usage.raw`.
466
456
 
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
457
+ Because endpoint quality varies, qualify an `openai-compatible` model with
560
458
  [`neal compat`](compat.md) before trusting it with real work: it drives the
561
459
  candidate through complete neal runs against trivial bundled fixtures and emits
562
460
  a binary PASS/FAIL per (model, role). `neal check` proves only connectivity and
563
- basic protocol; `neal compat` proves the full loop.
461
+ basic protocol. `neal compat` proves the full loop.
564
462
 
565
463
  Role support and behavior:
566
464
 
567
- - Coder-first. The coder capability role is read, write, and shell capable;
568
- the planner inherits the coder provider unless configured explicitly.
465
+ - Coder-first. The coder capability role is read, write, and shell capable.
466
+ The planner inherits the coder provider unless configured explicitly.
569
467
  - 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
468
+ neal-owned loop over a read-only toolset (`read_file`, `list_dir`,
469
+ `grep`, and the read-only `git_diff` commit-range inspector), so the
572
470
  reviewer inspects the repository directly under the `read-only` review
573
471
  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.
472
+ [Review doctrine access modes](#review-doctrine-access-modes)). `git_diff`
473
+ is a neal-owned fixed-argv git query (never a shell string) that gives the
474
+ reviewer actual commit-range visibility, including deletions and renames
475
+ that head-state file reads cannot reveal. After the read-tool loop
476
+ completes with a zero-tool-call turn, the structured payload is requested
477
+ in a single dedicated finalization turn that carries no tools. The same
478
+ adapter also serves the final-completion summary gate when
479
+ `openai-compatible` is the coder.
582
480
  - No session resume (`supportsSessionResume: false` on both roles). The
583
481
  adapter never persists a provider session handle, so `neal resume` after an
584
482
  interruption or failure restarts the interrupted scope from scratch in a
585
483
  fresh session rather than resuming mid-conversation. Already-accepted
586
- scopes stay committed; only the in-flight scope is redone. Top-level
484
+ scopes stay committed. Only the in-flight scope is redone. Top-level
587
485
  plan-refinement revision rounds likewise start a fresh planner session per
588
486
  round instead of resuming the planning conversation.
589
487
  - 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
488
+ `OPENAI_COMPATIBLE_MAX_STEPS` constant (currently `48` model turns per
489
+ prompt). Structured-advisor/reviewer rounds use the smaller exported
490
+ `OPENAI_COMPATIBLE_ADVISOR_MAX_STEPS` constant (currently `24`: reviews are
593
491
  bounded inspections, not implementations). Reaching either cap fails the
594
492
  attempt with a non-retryable `provider_failed` error naming the cap. There
595
493
  are no config knobs for the caps.
@@ -599,26 +497,26 @@ Role support and behavior:
599
497
  tool-turns-per-review is auditable from `events.ndjson` when qualifying a
600
498
  model for reviewer duty.
601
499
  - No `effort` support. Any configured `effort` for this provider is rejected
602
- with the standard effort config error.
500
+ with the standard effort config error. See
501
+ [Reasoning effort overrides](#reasoning-effort-overrides) for why.
603
502
  - Structured output is SDK-native. After the tool loop's normal
604
503
  zero-tool-call completion turn, the adapter makes exactly one dedicated
605
504
  finalization turn (no tools) through the AI SDK's `Output.object`
606
505
  structured-output channel and validates the result against the protocol
607
506
  schema. There is no fenced-JSON text protocol on this provider and no
608
507
  second chance for a malformed payload: a finalization failure is a single
609
- non-retryable error kind `structured_output_missing` when the model
508
+ non-retryable error (kind `structured_output_missing` when the model
610
509
  produced no parseable object, or `structured_output_invalid` when the
611
- object failed validation with a truncated excerpt of the rejected text
510
+ object failed validation) with a truncated excerpt of the rejected text
612
511
  preserved under the `provider_error` event's `providerData.diagnostic`, so
613
512
  failed runs stay classifiable. Tool inputs are likewise strict: arguments
614
513
  must match the tool's schema as sent, with no coercion of stringified
615
514
  JSON. This failure taxonomy is the qualification signal for candidate
616
- models a model that cannot pass `neal check` on this provider is not
515
+ models: a model that cannot pass `neal check` on this provider is not
617
516
  pool-eligible.
618
517
  - Transient API failures (408/429/5xx, network errors, and empty
619
518
  HTTP-200 responses) are retried with bounded backoff and surfaced as
620
- `api_retry` progress events, matching the `openai-compatible` adapter's
621
- behavior.
519
+ `api_retry` progress events.
622
520
 
623
521
  Trust level is identical to the vendor writer providers: the `run` tool
624
522
  executes arbitrary shell commands in the scope working tree with no sandbox.
@@ -626,32 +524,39 @@ The path jail on file-tool arguments keeps `read_file`, `write_file`,
626
524
  `edit_file`, `list_dir`, and `grep` inside the repository (including through
627
525
  symlinks), but it does not contain what `run` can do. Apply the same
628
526
  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.
527
+ Structured-advisor rounds never receive `write_file`, `edit_file`, or `run`.
528
+ The read-only toolset is jailed the same way.
639
529
 
640
530
  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.
531
+ `:free` pools are not suitable as coders, and they are usually too flaky for
532
+ reviewer duty. Treat the dated [compatible-models.md](compatible-models.md)
533
+ whitelist as the current evidence for `openai-compatible` model compatibility.
534
+ It is a contract smoke test, not a skill ranking, so still trial unfamiliar
535
+ models on a disposable project before using them on real work.
647
536
 
648
537
  ### Configuration
649
538
 
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:
539
+ Add a `providers.openai_compatible` block (repo `neal.yml` overrides
540
+ `~/.neal/config.yml`, matching normal config precedence) and point one or
541
+ more roles at the provider. A worked DeepSeek example:
542
+
543
+ ```yaml
544
+ providers:
545
+ openai_compatible:
546
+ base_url: https://api.deepseek.com
547
+ api_key_env: DEEPSEEK_API_KEY
548
+ default_model: deepseek-chat
549
+
550
+ agent:
551
+ coder:
552
+ provider: openai-codex
553
+ model: null
554
+ reviewer:
555
+ provider: openai-compatible
556
+ model: deepseek-chat
557
+ ```
558
+
559
+ A worked OpenRouter example driving the coder through the provider:
655
560
 
656
561
  ```yaml
657
562
  providers:
@@ -662,31 +567,175 @@ providers:
662
567
 
663
568
  agent:
664
569
  coder:
665
- provider: generic-agentic
570
+ provider: openai-compatible
666
571
  model: null
667
572
  reviewer:
668
573
  provider: anthropic-claude
669
574
  model: null
670
575
  ```
671
576
 
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.
577
+ The planner inherits the coder provider unless configured explicitly, so the
578
+ OpenRouter config above routes planning through `openai-compatible` too.
579
+ Structured-advisor rounds additionally honor a neal-internal round-level model
580
+ override first.
581
+
582
+ Settings resolve config-first with environment fallbacks:
583
+
584
+ - `base_url`: `providers.openai_compatible.base_url`, else
585
+ `OPENAI_COMPATIBLE_BASE_URL`. Required.
586
+ - `api_key_env`: `providers.openai_compatible.api_key_env`, else the default
587
+ `OPENAI_COMPATIBLE_API_KEY`. The API key value is read from that named
588
+ environment variable. neal never stores the key itself.
589
+ - model: the role-level `agent.<role>.model` override, else
590
+ `providers.openai_compatible.default_model`, else `OPENAI_COMPATIBLE_MODEL`.
591
+ One of these is required.
592
+ - `headers`: optional string-to-string map of extra HTTP headers (useful for
593
+ OpenRouter attribution headers).
594
+ - `pricing`: an **optional override** for per-million-token rates. It is no
595
+ longer required to get a dollar cost (see "Cost pricing" below). Set it only
596
+ for models the vendored rate card does not key exactly, or to pin different
597
+ rates. All three rates are required when the block is present (a partial block
598
+ is a configuration error). Rates are in USD per one million tokens:
599
+
600
+ ```yaml
601
+ providers:
602
+ openai_compatible:
603
+ base_url: https://api.deepseek.com
604
+ api_key_env: DEEPSEEK_API_KEY
605
+ default_model: deepseek-chat
606
+ pricing:
607
+ input_per_million: 0.27
608
+ cached_input_per_million: 0.07
609
+ output_per_million: 1.10
610
+ ```
611
+
612
+ #### Cost pricing
613
+
614
+ neal resolves each run's dollar cost per provider/role bucket in this order:
615
+
616
+ 1. **Provider-reported cost.** The Claude adapter passes through the provider's
617
+ own `total_cost_usd`. It always wins when present.
618
+ 2. **Configured `pricing` override.** The
619
+ `providers.openai_compatible.pricing` block above.
620
+ 3. **Vendored published rate card.** The default rate source, keyed by model
621
+ slug. neal ships a trimmed copy of LiteLLM's
622
+ [`model_prices_and_context_window.json`](https://raw.githubusercontent.com/BerriAI/litellm/d9661222492a098555f40cb8b50014054bea5ab8/model_prices_and_context_window.json)
623
+ (retrieved 2026-07-18), so any adapter whose resolved model slug is exactly a
624
+ card key gets a rate-computed cost with **zero configuration**.
625
+ 4. **Tokens only.** If none of the above yields pricing, the run shows token
626
+ counts only. neal never invents dollars.
627
+
628
+ Card lookup is **exact-match only**: the resolved model slug must be an exact
629
+ card key. neal does not strip provider prefixes and does not fall back to the
630
+ basename after a `/`, so a slash-qualified slug (for example a local or gateway
631
+ slug like `local/gpt-5.5` or `azure/<deployment>`) is priced only if the card
632
+ lists that exact string. Otherwise it stays tokens-only. LiteLLM lists many
633
+ `vendor/model` keys directly, so provider-qualified slugs are still priced when
634
+ the card carries that exact key. Set an explicit `pricing` override for slugs the
635
+ card does not key exactly.
636
+
637
+ **Base-tier only.** Card cost uses each model's published base /
638
+ standard-context per-token rates only. neal does **not** apply long-context
639
+ surcharges, tiered, batch, or priority rates. A turn whose prompt crosses a
640
+ model's long-context threshold (for example GPT-5.6 Sol above 272K prompt tokens,
641
+ which upstream prices at 2× input / 1.5× output) is priced at the base tier and
642
+ is therefore an **underestimate** for that turn. Operators who need exact
643
+ long-context cost can pin explicit rates via the `pricing` override.
644
+
645
+ The `openai-codex` provider is now card-priced by its configured model (each
646
+ role is priced by the model it actually ran). There is no codex pricing config
647
+ block by design, so a codex role with no configured model (SDK default) stays
648
+ tokens-only.
649
+
650
+ The card is community-maintained list prices, and rate-computed cost is an
651
+ estimate. Rate-computed cost (from either the vendored card or a configured
652
+ override) is flagged with a footnote (`*`) in the retrospective's Provider Usage
653
+ table. The note reads "Cost estimated from published or configured rates, not
654
+ reported by the provider." That distinguishes it from cost a provider reports
655
+ directly. Cached input is billed once, at the cached rate.
656
+
657
+ `neal setup` offers `openai-compatible` for the planner, coder, and reviewer
658
+ roles and prints guidance when the base URL, API key, or model is unresolved.
659
+ `neal setup` detection reports which config keys or environment variables are
660
+ missing without making network calls.
661
+
662
+ ### Live smoke check (optional)
663
+
664
+ The default test suite never requires live credentials or network access. To
665
+ verify a real endpoint end to end, set credentials and run `neal check` with
666
+ the reviewer role configured for `openai-compatible`. The `provider-check`
667
+ structured round exercises the adapter against the live endpoint:
668
+
669
+ ```sh
670
+ OPENAI_COMPATIBLE_BASE_URL=https://api.deepseek.com \
671
+ OPENAI_COMPATIBLE_API_KEY=... \
672
+ OPENAI_COMPATIBLE_MODEL=deepseek-chat \
673
+ pnpm start -- check
674
+ ```
675
+
676
+ ### Local endpoints (Ollama, llama.cpp, vLLM)
677
+
678
+ A local server that speaks the OpenAI-compatible Chat Completions API (Ollama,
679
+ llama.cpp's `server`, vLLM, and similar) is just another endpoint for this
680
+ provider. It reuses the same `providers.openai_compatible` block documented
681
+ above, with the same config-first, environment-fallback resolution rules from
682
+ [Configuration](#configuration). Only the `base_url`, model slug, and auth
683
+ variable change.
684
+
685
+ ```yaml
686
+ providers:
687
+ openai_compatible:
688
+ base_url: http://localhost:11434/v1
689
+ api_key_env: OLLAMA_API_KEY # any non-empty placeholder for servers that ignore auth
690
+ default_model: qwen3-coder:30b
691
+
692
+ agent:
693
+ coder:
694
+ provider: openai-codex
695
+ model: null
696
+ reviewer:
697
+ provider: openai-compatible
698
+ model: null
699
+ ```
678
700
 
679
- ## Adding A Built-In Provider
701
+ neal has no `api_key` config field: it resolves the key from the environment
702
+ variable named by `api_key_env` (default `OPENAI_COMPATIBLE_API_KEY`), and the
703
+ AI SDK requires that value to be non-empty even when the local server ignores
704
+ auth. So export a non-empty placeholder to the variable you named
705
+ (`export OLLAMA_API_KEY=ollama`) before starting a run. Use the fully qualified
706
+ local slug your server reports (Ollama tags such as `qwen3-coder:30b`), not a
707
+ bare family name.
708
+
709
+ Qualify a local model with [`neal compat`](compat.md)
710
+ (`neal compat --model <slug> --role all`) before trusting it with writer runs.
711
+ `--model` forces the candidate onto `openai-compatible`. Note the reference
712
+ caveat: `--model` defaults `--reference` to `openai-codex`, which needs a Codex
713
+ login, so a purely-local operator should pass
714
+ `--reference openai-compatible:<same-slug>` for a self-contained check, while
715
+ being aware that self-reference is a weaker qualification partner (a candidate
716
+ grading itself), so treat its PASS with more caution than a native reference
717
+ (see [compatible-models.md](compatible-models.md)).
718
+
719
+ Set realistic role expectations. Local models below the whitelist bar usually
720
+ fail the **coder** role on structured output (`structured_output` dominates the
721
+ coder-FAIL rows in [compatible-models.md](compatible-models.md)), so the config
722
+ above is the realistic starting point: keep the coder on a native provider
723
+ (`openai-codex` or `anthropic-claude`) and give the local `openai-compatible`
724
+ endpoint reviewer-only duty. That split only works if the local model calls
725
+ tools reliably: the reviewer path is tool-driven, so a chat-only model (no
726
+ tool calling) is not supported in any role.
727
+
728
+ ## Adding a built-in provider
680
729
 
681
730
  To add another built-in provider:
682
731
 
683
732
  1. Implement a provider module under `src/neal/providers/`.
684
733
  2. Export a `NealProviderDefinition`.
685
734
  3. Register the definition in `src/neal/providers/registry.ts`.
686
- 4. Map SDK sessions and resume handles to opaque Neal handles.
735
+ 4. Map SDK sessions and resume handles to opaque neal handles.
687
736
  5. Map SDK stream or message events to `ProviderRuntimeEvent` values.
688
737
  6. Normalize SDK errors to `NealProviderError`.
689
- 7. Add focused tests for event mapping, capabilities, Neal structured control
738
+ 7. Add focused tests for event mapping, capabilities, neal structured control
690
739
  protocol behavior, session handles, and normalized errors.
691
740
  8. Document SDK prerequisites, environment assumptions, supported roles,
692
741
  capabilities, and limitations.
@@ -696,7 +745,7 @@ not need a real SDK. SDK adapter tests should stay focused on provider-specific
696
745
  event collection, structured-output parsing, retry behavior, and error
697
746
  normalization.
698
747
 
699
- ## SDK Prerequisites
748
+ ## SDK prerequisites
700
749
 
701
750
  Provider documentation should state the SDK package, required credentials or
702
751
  login state, model assumptions, filesystem/tool permission assumptions, and any
@@ -707,18 +756,16 @@ Current built-in writer providers run with broad local permissions:
707
756
  - OpenAI Codex uses `approvalPolicy: never` and
708
757
  `sandboxMode: danger-full-access`.
709
758
  - 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.
759
+ - OpenAI-compatible makes HTTP API calls for model turns but executes
760
+ neal-owned tools locally. The coder role's `run` tool executes shell
761
+ commands with no sandbox, matching the vendor writers' trust level.
762
+ Structured-advisor rounds execute only the read-only toolset locally.
716
763
 
717
764
  Those settings are part of current writer-run behavior. Do not narrow or widen
718
765
  them as part of provider registration unless a separate product change requires
719
766
  it.
720
767
 
721
- ## Review Boundary
768
+ ## Review boundary
722
769
 
723
770
  Public `neal review` uses a separate read-only findings loop:
724
771
 
@@ -733,16 +780,16 @@ writes isolated artifacts under `.neal/reviews/<review-id>/`.
733
780
  Two distinct read-only guarantees apply here, and they should not be conflated:
734
781
 
735
782
  - The provider-capability invariant (see
736
- [The Read-Only Reviewer Invariant](#the-read-only-reviewer-invariant)):
783
+ [The read-only reviewer invariant](#the-read-only-reviewer-invariant)):
737
784
  every supported `structured-advisor` capability has `write: false` and
738
785
  `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
786
+ write or run shell by capability. This holds for both writer runs and
740
787
  `neal review`.
741
788
  - The `neal review` command's additional artifact-boundary guard: the command
742
789
  checks protected writer state and worktree changes after provider rounds and
743
790
  fails if anything outside its review artifacts changed. This post-round guard
744
791
  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
792
+ is specific to `neal review`. A writer run does not perform this artifact
746
793
  diff.
747
794
 
748
795
  So the capability invariant guarantees reviewers never write or run shell in