Skip to content

opencode through the gateway

opencode is a real coding agent. For the demo video it plays DataBot: its model calls and its tools both go through the gateway, so the same policy, controls, session risk and audit apply to it as to the demo agent. docs/video.md has the beats.

Run it

make demo-up REMOTE=1                 # the stack, demo overlay (demo-web) and the remote LLM
make opencode AS=anna                 # the TUI, as anna@demo
make opencode AS=bartek ARGS='run "How many customers do we have?"'   # one turn, no TUI

make opencode (demo/opencode/launch.py):

  1. Mints a fresh token for <AS>@demo on the opencode agent from the operator API (POST /auth/demo-token). Every launch is therefore a new gateway session: no taint, no risk, fresh session budgets. The token lives one hour (the issuer's maximum). A session that runs longer starts failing with 401 token_expired; quit and run make opencode again (that is also how to reset a tainted session between takes).
  2. Picks the model the selected upstream serves, from /v1/models: deepseek-v4.1-flash in remote mode, qwen3:8b on local Ollama.
  3. Starts the pinned opencode (npx -y opencode-ai@1.18.34) in demo/opencode/.home/<AS>/ (gitignored), with HOME and every XDG_* directory pointing there and the npm cache in demo/opencode/.home/npm-cache/. Nothing reads or writes ~/.config/opencode, ~/.claude or ~/.npm. (opencode reads skills from ~/.claude/skills when HOME is the real one; that is why HOME is isolated too.) The workspace is its own git repository, so opencode never takes this repo, or its instructions, as the project.

The token reaches opencode only through its environment (ACL_OPENCODE_TOKEN). The tracked config, demo/opencode/opencode.json, refers to it as {env:ACL_OPENCODE_TOKEN} and holds no secret. The environment is built from an allowlist (PATH, TERM, locale), so no provider key from your shell (OpenAI, Anthropic, OpenRouter) reaches opencode: it can only talk to models through the gateway. Delete demo/opencode/.home/<AS>/ to clear opencode's local session history.

When scripting run, give it no stdin (`