Skip to content

feat(runner): drive opencode accounts through opencode serve - #216

Merged
kixelated merged 7 commits into
mainfrom
quest/m0/kix/opencode
Oct 24, 2026
Merged

kixelated merged 7 commits into
mainfrom
quest/m0/kix/opencode

Conversation

@kixelated

@kixelated kixelated commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Completes quest/m0/kix/opencode.md: a runner now offers opencode accounts alongside Claude Code ones, so jobs can run on an OpenCode Go plan, a ChatGPT plan, or an API key for any provider opencode supports.

Behavior

  • quest runner account add opencode [--model provider/model] runs opencode auth login in a new account home. That home has its own XDG config, data, cache, and state directories. The account's kind and label come from the providers in that home's auth.json. Only provider IDs and credential types are read, never the credentials. An OAuth login or OpenCode Go counts as a subscription; anything else is an api-key. Examples: ChatGPT (opencode) and OpenCode Go (opencode).
  • Each turn starts opencode serve inside the job's devShell. It listens on 127.0.0.1 on a random port, behind a password made for that turn. The runner drives it through @opencode-ai/sdk v2: it creates or resumes a session, sends one synchronous prompt with the kix system prompt, and streams the session's SSE events as raw output (token deltas are skipped).
  • The kix tools attach as a remote MCP server. Every tool is allowed (the same as Claude Code's bypassPermissions). opencode's own question tool is denied, so every question goes through kix's ask. If a project's own rules still ask for a permission, the runner allows it; a stray question.asked is rejected.
  • Resume: each kix session maps to an opencode session ID, stored in the account's private home under kix-sessions/. Cancel and stop abort the session, then end the server's process group.
  • Limits: an OpenCode Go account_rate_limit retry, a free-tier retry, or a retry whose message names a usage or quota limit makes the runner abort and report limited (until next for OpenCode Go). opencode would otherwise sleep until the reset. A final APIError with a usage or quota message is also a limit. The reset time comes from resets_at or resets_in_seconds in the body, or from retry-after. Brief "rate limit reached" retries are left to opencode.
  • The flake's runner package pins opencode next to Claude Code through QUEST_RUNNER_OPENCODE. A new opencode-sdk flake check keeps @opencode-ai/sdk in package.json on the same release as the pinned pkgs.opencode, now 1.19.0 after merging main's nixpkgs bump.
  • The Adapter interface now covers both CLIs. SignedIn carries the provider, and Account.provider is what goes to kix. The kix service is unchanged.

Choices made without asking

  • The account's XDG directories apply only to opencode. opencode reads its config, credentials, and sessions only from the XDG base directories. They are set with /usr/bin/env inside the devShell, so nix develop still reads the user's nix.conf. A small per-launch opencode plugin (a shell.env hook) gives the agent's commands the user's XDG directories back. A devShell that sets its own XDG_* values does not get them back in agent commands.
  • enabled_providers is limited to the account's own credentials, so opencode's free Zen models and other providers never leak into an account. opencode's environment also drops OPENCODE_* and credential-named variables (*_API_KEY, *_API_TOKEN, *_AUTH_TOKEN, AWS-style access and session keys, *BEARER_TOKEN*), because some providers prefer an environment key over the stored one. This matches by name, so it is best effort.
  • --model is added. It is stored in the account's own opencode.json and passed in each launch's config, so it outranks a project's opencode.json. Without it, the project's choice or opencode's default applies.
  • quest runner account add [cli] takes a positional argument (claude by default). --console is for Claude Code only, and --model is for opencode only.
  • The account's provider is the first signed-in provider ID, cleaned up to match kix's ID rules.
  • The opencode-sdk flake check fails when the nixpkgs pin moves opencode and the SDK does not move with it, as the bump merged from main did. That keeps the server and client on one release, but a Dependabot nix or bun bump will need a paired bump. Dropping the check is the alternative.

Validation

  • just check and just test pass after merging main.
  • New tests in test/runner.test.ts:
    • A fake opencode serve drives the real runner through a checkpoint, a question, and a resume in the same session. The test checks the password, the MCP bearer header, enabled_providers, the question deny, and the XDG split.
    • A cancel aborts the session, and an OpenCode Go limit retry reports limited with its reset.
    • Unit tests cover credential labels, limit detection, and account add opencode with --model.
  • A manual smoke run used the real pinned opencode, both 1.18.34 and 1.19.0, and the free opencode/big-pickle model. It covered a shell command ($XDG_CONFIG_HOME came back as the user's), a kix_checkpoint MCP call, a resumed session that remembered the first turn, and a stop that aborted a sleep 60 in about 8 seconds.

Not verified here

Signing in to real OpenCode Go and ChatGPT plans and hitting their limits needs real accounts. The kix epic's live rehearsal now lists these checks, including whether ChatGPT plans fall short enough to plan a separate Codex adapter.

Follow-ups

  • kixelated/quest#212 (the job sandbox) also changes src/runner/claude.ts. Whichever PR merges second resolves the conflict. The sandbox should wrap opencode serve too, using sandbox-runtime, and check how much of the account home the agent's commands can read. They can still read OPENCODE_SERVER_PASSWORD and the per-turn MCP token, as they can read Claude Code's MCP config today.
  • opencode's limit messages are matched by text. If the live rehearsal shows other wording, extend LIMIT in src/runner/opencode.ts.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 23, 2026 23:04
Quest-Claim: 91eed9ed7411e89d
A runner now offers opencode accounts alongside Claude Code ones, covering
OpenCode Go, ChatGPT plans, and API-key providers. Each account signs in with
`opencode auth login` in its own XDG home; each turn runs `opencode serve` on
localhost behind a fresh password inside the job's devShell and drives one
session through the SDK, with the kix tools as a remote MCP server and
opencode's question tool denied. Limits abort the turn and pause the account.

The flake pins opencode for the runner and checks the SDK matches it.

Completes quest/m0/kix/opencode.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Drive opencode from the runner feat(runner): drive opencode accounts through opencode serve Oct 24, 2026
@kixelated
kixelated marked this pull request as ready for review October 24, 2026 07:13
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-24T15:54:42.619013Z 705a68e New commits
🔒 Security Review ✅ Completed 2026-10-24T15:58:23.285537Z 705a68e New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2dfa2340e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/runner/opencode.ts Resolved
Comment thread src/runner/opencode.ts Resolved
…count model

Provider credentials in the runner's environment could outrank the account's own sign-in, and a project's opencode.json could override the account's --model. Strip credential-named variables from opencode's environment, and pass the account's model in the launch config, which loads after project config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Landing summary

Lands 705a68e as a squash merge. It completes quest/m0/kix/opencode.md: runners now offer opencode accounts, driven through opencode serve and @opencode-ai/sdk, next to Claude Code accounts.

Since the last review round

  • Merged main twice, never rebased:
    • c31b208 brought in kixelated/quest#210 (runner hardening). The only conflict was the README runner paragraph. The resolution keeps both changes: Claude Code and opencode are both pinned, both QUEST_RUNNER_CLAUDE and QUEST_RUNNER_OPENCODE are documented, and so is Fix the runner's post-merge review findings #210's "one quest runner runs in a home at a time". The src/runner changes (the home hold() and waiting on attempt.finished after a replaced connection) merged without conflicts and work with the opencode adapter. Codex's code and security reviews of c31b208 finished with no findings.
    • 705a68e brought in kixelated/quest#205 with no conflicts.
  • Both Codex findings on b2dfa23 were fixed in faddef8, and their threads are resolved.
  • Local just check and just test pass on 705a68e, and quest check --base origin/main --branch quest/m0/kix/opencode passes. The required CI checks pass. The optional native Windows (windows-11-arm) job failed in test/gate.test.ts ("concurrent processes never admit past the headroom"). This PR doesn't touch that test, and the same job passed on c31b208.

Decisions (maintainer)

  • Keep the opencode-sdk flake check. It keeps @opencode-ai/sdk on the same release as the pinned pkgs.opencode. The cost is that a Dependabot nix or bun bump that moves only one of them will need a paired bump.
  • Defer the real-plan checks. Signing in to real OpenCode Go and ChatGPT plans, and checking how their limits are reported, is left to the kix epic's live rehearsal. That rehearsal also decides whether ChatGPT plans fall short enough to plan a separate Codex adapter.

Follow-ups

  • kixelated/quest#212 (job sandbox) merges main after this. It should also wrap opencode serve and limit what agent commands can read from the account home.
  • If the live rehearsal shows other limit wording, extend LIMIT in src/runner/opencode.ts.
  • The Windows gate.test.ts headroom test looks flaky on windows-11-arm.

(Written by Claude Opus 5.5)

@kixelated
kixelated merged commit 0ae6d70 into main Oct 24, 2026
12 of 13 checks passed
@kixelated
kixelated deleted the quest/m0/kix/opencode branch October 24, 2026 16:00
kixelated added a commit that referenced this pull request Oct 24, 2026
Resolves the opencode adapter (#216) against the job sandbox: opencode
now enters the devShell the runner built from the job's revision, through
the sandbox's env script, instead of `nix develop` on the checkout. Its
commands are not yet confined; wrapping `opencode serve` in
sandbox-runtime stays in quest/m0/kix/sandbox.md, which takes over the
note from the deleted opencode quest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant