@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.
- package/LICENSE +21 -0
- package/README.md +527 -0
- package/SECURITY.md +91 -0
- package/config.yml +104 -0
- package/dist/neal/activity-footer.js +177 -0
- package/dist/neal/activity-reporting.js +1 -0
- package/dist/neal/adjudicator/artifacts.js +58 -0
- package/dist/neal/adjudicator/blocked-adjudicator.js +223 -0
- package/dist/neal/adjudicator/contracts.js +139 -0
- package/dist/neal/adjudicator/execute.js +611 -0
- package/dist/neal/adjudicator/final-completion.js +104 -0
- package/dist/neal/adjudicator/planning.js +145 -0
- package/dist/neal/adjudicator/specs.js +453 -0
- package/dist/neal/agents/prompts.js +120 -0
- package/dist/neal/agents/rounds.js +706 -0
- package/dist/neal/agents/schemas.js +832 -0
- package/dist/neal/agents/structured-coder.js +82 -0
- package/dist/neal/agents/structured-json.js +528 -0
- package/dist/neal/agents.js +4 -0
- package/dist/neal/atomic-write.js +18 -0
- package/dist/neal/blocked-guidance.js +406 -0
- package/dist/neal/cli.js +471 -0
- package/dist/neal/commands/check.js +401 -0
- package/dist/neal/commands/compat.js +807 -0
- package/dist/neal/commands/interactive-activity.js +57 -0
- package/dist/neal/commands/new-run.js +79 -0
- package/dist/neal/commands/plan-and-execute.js +44 -0
- package/dist/neal/commands/recovery-guidance.js +217 -0
- package/dist/neal/commands/resume-run.js +395 -0
- package/dist/neal/commands/review.js +21 -0
- package/dist/neal/commands/runtime.js +557 -0
- package/dist/neal/commands/setup.js +596 -0
- package/dist/neal/commands/squash.js +113 -0
- package/dist/neal/commands/status.js +33 -0
- package/dist/neal/commands/writer-exit-codes.js +42 -0
- package/dist/neal/commit-message.js +17 -0
- package/dist/neal/config.js +432 -0
- package/dist/neal/context/artifacts.js +140 -0
- package/dist/neal/context/context.js +324 -0
- package/dist/neal/context/inline-review-context.js +131 -0
- package/dist/neal/context/reviewer-context.js +166 -0
- package/dist/neal/context/shared.js +117 -0
- package/dist/neal/context/types.js +1 -0
- package/dist/neal/diagnostic.js +208 -0
- package/dist/neal/execute-finalization.js +5 -0
- package/dist/neal/final-completion-review.js +188 -0
- package/dist/neal/final-completion.js +229 -0
- package/dist/neal/git.js +339 -0
- package/dist/neal/index.js +135 -0
- package/dist/neal/interactive-controls.js +85 -0
- package/dist/neal/logger.js +102 -0
- package/dist/neal/manual-gates.js +121 -0
- package/dist/neal/orchestrator/artifacts.js +70 -0
- package/dist/neal/orchestrator/completion.js +531 -0
- package/dist/neal/orchestrator/failures.js +31 -0
- package/dist/neal/orchestrator/notifications.js +175 -0
- package/dist/neal/orchestrator/phases/coder.js +516 -0
- package/dist/neal/orchestrator/phases/planning.js +540 -0
- package/dist/neal/orchestrator/phases/recovery.js +798 -0
- package/dist/neal/orchestrator/phases/review.js +136 -0
- package/dist/neal/orchestrator/phases/shared.js +279 -0
- package/dist/neal/orchestrator/run-loop.js +113 -0
- package/dist/neal/orchestrator/split-plan.js +235 -0
- package/dist/neal/orchestrator/transitions.js +309 -0
- package/dist/neal/orchestrator.js +215 -0
- package/dist/neal/phase-display.js +27 -0
- package/dist/neal/plan-doc.js +154 -0
- package/dist/neal/plan-queue.js +1092 -0
- package/dist/neal/plan-refinement.js +39 -0
- package/dist/neal/plan-validation.js +525 -0
- package/dist/neal/progress.js +237 -0
- package/dist/neal/prompts/assert-builder.js +13 -0
- package/dist/neal/prompts/execute.js +290 -0
- package/dist/neal/prompts/guidance.js +70 -0
- package/dist/neal/prompts/planning.js +313 -0
- package/dist/neal/prompts/review-doctrine.js +142 -0
- package/dist/neal/prompts/shared.js +101 -0
- package/dist/neal/prompts/specialized.js +212 -0
- package/dist/neal/prompts/specs.js +572 -0
- package/dist/neal/providers/anthropic-claude.js +1599 -0
- package/dist/neal/providers/detection.js +139 -0
- package/dist/neal/providers/generic-agentic-tools.js +586 -0
- package/dist/neal/providers/generic-agentic.js +1238 -0
- package/dist/neal/providers/liveness.js +151 -0
- package/dist/neal/providers/openai-codex.js +1014 -0
- package/dist/neal/providers/openai-compatible.js +654 -0
- package/dist/neal/providers/registry.js +389 -0
- package/dist/neal/providers/telemetry.js +208 -0
- package/dist/neal/providers/types.js +21 -0
- package/dist/neal/recovery-artifacts.js +50 -0
- package/dist/neal/resume-decision.js +220 -0
- package/dist/neal/resume-planner.js +265 -0
- package/dist/neal/retrospective.js +391 -0
- package/dist/neal/review-debt.js +18 -0
- package/dist/neal/review-findings/artifacts.js +173 -0
- package/dist/neal/review-findings/prompts.js +172 -0
- package/dist/neal/review-findings/provider.js +330 -0
- package/dist/neal/review-findings/run.js +373 -0
- package/dist/neal/review-findings/types.js +1 -0
- package/dist/neal/review-mode.js +67 -0
- package/dist/neal/review.js +137 -0
- package/dist/neal/run-lock.js +334 -0
- package/dist/neal/run-metrics.js +355 -0
- package/dist/neal/run-narrative-types.js +1 -0
- package/dist/neal/run-narrative.js +1374 -0
- package/dist/neal/run-registry.js +218 -0
- package/dist/neal/run-status.js +25 -0
- package/dist/neal/scopes.js +451 -0
- package/dist/neal/sensitive-text.js +8 -0
- package/dist/neal/squash-message.js +379 -0
- package/dist/neal/squash.js +591 -0
- package/dist/neal/state-invariants.js +496 -0
- package/dist/neal/state-views.js +344 -0
- package/dist/neal/state.js +887 -0
- package/dist/neal/status-footer.js +258 -0
- package/dist/neal/status.js +1260 -0
- package/dist/neal/storage-paths.js +57 -0
- package/dist/neal/support.js +58 -0
- package/dist/neal/terminal-narrator.js +435 -0
- package/dist/neal/types.js +1 -0
- package/dist/neal/verification-events.js +81 -0
- package/dist/neal/version.js +37 -0
- package/dist/neal/worktree-status.js +137 -0
- package/dist/notifier.js +44 -0
- package/docs/ADJUDICATOR_INVENTORY.md +310 -0
- package/docs/PROMPT_SPECS.md +266 -0
- package/docs/README.md +22 -0
- package/docs/architecture.md +113 -0
- package/docs/assets/neal-execution-flow.png +0 -0
- package/docs/automation.md +65 -0
- package/docs/comparison.md +105 -0
- package/docs/compat.md +269 -0
- package/docs/compatible-models.md +135 -0
- package/docs/demo.md +55 -0
- package/docs/maintenance.md +64 -0
- package/docs/plan-format.md +213 -0
- package/docs/providers.md +751 -0
- package/docs/release.md +147 -0
- package/docs/state-machine.md +266 -0
- package/docs/storage.md +207 -0
- package/docs/troubleshooting.md +152 -0
- package/examples/compat/add-edit-verify/PLAN.md +29 -0
- package/examples/compat/add-edit-verify/broken.diff +8 -0
- package/examples/compat/add-edit-verify/good.diff +8 -0
- package/examples/compat/add-edit-verify/package.json +5 -0
- package/examples/compat/add-edit-verify/src/add.js +2 -0
- package/examples/compat/add-edit-verify/test/add.test.js +9 -0
- package/examples/compat/is-even-add-test/PLAN.md +30 -0
- package/examples/compat/is-even-add-test/broken.diff +11 -0
- package/examples/compat/is-even-add-test/good.diff +11 -0
- package/examples/compat/is-even-add-test/package.json +5 -0
- package/examples/compat/is-even-add-test/src/is-even.js +3 -0
- package/examples/compat/is-even-add-test/test/is-even.test.js +9 -0
- package/examples/compat/manifest.json +60 -0
- package/examples/compat/plan-greeting/ISSUE.md +25 -0
- package/examples/compat/plan-greeting/package.json +5 -0
- package/examples/compat/plan-greeting/src/greet.js +2 -0
- package/examples/compat/plan-greeting/test/greet.test.js +8 -0
- package/examples/compat/reverse-grep-edit/PLAN.md +32 -0
- package/examples/compat/reverse-grep-edit/broken.diff +12 -0
- package/examples/compat/reverse-grep-edit/good.diff +12 -0
- package/examples/compat/reverse-grep-edit/package.json +5 -0
- package/examples/compat/reverse-grep-edit/src/strings.js +10 -0
- package/examples/compat/reverse-grep-edit/test/strings.test.js +15 -0
- package/examples/issue-triage-js/PLAN.md +83 -0
- package/examples/issue-triage-js/README.md +76 -0
- package/examples/issue-triage-js/package.json +9 -0
- package/examples/issue-triage-js/src/issue-triage.js +87 -0
- package/examples/issue-triage-js/test/issue-triage.test.js +107 -0
- 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.
|