Skip to content

Coding agent CLIs

twcli launch opens Claude Code, Codex, or opencode with Tileward as their model backend.

twcli launch claude
twcli launch codex
twcli launch opencode

Each picks the account's configured default served model unless you pass --model:

twcli launch claude --model Tileward-Qwen3.8-27b

With no --model and no configured default, a single served model is used with no fuss. More than one, and there's no way to guess which one you meant, so you're asked from a table of what's served:

$ twcli launch claude
                            Available models
#  id                        context  $/M in  $/M out  compression ratio
1  Tileward-Qwen3.6-35B-A3B  262,144  0.12    0.99     2.8
2  gpt-oss-20b               8,192    0.12    0.28     1
3  Tileward-Qwen3.8-27b      262,144  0.12    0.96     1.79
Pick a model [1-3]:

Answer once, or set a default so future launches skip the question:

twcli config set model Tileward-Qwen3.8-27b

Running from a script or CI with no default configured fails with a clear error instead of prompting into a pipe that will never answer.

Extra arguments go straight to the underlying tool — put them after -- if they could be mistaken for one of twcli's own flags:

twcli launch claude -- -p "explain this repo"
twcli launch codex -- exec "explain this repo"
twcli launch opencode -- run "explain this repo"

Why this isn't just an environment variable

opencode already speaks Tileward's real /v1/chat/completions — launch opencode just writes a provider.tileward block into opencode's own config and runs it. No proxy involved.

Claude Code and Codex don't. Claude Code only speaks the Anthropic Messages API; Codex dropped support for chat completions entirely in Codex 0.122 and speaks only the OpenAI Responses API. Tileward's own /v1/messages is a governed passthrough to a customer's own Anthropic key, by deliberate decision, and there is no /v1/responses at all — neither reaches Tileward's own served models. So launch claude and launch codex each start a small local HTTP server for the run's lifetime, bound to 127.0.0.1 only, that translates the tool's own protocol into a Tileward chat completion and back, then tear it down when the tool exits. The real tw_live_… key never leaves twcli's own process — the child only ever sees a random per-run token.

Talks to Tileward How
launch opencode directly opencode's own OpenAI-compatible provider
launch claude through a local proxy ANTHROPIC_BASE_URL points at 127.0.0.1
launch codex through a local proxy a tileward provider + profile in config.toml

claude

Sets ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_MODEL, and ANTHROPIC_SMALL_FAST_MODEL (defaults to the same model; override with --fast-model) on the child process only. An inherited ANTHROPIC_API_KEY is unset for that process, so a real Anthropic key sitting in your shell can't silently take over and send the session to api.anthropic.com instead.

twcli launch claude --model Tileward-Qwen3.6-35B-A3B --fast-model gpt-oss-20b

It also points CLAUDE_CONFIG_DIR at <config dir>/claude-launch, never your real ~/.claude. If Claude Code has an active claude login session on the machine, it keeps using that Keychain-stored credential for the actual model call even with ANTHROPIC_AUTH_TOKEN set — confirmed against a real proxy (the login's OAuth token showed up on the wire; the auth token never did), a mismatch from Claude Code's own documented precedence. A separate, empty config directory gives it nothing to fall back to. This is why the first launch claude on a machine goes through tool-permission and trust prompts again — they're being asked once for that isolated directory, not for your regular claude setup, and later launches reuse it.

It declares the model's context window, less Claude Code's output reserve. Claude Code can't know a Tileward model's context size — for a model id it doesn't recognize it assumes 200k — and it compacts the conversation a roughly fixed ~24.8k tokens below whatever window it has. Every request also reserves Claude Code's output budget (32,000 tokens unless CLAUDE_CODE_MAX_OUTPUT_TOKENS says otherwise) out of the same served window, so declaring the full served size isn't safe: at 262,144, compaction fires near 237.3k, while the server rejects any prompt over 230,144 alongside a 32,000-token reserve. So launch claude sets CLAUDE_CODE_MAX_CONTEXT_TOKENS to the served context less that reserve — 230,144 for a 262,144-token model — and compaction fires near 205.3k instead. Measured against a stub server standing in for the model, not taken from docs. A CLAUDE_CODE_MAX_CONTEXT_TOKENS you set yourself is left alone, and a model whose context can't hold the output reserve gets a warning and no value.

It logs each compaction. Claude Code runs hooks around a compaction: PreCompact before it, PostCompact with the summary, and SessionStart (source compact) on the next turn. launch claude passes one handler for all three as --settings <json>. It appends a line of metadata per event — trigger, session id, transcript path, summary size, never the summary itself — to <config dir>/claude-launch/compactions.jsonl, prints nothing, and always exits 0, so the session behaves exactly as it would without it. Claude Code keeps only the last --settings it is given rather than merging them, so if you pass your own after --, yours is used and the hooks are left out for that run.

twcli launch claude --no-compaction-hooks

codex

Codex reads its provider from config.toml, not the environment, so each launch writes a profile named tileward-<pid> — codex --profile <name> layers $CODEX_HOME/<name>.config.toml on top of the base config — carrying a model_providers table and the model_provider/model selection, then runs codex --profile tileward-<pid>. Your real config.toml is never touched at all: the file is written fresh for this process and deleted when it exits. It's keyed to the pid rather than a fixed name so two launch codex sessions running at once can't race on the same file and cross-wire which proxy each one talks to.

twcli launch codex --model Tileward-Qwen3.8-27b

opencode

Merges a provider.tileward block into ~/.config/opencode/opencode.json (or $OPENCODE_CONFIG, or the existing opencode.jsonc if you have one). If that file isn't plain JSON — opencode also accepts JSONC, comments and all, which this can't safely rewrite — nothing is written and the block is printed for you to add by hand instead.

twcli launch opencode --model Tileward-Qwen3.6-35B-A3B

When a request fails

The proxy shares your terminal with the tool, so it never prints there. If the tool abandons a request — you press Esc, or it gives up waiting — the proxy drops it quietly and stops reading the model's answer. A failure comes back to the tool as an ordinary API error, which it shows and retries the way it would for any provider. Anything unexpected is appended to <config dir>/proxy-errors.log.

Requests the tool makes without streaming are still streamed from Tileward and assembled locally, so a long answer isn't cut off by a read timeout while the model is still writing it.

What you give up going through claude or codex

Tool calling on Tileward's own models is measured, not assumed — see Tileward Models for where that stands per model. A tool call the model itself returns malformed JSON for degrades to an empty {} argument rather than crashing the proxy or the session; that's the model's own reliability showing through the translation, not a bug in the translation itself.

Governance still runs on every call — the proxy is a shape change in front of Tileward's API, not a bypass of it. A refusal comes back as a normal turn (Claude Code sees a refusal stop reason; Codex sees a completed response) rather than an HTTP error, matching how each tool's own provider would report one.

A system-role message is only ever sent first, whatever position it arrived in — Claude Code's mid-conversation-system beta can revise instructions partway through a long session, and Tileward's backend 400s on a system message that isn't the first entry, so every one found is folded into a single leading message instead. Found running a real agentic session through the proxy, not a synthetic test case.