Skip to content

opencode serve: lifecycle logging + configurable launcher (Ollama)#17

Merged
adamw merged 3 commits into
masterfrom
log-opencode-server-lifecycle
Jun 14, 2026
Merged

opencode serve: lifecycle logging + configurable launcher (Ollama)#17
adamw merged 3 commits into
masterfrom
log-opencode-server-lifecycle

Conversation

@adamw

@adamw adamw commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes #10 - @JD557 let me know if this would work for you

Two related improvements to how orca runs the shared opencode serve process.

1. Log server start/stop to the run trace

The shared server started and stopped silently — the raw spawn: line shows
--port 0 (not the bound port) and there was no teardown line, so a long-lived
process was invisible in the trace. Now logs the resolved listening URL on start
and a stopping line at teardown (DEBUG → /tmp/orca-*.log).

This came out of issue #10, where a user was confused that orca had silently
started its own opencode server.

2. Configurable serve launcher (OpencodeLauncher)

The opencode binary was hardcoded, so there was no way to start the server via
ollama launch opencode — which injects Ollama's generated provider config
(OPENCODE_CONFIG_CONTENT) and is the zero-config path for local Ollama models
(issue #10). Hand-writing that provider config (npm package, baseURL, exact model
ids, raised num_ctx) is fiddly, so the launcher is the genuine easy path.

  • New public OpencodeLauncher: default (plain opencode), ollama(model),
    and apply(Seq) for any wrapper. Threaded flow(opencodeLauncher = …) → DefaultFlowContext → OpencodeBackend → OpencodeServer → OpencodeArgs.serve
    (orca appends serve --port 0 --log-level WARN).
  • ollama(model)ollama launch opencode --model <model> --. --model is
    required (headless ollama launch otherwise falls back to interactive
    selection). It pins that one model as the server default, so a bare
    opencode turn routes to it — no withModel. Other Ollama ids are rejected,
    so switching models means relaunching.
  • Teardown SIGINTs the process tree (CliProcess.sendSigIntTree, used only
    by the server) so a forking launcher can't orphan the real opencode serve.
flow(OrcaArgs(args), opencodeLauncher = OpencodeLauncher.ollama("qwen3-coder")):
  opencode.autonomous.run("")   // routes to ollama/qwen3-coder

Docs

README documents both Ollama paths (launcher vs. manual opencode.json +
withModel); ADR 0017 records the decision.

Testing

65 opencode unit tests pass, including one asserting orca spawns the
launcher-wrapped argv. Not verified end-to-end against a live model here
(CPU-only + Ollama's model registry firewalled): validated by inspecting a
launcher-started server's injected /config (the ollama provider and default
model are present); a real agentic turn was too slow to finish on a 0.5B model.
A real-model run on capable hardware is recommended before relying on it.

adamw and others added 2 commits June 12, 2026 20:01
The shared `opencode serve` process started and stopped silently: the
raw `spawn:` line shows `--port 0`, not the bound port, and there was no
teardown line. Log the resolved listening URL on start and a stopping
line at scope teardown (DEBUG → /tmp/orca-*.log), so the long-lived
server is visible in the per-run trace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the `opencode serve` launch command configurable, so a flow can
start the server via `ollama launch opencode` — which injects Ollama's
generated provider config (OPENCODE_CONFIG_CONTENT) — instead of the
hardcoded `opencode` binary. This is the zero-config path for local
Ollama models (issue #10).

- OpencodeLauncher: `default`, `ollama(model)`, and `apply(Seq)` for any
  wrapper. Threaded flow → DefaultFlowContext → OpencodeBackend →
  OpencodeServer → OpencodeArgs.serve (which appends `serve --port 0
  --log-level WARN`). Exposed as `flow(opencodeLauncher = …)`.
- `ollama(model)` needs the model: `ollama launch` falls back to
  interactive selection headless otherwise. It pins that one model as the
  server default, so bare `opencode` routes to it (no `withModel`).
- Teardown SIGINTs the process tree (CliProcess.sendSigIntTree, used only
  by the server) so a forking launcher can't orphan the real serve process.
- README documents both Ollama paths; ADR 0017 records the decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JD557

JD557 commented Jun 13, 2026

Copy link
Copy Markdown

I haven't compiled the code to test, but one thing that pops out to me from the description and the tests:

ollama(model) → ollama launch opencode --model --. --model is
required (headless ollama launch otherwise falls back to interactive
selection)
. It pins that one model as the server default, so a bare
opencode turn routes to it — no withModel. Other Ollama ids are rejected,
so switching models means relaunching.

(Emphasis mine)

This is true BUT if the model doesn't exist in the machine (this includes missing versions, for example, if I do ollama launch opencode --model gemma4 instead of ollama launch opencode --model gemma4:26b), ollama launches an interactive prompt asking do download the model.

image

I don't know how Orca will behave if that happens. The alternative is to pass a --yes flag. Unfortunately, there is no equivalent --no flag.

I don't know which would be the best default. Personally I think this is fine as is (I think I would rather have Orca crash/halt than to have it use the wrong model). But again, I didn't really check what happens if it tries launch an interactive prompt.

A serve that exited without binding (e.g. `ollama launch --model X` for a
model that isn't pulled) threw a generic "did not report a listening URL"
while the actionable reason went to inherited stderr. Now pipe and drain
stderr (tracing each line, keeping a bounded tail) and include it in the
start-failure error, so the message reads e.g. `opencode serve did not
start: … model "X" not found; run 'ollama pull X' …`.

Also keeps serve's stderr out of the console on the success path (it goes
to the trace at DEBUG instead).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adamw

adamw commented Jun 14, 2026

Copy link
Copy Markdown
Member Author

@JD557 good question :) so in case the model was missing, the flow was failing (it's a non-interactive terminal), but with a cryptic error message. So now you need to start ollama serve, and then when you use a non-existent model, you get:

▸ hi
ERROR orca.flow - flow aborted: opencode serve did not start:
Error: model "bogus:404" not found; run 'ollama pull bogus:404' first, or use --yes to auto-pull

You can't really use --yes so pulling is the only option

@JD557

JD557 commented Jun 14, 2026

Copy link
Copy Markdown

Thanks for confirming, looks good to me, then.

Thanks for the fix 👌

@adamw adamw merged commit 8759211 into master Jun 14, 2026
4 checks passed
@adamw adamw deleted the log-opencode-server-lifecycle branch June 14, 2026 19:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proper support for ollama launch

2 participants