Reuse Anthropic's `[1m]` long-context suffix as a model-list annotation that works across both protocol families. - A string-form model `"name[1m]"` decodes to base id `name` + context_window 1000000. Any model (string or object form) with context_window >= 1000000 exposes two selectable variants: plain `name` and long `name[1m]`. Object-form ids are taken verbatim (a literal trailing `[1m]` is a plain name, never decoded). - New `LaunchModel` carries per-protocol wire names resolved once at launch: Anthropic gets the variant label (marker kept), OpenAI gets the base id (marker stripped) plus a forced `model_context_window` for the long variant only. - `default_model` / `--model` accept either variant label; shell completion and describe-help expand variants; `models list` keeps base rows and prints a hint about available `[1m]` variants. - `--force model` synthesizes a plain LaunchModel from the literal name instead of panicking; an empty model list with `--model` now returns a guided error. Duplicate selectable labels (variant collision across entries) emit a warning. - Drop the old flat `sanitize_chat_completions_model` / `wire_model_id` stripping; the variant model supersedes it. - Document the feature in README.md and add a sample in config.demo.yaml. |
||
|---|---|---|
| docs | ||
| scripts | ||
| src | ||
| .envrc | ||
| .gitignore | ||
| AGENTS.md | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| config.demo.yaml | ||
| deepseek-pricing.json | ||
| default.nix | ||
| flake.lock | ||
| flake.nix | ||
| nvchecker.toml | ||
| README.md | ||
| shell.nix | ||
agent-run
agent-run is a small launcher for coding agents.
It keeps provider settings in one place, then adapts them for each target agent instead of asking you to manually rewrite per-agent config files every time.
Current focus:
claudecodex
Current protocol support:
anthropicopenai-responsesopenai-chat-completions
Why
Different coding agents expect different configuration shapes:
- some read environment variables
- some want a config file or profile
- some mix both
agent-run gives you one provider config and adapts it to the target agent at launch time.
The provider config stays generic; each agent adapter is responsible for mapping that generic config into the shape the downstream tool expects.
What It Does
- Centralizes provider definitions in one config file
- Resolves secrets from either
keyorkey_command - Optionally loads model lists from provider APIs and caches normalized results on disk
- Validates protocol compatibility before launch
- Negotiates the final protocol for agents that support more than one wire API
- Generates temporary runtime config where needed
- Forwards extra args to the underlying agent command
Supported Agents
Claude Code
- protocol:
anthropic - launch mode: environment variables
- supports both
ANTHROPIC_AUTH_TOKENandANTHROPIC_API_KEY - ensures onboarding is marked complete before launch
Codex
- protocol:
openai-responses - launch mode: uses
~/.codexby default, or an isolatedCODEX_HOMEwhen configured - touches/injects provider config instead of overwriting
config.toml - selects the model via generated profile
<provider>-<normalized-model>.config.toml
Shell
- exposes all supported protocols as environment variables
- launches a shell (or any command) with
ANTHROPIC_*/OPENAI_*set from the provider
Configuration
Default config path:
~/.config/agent-run/config.yaml
Minimal example:
providers:
deepseek:
protocols:
- openai-chat-completions
- anthropic
base_urls:
openai: https://api.deepseek.com
anthropic: https://api.deepseek.com/anthropic
key_command:
- printenv
- DEEPSEEK_API_KEY
default_model: deepseek-v4-pro
models:
- deepseek-v4-pro
model_api_filters: []
kimi-code:
protocols:
- openai-chat-completions
- anthropic
base_urls:
openai: https://api.kimi.com/coding/v1
anthropic: https://api.kimi.com/coding
key_command:
- printenv
- KIMI_API_KEY
anthropic_use_api_key: true
default_model: kimi-for-coding
models:
- kimi-for-coding
isolated_homes:
codex:
sandbox: {}
See config.demo.yaml for a fuller example.
Protocol Negotiation
protocols declares what a provider can speak.
clauderequiresanthropiccodexrequiresopenai-responsesshellexposes all three families
For single-protocol agents, launch fails unless the provider supports that protocol or --force protocol is used.
Shell mode does not require a specific protocol.
Models
Each provider declares its models under models. An entry is either a bare
string (the model id) or an object with id plus optional metadata
(name, context_window, max_output_tokens, reasoning, vision,
supports_attachments, pricing fields):
models:
- deepseek-v4-pro
- id: anthropic/claude-opus-4.6
name: Claude Opus 4.6
context_window: 200000
vision: true
Long-context variants with [1m]
Anthropic exposes 1M-context models by appending [1m] to the model name
(e.g. glm-5.2[1m]). agent-run reuses that suffix as a model-list
annotation so a single declaration works across both protocol families:
- A string-form model
"deepseek-v4-flash[1m]"decodes to the base iddeepseek-v4-flashwithcontext_window: 1000000. - Any model (string or object form) whose
context_window >= 1000000automatically exposes two selectable variants: the plainnameand the long-contextname[1m].
default_model and --model may reference either variant. The two are wired
differently per protocol:
| variant | Anthropic endpoint | OpenAI endpoint |
|---|---|---|
name |
name |
name |
name[1m] |
name[1m] (marker kept) |
name (marker stripped) + forced model_context_window |
The [1m] marker is only decoded from string-form declarations and only as
a trailing suffix. An object-form id is taken verbatim, so
{ id: "some-model[1m]" } is a plain model whose real name contains [1m] —
the marker is never stripped for it. If a provider's real model id literally
ends in [1m] and you do NOT want it treated as a long-context base, declare
it as an object.
models:
- "deepseek-v4-flash[1m]" # base `deepseek-v4-flash`, ctx 1M; variants: deepseek-v4-flash, deepseek-v4-flash[1m]
- id: glm-5.2
context_window: 2000000 # variants: glm-5.2, glm-5.2[1m] (forces 2M on openai)
- kimi-for-coding # plain only
default_model: deepseek-v4-flash[1m]
The {model} value in extra_env templates (e.g.
${context:agent}:${context:model}) resolves to the selected variant label
(name or name[1m]), not the bare wire id.
Usage
Open or initialize your config:
agent-run config
agent-run config --bootstrap-config
agent-run config only opens an existing config. Use --bootstrap-config to write the embedded sample config first when the file does not exist.
Launch Claude:
agent-run launch deepseek claude
agent-run launch kimi-code claude
Launch Codex:
agent-run launch ollama codex
agent-run launch openrouter --model openai/gpt-5.3-codex codex
agent-run launch sandbox codex
isolated_homes notes:
isolated_homes.codex.<name>allows launching Codex with an isolated runtime home even when no provider exists.- Entries are empty objects today. They do not accept
key,base_url,model, or custom paths. - When both a provider and an isolated home entry share the same name, agent-run combines them: isolated runtime home plus provider-derived runtime config.
- By default Codex uses
~/.codexand agent-run only injects/touches the provider configuration instead of overwritingconfig.toml. - Isolated runtime homes persist across launches. agent-run reuses the existing directory and only updates the generated config file.
Generate shell completion:
agent-run completion bash
agent-run completion zsh
When installed from Nix, bash and zsh completion files are installed automatically.
Manual shell setup is only needed when running the binary outside the Nix package:
source <(agent-run completion bash)
source <(agent-run completion zsh)
Model catalog:
agent-run models list openrouter
agent-run models list --refresh openrouter
agent-run models list --all
Forward extra args to the underlying agent:
agent-run launch deepseek claude resume
agent-run launch ollama codex resume --last
Both forms are supported:
agent-run launch provider agent arg1 arg2agent-run launch provider agent -- arg1 arg2
Completion notes:
- Bash and Zsh are supported.
- Provider completion is loaded from local
config.yaml. --modelcompletion refreshes remote model cache by default whenmodel_api_filtersis enabled.- Set
AGENT_RUN_DISABLE_MODEL_COMPLETION_REFRESH=1to make completion use local models plus existing cache only. - Default log level is
WARN. Completion runs stay silent unless you explicitly setRUST_LOG. - Trailing
agent_argsare forwarded but are not completed.
Model API filter notes:
- Omit
model_api_filtersto use the default catch-all rule. - Set
model_api_filters: []ormodel_api_filters: nullto disable remote model loading and cache interaction for that provider. - Filters are applied to normalized remote models before cache write.
Secret Handling
Each provider can use either:
keykey_command
key_command is preferred when you want to fetch secrets from an external source such as a password manager, shell environment, or local secret helper.
Extra Environment Variables
Each provider may declare an extra_env map that is injected into the launched agent process. The map is applied last and may override the env vars agent-run sets by default (e.g. ANTHROPIC_API_KEY, CODEX_HOME, OPENAI_API_KEY).
Values support inline template expansion:
${env:NAME}— reads env varNAMEfrom the launcher process; errors if unset.${context:FIELD}— reads a resolved launch field. Supported fields:provider,protocol,model,key,agent,base_url.
Example:
providers:
openrouter:
# ...other fields...
extra_env:
OPENROUTER_API_KEY: "${context:key}"
HTTPS_PROXY: "${env:CORP_PROXY}"
AGENT_RUN_TRACE: "${context:agent}:${context:model}"
Runtime Strategy
agent-run avoids modifying long-lived agent config for single-provider agents.
claudeuses temporary env plus a one-time onboarding state fixcodexuses~/.codexby default, or an isolatedCODEX_HOMEwhen configured; only touches/injects the provider block and writes a profile fileshellinjects provider settings into the child process environment
Development
This repository uses a minimal Rust + Nix setup.
Useful commands:
cargo check
cargo clippy -- -D warnings
cargo fmt