Draft: point an agent at OpenRouter, with the user's key and never ours - #127
Draft
Amitgb14 wants to merge 3 commits into
Draft
Draft: point an agent at OpenRouter, with the user's key and never ours#127Amitgb14 wants to merge 3 commits into
Amitgb14 wants to merge 3 commits into
Conversation
OpenRouter and this tool's routing solve different problems — that one switches whole agents when one stops working, this switches what a single agent talks to — and they compose. `gateway:` in the user's own config is that composition: name the agents, and their API calls go to an OpenAI-shaped gateway instead of the vendor. **sandbox-cli supplies no key and holds no account.** `key_env` is a *name*; the value arrives the way every other credential does, forwarded from the user's environment or resolved by the broker from `secrets:`, and never passes through the code that builds the argv. A run configured for a gateway with nothing to read is refused, because both silent alternatives are wrong: reach the gateway unauthenticated, or fall through to the vendor on the agent's own credential and spend the wrong account against the wrong endpoint. Four facts have to agree or the run is worse than unconfigured, so one function resolves them together — base URL, key variable, probe host, egress domain. The last two are the ones that would otherwise be got wrong quietly: probing the *vendor* while talking to a gateway makes routing skip the agent exactly when the vendor is down, which is the outage a gateway is bought to survive; and an allowlist that does not permit the gateway fails as a connection error from the agent, naming nothing. Two refusals rather than best-effort support. An agent that speaks its vendor's own API shape is refused, because a base URL cannot translate a protocol and the failure lands in a container as a parse error blamed on the model — so the table lists opencode (its own feature; verified) and codex (documented shape; marked unverified) and deliberately not claude, gemini or droid. And a plaintext base_url is refused outright. `gateway:` is user-config only, refused from a project file: it names the host every prompt travels through and the credential that pays for it. Draft because the codex pairing has not been run end to end, and because the Studio surface for this is not built — the config path is complete and tested.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The Routing screen could not have shown this correctly from what the daemon reported: `host` is what routing probes, and for a gateway agent that is openrouter.ai — so a picture built from it would draw the gateway as the agent's vendor. `ProviderStatus.Gateway` is the separate fact, and the two are equal today only by coincidence. The graph puts it below the ring rather than in it, and the shape carries the argument. Agents in the ring are alternatives — the chain falls from one to the next — while agents on a gateway are the opposite of alternatives: they share one credential, one bill and one point of failure that no chain can route around. If the gateway is down, every agent on it is down together, which is precisely what a ring of interchangeable nodes would deny. Their nodes take a dashed ring so the row and the node are recognisably one fact, the providers list says *via <host>*, and the health shown is the gateway's own — the vendor behind it being unreachable is the case a gateway is bought to survive, so measuring the vendor would skip the agent that still worked.
…cues Three panels, and a correction that had to come with them. **If you launched now** resolves each chain against the probe results already on the page. That join — provider list says claude is down, chain list says claude falls back to codex — was left to the reader, which is a strange thing to leave to a reader on the one screen whose subject is what happens automatically. It keeps the daemon's rule that unprobed is not down: an agent nothing was asked about is taken rather than skipped, since skipping it acts on a measurement nobody made. **Why they fired** ranks the reasons in the words the runs recorded — grouping them would be this screen inventing a taxonomy the log does not have, and a column of "provider answered 503" and a column of "exited 1 having changed nothing" call for different actions. **Asked for, and ran** puts two bars per agent, because the interesting number is the gap: an agent finishing work it was never asked for is doing it under its own login and its own bill. The correction is that the third panel would have been drawn on a lie. A detached run's audit line is written when the container launches — there is no exit code to wait for — so it carries 0 whatever happens, and *every* Studio run is detached. The rescue rate on this screen was therefore 100% by construction and the trend's still-failed series unreachable. An episode ending in a detached attempt now reports **not recorded**: its own counter, its own band, its own row badge. The same rule the rest of the tool keeps about absent readings — a missing measurement is not a good one.
Amitgb14
added a commit
that referenced
this pull request
Aug 21, 2026
Twenty-one user-facing entries in the sixty-five commits since beta.15: Studio's projects registry and file browsing, conversation resume and cross-agent handoff, the routing screens, a published TypeScript SDK, the terminal fix that cost goose its banner, and today's two submodule bugs. One entry moved back to Unreleased rather than shipping: the `gateway:` block that points an agent at OpenRouter lives in #127, which is still a draft. Its changelog text had ridden onto main with an unrelated Routing commit, so the release would have announced a config key nobody could use. What main actually has is OPENROUTER_API_KEY in the env allowlists — your own key forwarded by name, which is a much smaller claim. The version is pinned in two places and both move together: version.go, which the build stamps, and web/src/lib/site.ts, which the site prints.
Amitgb14
added a commit
that referenced
this pull request
Aug 22, 2026
Three entries: the podman network reaper (issue #77), the SDK's sequential-run fix, and the travel-planner example. The OpenRouter `gateway:` entry stays in Unreleased for the second release running — #127 is still a draft, so `main` has no such config key and shipping the note would announce something nobody can use.
Amitgb14
added a commit
that referenced
this pull request
Aug 23, 2026
Five entries: the Python SDK, `addProject(init: true)` and the empty-worktree refusal, and the two SDK bugs the Python review found in both clients — a failover reported as a failure, and a misspelled run option ignored. The OpenRouter `gateway:` entry stays in Unreleased for the third release running. #127 is still a draft and now conflicting, so main has no such config key and shipping the note would announce something nobody can use.
Amitgb14
added a commit
that referenced
this pull request
Aug 26, 2026
Six entries: cline reaching Studio and both SDKs, a Devin adapter, the Python client's start() and its two examples, and the distribution rename to sandbox-cli-sdk. The OpenRouter `gateway:` entry stays in Unreleased for the fourth release running. #127 is a conflicting draft, so main has no such config key and shipping the note would announce something nobody can use.
Amitgb14
added a commit
that referenced
this pull request
Aug 27, 2026
Two entries: six agents removed and a Kilo Code adapter added. The roster is twelve. `agent: droid` in an existing fleet.yaml is refused from this version on, which is the one thing in here that can break a file somebody already has. Droid shipped in beta.19 yesterday, so anyone on that release meets the refusal on upgrade rather than reading about it — hence the release note leading with it rather than with what was added. The OpenRouter `gateway:` entry stays in Unreleased for the fifth release running: #127 is still a conflicting draft, main has no such config key, and shipping the note would announce something nobody can use. Also corrects the site's meta description, which said "Claude Code, Codex, Gemini and 12 more agents" — fifteen, and the count nobody grep'd because the number is in prose rather than beside the word "adapter". It is the description search results show, so it was the roster claim with the widest reach and the least chance of being noticed here. Neither SDK is republished. The only change under sdk/ since beta.19 is four lines of doc comment in contract.ts, so the published packages differ from this tree by prose that names droid and nothing else; spending a version number on that would make the next real SDK release harder to find. It is worth knowing the published contract.d.ts says droid until whatever ships next.
Amitgb14
added a commit
that referenced
this pull request
Aug 27, 2026
Twenty pre-releases is enough. The isolation boundary, the profiles, the
agent roster and the Studio contract have all been stable for several
releases; continuing to ship "beta.N" says more about the habit than about
the software. Nothing in the boundary changes here — this is beta.20 plus
the two fixes it carries.
The number stays conservative deliberately. 0.0.1 is the first stable tag,
not a claim of feature-completeness: open-items.md is still a live backlog
and the agent still holds raw credentials inside the container. What
"stable" is claiming is narrower — that the contracts above do not move
without a changelog entry saying so.
Three things this changes beyond the number:
goreleaser's `prerelease` goes false, which its own comment has been
waiting for since the first beta ("Set this to false for a stable
release"). GitHub stops labelling the release Pre-release. The install
script is unaffected either way — it reads the releases list newest-first
rather than /releases/latest, precisely so a repository whose releases were
all pre-releases still resolved.
The site's go-install note had to narrow rather than stay put. It said the
tags "are not valid semver", which was true of 0.0.1beta.8 — no `-` before
the pre-release — and stops being true of 0.0.1, which is a perfectly valid
version. Go still cannot pin it, for the one reason left: a module tag
needs a `v` prefix and ours has none. Adopting v-prefixed tags would fix
that and is a real improvement, but it renames every release asset and
every --version example, so it is its own change and not a side effect of
this one.
The OpenRouter `gateway:` entry stays in Unreleased for the sixth release
running, for the sixth time for the same reason: #127 is a conflicting
draft and main has no such config key.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OpenRouter and sandbox-cli routing solve different problems — that one switches
whole agents when one stops working, this one switches what a single agent talks
to — and they compose.
gateway:is that composition.The rule this is built around
sandbox-cli supplies no key and holds no account.
key_envis a name; thevalue arrives the way every other credential does — forwarded from your
environment if set, or resolved by the broker from
secrets:— and never passesthrough the code that renders the argv. A run configured for a gateway with
nothing to read is refused, because both silent alternatives are wrong:
reaching the gateway unauthenticated, or falling through to the vendor on the
agent's own credential and spending the wrong account against the wrong endpoint.
Four facts, resolved together
The base URL, the key variable, the probe host and the egress domain have to
agree or the run is worse than unconfigured. The last two fail quietly:
exactly when the vendor is down — the outage a gateway is bought to survive;
the agent, naming nothing.
Two refusals rather than best-effort support
refused, not pointed at an OpenAI-shaped endpoint: a base URL cannot translate a
protocol, and the failure would land in a container as a parse error blamed on
the model. The table lists
opencode(its own feature, verified) andcodex(documented shape, marked unverified).
base_urlis refused: the credential and every prompt cross it.gateway:is user-config only, refused from a project.sandbox.yaml— it namesthe host every prompt travels through and the credential that pays for it, which
is
providers:'s three objections at once plus one of its own, since a gatewayreads the work.
Why draft
It should be exercised against a real key before this is presented as supported.
through a gateway nor lets one be chosen. The config path is complete and tested.
gateway:belongs insandbox-cli doctor.Tests cover the key never being ours, the shape refusal naming what would work,
per-agent opt-in, the plaintext refusal, probe-follows-the-gateway with
providers:still winning, and end to end throughBuildSpec: refusal with nokey, base URL in the container, key forwarded by name with no value in the spec,
and the host joining the allowlist.
🤖 Generated with Claude Code