I concluded a few commits ago that the serve new /api/session pipeline accepts prompts and never executes them, and kept turns on opencode run. Wrong. Every probe behind that passed an explicit model claude-sonnet-4-6, and THAT model silently does not run on the new surface — no error, no event, no assistant message. Drop the field and the same request completes. One broken variable in every experiment, read as a property of the system. Measured on 1.18.16, both machines upgraded today: delivery steer injects into a running turn (verified, output changed to order), delivery queue runs after it (verified, ONE then TWO, zero errors), and model selection works via POST /model — just not with sonnet. So the fork is reopened and worth taking, targeting the new surface rather than the legacy message path, which generates fine but has neither steer nor queue. Blocked only on why sonnet dies there while working under opencode run. Third time this project has hit the same trap: opencode accepts input it does not honour and says nothing — directory in the body, location.directory that never existed, now model. A probe that changes one thing and sees nothing has not learned the feature is missing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
5.6 KiB
Markdown
93 lines
5.6 KiB
Markdown
# The Phase 2 fork: reopened, and why the first answer was wrong
|
|
|
|
> **CORRECTION, same day.** An earlier version of this file concluded "the new pipeline does not
|
|
> execute, keep the subprocess". **That was wrong, and wrong for an embarrassing reason: my probe.**
|
|
> The conclusion is reversed below. The mistake is written up rather than deleted, because the shape of
|
|
> it is the useful part.
|
|
|
|
**Decision: the fork is worth taking. Not started, and gated on one open question.**
|
|
|
|
---
|
|
|
|
## What actually happened
|
|
|
|
The serve exposes a newer `/api/session/*` surface, alongside the `/session/*` one every prior document
|
|
was written against. It offers, natively, what the parity doc lists as impossible under `stdin: 'ignore'`:
|
|
`delivery: "steer" | "queue"` on `POST /prompt`, `/interrupt`, a per-session `text/event-stream` with an
|
|
`?after=` cursor, `/compact`, `prompt.files`, and `/permission` + `/question`.
|
|
|
|
I probed it, saw prompts accepted and never executed, and concluded it was an unfinished pipeline
|
|
("`session.next.*` is the tell"). Every one of those probes passed an explicit
|
|
`model: {providerID: 'opencode', id: 'claude-sonnet-4-6'}`.
|
|
|
|
**That model silently does not run on the new pipeline.** No error, no event, no assistant message — the
|
|
prompt is admitted, stored, `prompt.admitted` and `prompted` fire, and nothing else ever happens. Drop
|
|
the model field and the identical request completes normally.
|
|
|
|
So I had one broken variable in every experiment and read the result as a property of the system.
|
|
|
|
## What is actually true, measured on 1.18.16 (both machines upgraded 2026-08-10)
|
|
|
|
| Claim | Verdict |
|
|
| ----------------------------------------- | -------------------------------------------------------------------- |
|
|
| The new pipeline executes turns | **Yes** — replies normally when no model is forced |
|
|
| `delivery: "steer"` injects mid-turn | **Yes, verified** — steered a running turn, output changed to order |
|
|
| `delivery: "queue"` runs after | **Yes, verified** — two replies, "ONE" then "TWO", zero errors |
|
|
| Model selection works at all | **Yes** — `POST /api/session/{id}/model` → 204, then runs on it |
|
|
| `claude-sonnet-4-6` works there | **No** — silent no-op, at create *and* via the model route |
|
|
| `claude-sonnet-4-6` works via `run --dir` | **Yes** — verified end to end the same day |
|
|
|
|
Steer and queue are exactly the two features we hand-built for Claude and that Andre called a game
|
|
changer. Having them as primitives, plus a resumable per-session cursor that mirrors officer's durable
|
|
replay, is a strong argument for migrating.
|
|
|
|
## The open question, which is now the only blocker
|
|
|
|
**Why does `claude-sonnet-4-6` run under `opencode run --model` and silently die under the new API?**
|
|
|
|
Until that is answered, the migration cannot be adopted: model choice is a user-facing control, and a
|
|
harness where picking the wrong model produces silence rather than an error is worse than one without
|
|
streaming. Things to try, cheapest first:
|
|
|
|
1. `variant` on `ModelRef` — it is the one field of the three we never set.
|
|
2. Compare `/config/providers` against what `run` resolves, to see whether the ids differ in form.
|
|
3. Whether it is auth-scoped: alpha's default model failed `401 missing_api_key` on a *different*
|
|
provider (`nano-gpt`), which proves the new pipeline surfaces provider auth errors properly when it
|
|
gets that far — so sonnet's silence is not a generic auth failure.
|
|
|
|
## Revised recommendation
|
|
|
|
**Take the fork, targeting the new surface, once the model question is answered.** Not the legacy
|
|
`/session/{id}/message` path — that generates fine but has neither steer nor queue, so it buys streaming
|
|
at the cost of SSE demux and warm-session lifetime for the two least interesting gaps.
|
|
|
|
Until then `opencode run --dir` stays, and it is verified working: `session:init` → `assistant:text` →
|
|
`result` with cost, on 1.18.16, after the upgrade.
|
|
|
|
## The lesson, which is the reason this file keeps its history
|
|
|
|
This project has now hit the same trap three times, each time in a different costume:
|
|
|
|
- `directory` in the body of `POST /session` — accepted, echoed, ignored. Produced a confident wrong
|
|
answer about per-request directories.
|
|
- `location.directory` in the parity doc — a field that did not exist on that surface, which would have
|
|
compiled and silently produced `''`.
|
|
- `model` on `POST /api/session` — accepted, echoed back in the response, and fatal to execution.
|
|
|
|
**OpenCode's API accepts input it does not honour, and says nothing.** So a probe that changes one thing
|
|
and sees nothing happen has not learned that the feature is missing; it has learned that *something* is
|
|
wrong, and the next step is to remove variables, not to conclude. The corrected probes here each changed
|
|
exactly one field.
|
|
|
|
## Findings worth keeping
|
|
|
|
- `delivery` defaults to `"steer"` when omitted.
|
|
- New-surface responses wrap in `{"data": …}`; the legacy surface returns bare objects, so reading
|
|
`body.id` instead of `body.data.id` silently yields `undefined`.
|
|
- The new surface is location-scoped per request: `x-opencode-directory` header, or
|
|
`?location[directory]=` as a `deepObject` query.
|
|
- `?after=` genuinely replays a finished session's events.
|
|
- **opencode versions: both machines on 1.18.16** as of 2026-08-10 (Mac was 1.18.11, alpha 1.17.9).
|
|
Several comments recorded these backwards; corrected. Policy from Andre: **write against the latest
|
|
version regardless of what alpha happens to run.**
|