Add OpenCode as a synthesis backend - #80
Conversation
63df8e6 to
520f3e0
Compare
520f3e0 to
d1b766a
Compare
| // Passes no model, leaving OpenCode to resolve one: its configured model | ||
| // key, else the model last selected in the CLI, which varies between runs. | ||
| OpenCodeDefaultModel = "default" | ||
|
|
||
| // The one free model with a reasoning variant, which the runner turns up. | ||
| OpenCodeSynthesisModel = "opencode/deepseek-v4-flash-free" | ||
|
|
||
| // The default Codex model, which the runner runs at high reasoning effort. | ||
| CodexSynthesisModel = "gpt-5.6-luna" | ||
|
|
||
| // The default Claude model, likewise run at high reasoning effort. | ||
| ClaudeSynthesisModel = "claude-haiku-4-5" |
There was a problem hiding this comment.
may become outdated upon new model releases, but only might become an issue if model is deprecated
There was a problem hiding this comment.
Agreed, and it applies to all three constants equally. Leaving them pinned: each one also gates the reasoning-effort flag, so it has to name a specific model rather than track whatever the CLI currently defaults to. A deprecation shows up as a run failure with the CLI-auth-or-model message, and since validation is now a shape check, the fix is a one-line constant bump or a user-set model in settings.json.
0ac69f4 to
8fc0d6e
Compare
cebb081 to
4f91c2b
Compare
| if err := json.Unmarshal(data, &fields); err != nil { | ||
| return fmt.Errorf("decode synthesis result: %w", err) | ||
| } | ||
| for _, name := range []string{"goals", "outcome", "keyDecisions", "nextStep"} { |
There was a problem hiding this comment.
[P2] Validate required values, not only key presence
This loop proves only that the keys exist, and the later check validates only the pre-normalized goals length. encoding/json accepts null for string and slice fields and accepts null array elements as empty strings. As a result, {"goals":["ship"],"outcome":null,"keyDecisions":null,"nextStep":null} succeeds and is cached; keyDecisions then serializes as null while SessionInspector and handoffBrief call .length on it. {"goals":[null],"outcome":"done","keyDecisions":[],"nextStep":"ship"} also succeeds before the null goal normalizes away. Please reject null required values and null array elements before parsing and caching.
There was a problem hiding this comment.
Fixed in ad60fd2 — OpenCode synthesis validation now rejects null required values and null array elements before parsing and caching.
d519e49 to
630b20d
Compare
Context
Session debriefs could only run through the Claude Code or Codex CLIs. With
neither installed, synthesis could not be enabled at all — the Settings dialog
left it off because no backend reported as available. OpenCode was already a
first-class read source, so the gap was only on the invoke side.
Changes
opencode models opencode --verbose, cached for ten minutesand filtered to zero-cost models that OpenCode has not deprecated.
--pureand project config off.OpenCode loads project plugins from the working directory, so a probe inheriting
the collector's cwd would run repository code on opening Settings. Global config
is left intact, since enumerating models needs its providers.
free models OpenCode has published are still live, that table carries no
retirement signal, and it freezes at release while the free tier turns over in
weeks.
opencode/deepseek-v4-flash-free, run at itshighreasoning variant.--model, leaving OpenCode to resolve one itself:its configured model key, else the model last selected in the CLI.
from ambient credentials, and the full list runs to hundreds of entries.
then checked for the fields the schema marks required. A partial object now
fails into cooldown rather than being cached as a debrief.
~/.coslash/synthesis, removed once the run ends, so runs never enter theuser's own OpenCode history.
during init on a shared database, and the manager runs up to four at once.
startup. The cutoff sits well past the 90s run timeout, so a second collector
on
--portcannot lose a live run's database to the sweep.--dirpassed explicitly, since OpenCode readsPWDbefore the processworking directory.
settings.jsonnow accepts any model the chosen CLI can reach, including onebehind an API proxy such as
ANTHROPIC_BASE_URL.-are rejected, so a value cannot be read as a CLI flag.-c model_reasoning_effort=high; OpenCode:--variant high; Claude:--effort high.keeps the CLI's own default.
--effortworks onclaude-haiku-4-5through the CLI even though theMessages API rejects
output_config.effortfor that model.whitespace-collapsed. Goals, decisions, and next step keep their caps.
Test
go build ./... && go vet ./... && go test ./...— passed(including
nullgoals), bare, fenced, and prose-wrapped objects accepted.the scratch sweep (abandoned removed, in-flight and unrelated left alone).
npm run build && npm test && npm run lint— passedbackend's default model, absent for a non-default one.
user's OpenCode database and one in the scratch file. Verified before the
database became per-run; concurrent runs not yet exercised by hand.
plugin in
.opencode/pluginand.opencode/pluginsexecuted it on the oldinvocation, and did not on the new one, which still returned the same 7 models.
whitespace ids rejected.
reports match filtering
models.opencode.ai/api.jsonon zero cost andnon-deprecated status, exactly.
Screenshots