Show the model behind each choice; toolchains in the sandbox
- runner: model menu carries the public model name; each run's session opens with a note naming the model - sandbox image: Rust, Go, Python and build tools, so an agent can build and test what it changes
7 files+36−160/7 viewed
| 41 | 41 | ||
| 42 | 42 | When you start a run you pick how much model to spend on it: | |
| 43 | 43 | ||
| 44 | − | | Choice | Use it for | | |
| 45 | − | | --- | --- | | |
| 46 | − | | **Balanced** | Most tasks. The default. | | |
| 47 | − | | **Deep** | Hard problems that need the strongest reasoning. Slower and costlier. | | |
| 48 | − | | **Fast** | Small, well-defined changes. | | |
| 44 | + | | Choice | Model today | Use it for | | |
| 45 | + | | --- | --- | --- | | |
| 46 | + | | **Balanced** | Claude Sonnet 5.5 | Most tasks. The default. | | |
| 47 | + | | **Deep** | Claude Opus 5.5 | Hard problems that need the strongest reasoning. Slower and costlier. | | |
| 48 | + | | **Fast** | Claude Haiku 4.5 | Small, well-defined changes. | | |
| 49 | + | ||
| 50 | + | The menu always shows which model each choice runs on, and every attempt's | |
| 51 | + | session opens with a note naming the model that produced it. You are paying | |
| 52 | + | for model usage, so you can always see what you are getting. | |
| 49 | 53 | ||
| 50 | − | These are g1t's names, not a vendor's. Which model stands behind each one is | |
| 51 | − | g1t's choice and can change without your intents or automations changing. | |
| 52 | − | An agent's attempt carries the label `g1t-agent`. | |
| 54 | + | The choices keep their names when the model behind one is upgraded, so | |
| 55 | + | intents and automations that say "Balanced" keep working. An agent's | |
| 56 | + | attempt carries the label `g1t-agent`. | |
| 53 | 57 | ||
| 54 | 58 | ## How model traffic is routed | |
| 55 | 59 |
| 247 | 247 | > | |
| 248 | 248 | {agentModels.map((model) => ( | |
| 249 | 249 | <option key={model.id} value={model.id} title={model.description}> | |
| 250 | − | {model.label} | |
| 250 | + | {model.label} · {model.modelName} | |
| 251 | 251 | </option> | |
| 252 | 252 | ))} | |
| 253 | 253 | </select> |
| 63 | 63 | let auth = auth_option(&env("G1T_USER")?, &env("G1T_TOKEN")?); | |
| 64 | 64 | let workdir = Path::new(WORKDIR); | |
| 65 | 65 | ||
| 66 | + | if let Ok(model) = std::env::var("AGENT_MODEL_NAME") { | |
| 67 | + | reporter.record(Entry::new("note", &format!("Running on {model}."))); | |
| 68 | + | } | |
| 66 | 69 | reporter.record(Entry::new("prompt", &prompt)); | |
| 67 | 70 | reporter.flush(); | |
| 68 | 71 |
| 8 | 8 | id: string; | |
| 9 | 9 | label: string; | |
| 10 | 10 | description: string; | |
| 11 | + | /** The model behind it, by its public name, e.g. `Claude Sonnet 5.5`. */ | |
| 12 | + | modelName: string; | |
| 11 | 13 | }; | |
| 12 | 14 | ||
| 13 | 15 | export type RunHostedInput = { |
| 1 | − | # The sandbox a hosted attempt runs in: git, Claude Code, and the g1t runner. | |
| 1 | + | # The sandbox a hosted attempt runs in: git, the agent, the g1t runner, and | |
| 2 | + | # the toolchains an agent needs to build and test what it changes. | |
| 2 | 3 | # Build context: the repository root. | |
| 3 | 4 | ||
| 4 | 5 | # The same Debian release as the runtime image, so glibc matches. | |
| 11 | 12 | ||
| 12 | 13 | FROM node:22-bookworm-slim | |
| 13 | 14 | RUN apt-get update \ | |
| 14 | − | && apt-get install -y --no-install-recommends git ca-certificates \ | |
| 15 | + | && apt-get install -y --no-install-recommends \ | |
| 16 | + | git ca-certificates curl build-essential pkg-config libssl-dev \ | |
| 17 | + | python3 python3-pip python3-venv golang-go ripgrep jq \ | |
| 15 | 18 | && rm -rf /var/lib/apt/lists/* \ | |
| 16 | 19 | && npm install --global @anthropic-ai/claude-code \ | |
| 17 | 20 | && mkdir /work && chown node:node /work | |
| 19 | 22 | # Claude Code refuses to skip permission prompts as root. | |
| 20 | 23 | USER node | |
| 21 | 24 | ENV HOME=/home/node | |
| 25 | + | # Rust, for the agent's own use, installed for the user it runs as. | |
| 26 | + | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ | |
| 27 | + | | sh -s -- -y --profile minimal --default-toolchain stable | |
| 28 | + | ENV PATH=/home/node/.cargo/bin:$PATH | |
| 22 | 29 | ENTRYPOINT ["g1t-runner"] |
| 29 | 29 | */ | |
| 30 | 30 | HOSTED_AGENT_USERS: string; | |
| 31 | 31 | /** | |
| 32 | − | * The models offered, as JSON: `[{ id, label, description, model }]`. | |
| 33 | − | * `model` is the provider's model name and is never shown to users. | |
| 32 | + | * The models offered, as JSON: | |
| 33 | + | * `[{ id, label, description, modelName, model }]`. `modelName` is what | |
| 34 | + | * people see; `model` is the identifier sent to the provider. | |
| 34 | 35 | */ | |
| 35 | 36 | AGENT_MODELS: string; | |
| 36 | 37 | /** | |
| 86 | 87 | const vars: Record<string, string> = { | |
| 87 | 88 | ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY!, | |
| 88 | 89 | ANTHROPIC_MODEL: model.model, | |
| 90 | + | // Recorded at the top of the session, so anyone can see what ran. | |
| 91 | + | AGENT_MODEL_NAME: `${model.modelName} (${model.label})`, | |
| 89 | 92 | }; | |
| 90 | 93 | if (env.AI_GATEWAY_ID) { | |
| 91 | 94 | vars.ANTHROPIC_BASE_URL = `https://gateway.ai.cloudflare.com/v1/${env.CLOUDFLARE_ACCOUNT_ID}/${env.AI_GATEWAY_ID}/anthropic`; | |
| 137 | 140 | ||
| 138 | 141 | async models(viewer: Viewer): Promise<AgentModel[]> { | |
| 139 | 142 | if (!this.allowed(viewer)) return []; | |
| 140 | − | return this.configuredModels().map(({ id, label, description }) => ({ | |
| 143 | + | return this.configuredModels().map(({ id, label, description, modelName }) => ({ | |
| 141 | 144 | id, | |
| 142 | 145 | label, | |
| 143 | 146 | description, | |
| 147 | + | modelName, | |
| 144 | 148 | })); | |
| 145 | 149 | } | |
| 146 | 150 |
| 27 | 27 | "vars": { | |
| 28 | 28 | "HOSTED_AGENT_USERS": "syntaqx", | |
| 29 | 29 | // What a person can choose when starting g1t agents. The first is the | |
| 30 | − | // default. "model" is the provider's name for it and stays server-side. | |
| 31 | − | "AGENT_MODELS": "[{\"id\":\"balanced\",\"label\":\"Balanced\",\"description\":\"Good at most tasks.\",\"model\":\"claude-sonnet-5-5\"},{\"id\":\"deep\",\"label\":\"Deep\",\"description\":\"Strongest reasoning; slower and costlier.\",\"model\":\"claude-opus-5-5\"},{\"id\":\"fast\",\"label\":\"Fast\",\"description\":\"Quick and cheap, for small changes.\",\"model\":\"claude-haiku-4-5-20251001\"}]", | |
| 30 | + | // default. "modelName" is shown to people; "model" is sent to the provider. | |
| 31 | + | "AGENT_MODELS": "[{\"id\":\"balanced\",\"label\":\"Balanced\",\"description\":\"Good at most tasks.\",\"modelName\":\"Claude Sonnet 5.5\",\"model\":\"claude-sonnet-5-5\"},{\"id\":\"deep\",\"label\":\"Deep\",\"description\":\"Strongest reasoning; slower and costlier.\",\"modelName\":\"Claude Opus 5.5\",\"model\":\"claude-opus-5-5\"},{\"id\":\"fast\",\"label\":\"Fast\",\"description\":\"Quick and cheap, for small changes.\",\"modelName\":\"Claude Haiku 4.5\",\"model\":\"claude-haiku-4-5-20251001\"}]", | |
| 32 | 32 | // Set to a Cloudflare AI Gateway id to route model traffic through it. | |
| 33 | 33 | "AI_GATEWAY_ID": "", | |
| 34 | 34 | "CLOUDFLARE_ACCOUNT_ID": "1e6f2cffa3f445920836e8ebe446bb58" |