Providers and budget
Windows, limits and budget
A run is one window opening, or one awaywork run by hand:
windows:
- { start: "01:00", end: "07:00" } # the default
- { days: [sat, sun], start: "10:00", end: "18:00" }A scheduled run starts at start; no unit starts after end, and a running one stops there. days (sun to sat) are the days it opens on.
Pacing follows the clock, not the number of windows. A run may spend up to where an even spend over the long window stands at the next window's opening, or at the reset if that comes first, so more windows split the week finer rather than spend more. How the budget shares combine is in How it works; start with per_run: 5% for a week to see what a unit costs.
Limits cap what you'd have to review, per run and for every provider. None is set by default:
limits: { units: 8, units_per_repo: 3, unit_minutes: 90 }Units past a cap are skipped in queue order, the cap named in awaywork plan and the report. The keys are in the reference.
Details:
- Times are the machine's local time. In GitHub Actions they're the timezone you ran
schedule installin, a run may start up to an hour late in summer time, and a hosted runner stops it at about 6 hours. awaywork watchrereads windows every minute: no reinstall, unless you move the wake.windows: []means none: the daemon idles, and the Actions workflow runs only when dispatched. Windows shouldn't overlap.- A run started outside every window stops workers at
present_session_max(40%) instead ofsession_max, and skips providers whose quota it can't read. per_runcaps each run, andruns_until_resetcounts window openings.budgetapplies to every provider; a different budget takes a second config.- A
thenchain is one unit. Each of a plan's tasks is one, in its own repo, so a cap can cut a card's block; the next run carries on. - Only units whose first step got a provider count. Caps apply per
awaywork run, likefallback.units, which every step draws on. A unit named with--unitgoes past them. - A rule's own
unitscaps that rule; adeliver: planrule'splan_tasks(8) caps its plan's tasks.
Providers
A list, in routing order. A bare name uses the defaults; a map sets model (passed as is: claude --model, codex exec -m, agent --model) and its own settings:
providers:
- claude: { model: opus, settings: claude-worker.json }
- codex: { config: { model_reasoning_effort: high } }
- cursorWorkers skip your user-level agent config. What you do want them to have, on every unit including someone else's PR, goes here:
claude: { settings }: a settings file, relative to the config, passed as--settings. Auto-memory stays off, and workers run indontAskmode: a tool not pre-approved, by awaywork or yourpermissions.allow, is denied.codex: { config }: each key passed ascodex exec -c key=value.
Cursor takes only model (its auto when unset). Details:
- It needs
CURSOR_API_KEYin the env file, even on your machine: each run gets a freshHOMEin the clone,.away/agent-home, so none of your~/.cursorcomes along,agent loginincluded. Make a key in Cursor's dashboard. - awaywork writes that
HOME's permissions: any shell command, read and write, plus thetoolsof the rule's MCP servers. Never--force. - Its commits can carry
Co-authored-by: Cursor <cursoragent@cursor.com>, as Claude Code's can. - A Cursor subagent is one turn toward the cap and one
task:line inworker.log, whatever it does inside;limits.unit_minutesbounds it. - Its usage shows only in its dashboard, so it runs on the fallback caps and sits out runs started outside a window.
- On a GitHub-hosted runner the action installs it: Running Cursor.
- An older command provider of yours named
cursormust be renamed:providers[0].cursor: built in, so it takes no command; give your own provider another name.
Which login
Work and personal subscriptions on one machine? Pin each config's logins:
providers:
- claude: { config_dir: ~/.claude-work }
- codex: { config_dir: ~/.codex-work }
forge: { github: { user: octocat-acme } }config_dir is passed as CLAUDE_CONFIG_DIR or CODEX_HOME; log in there once (CLAUDE_CONFIG_DIR=~/.claude-work claude /login, CODEX_HOME=~/.codex-work codex login). forge.github.user picks a gh auth status account: its gh auth token --user becomes GH_TOKEN for everything awaywork does on GitHub, no gh auth switch. doctor names each login and fails with the fix:
✔ gh login: octocat-acme, pinned by forge.github.user
✔ claude login: <email>, max, in ~/.claude-work, pinned by providers[0].claude.config_dir
✖ codex login: ~/.codex-work doesn't exist; log in there with CODEX_HOME=~/.codex-work codex loginDetails:
~/.claudemeans Claude Code's default login, found only withCLAUDE_CONFIG_DIRunset, so that's what awaywork does.- Unpinned, a run uses the environment: for a scheduled job, the env file and each binary's default, not a shell function that switches logins.
- The environment wins:
CLAUDE_CODE_OAUTH_TOKENoverconfig_dir,GH_TOKENorGITHUB_TOKENoveruser(GH_ENTERPRISE_TOKENwith ahost). So one config also runs in Actions or a container.doctorwarns when that token is someone else's:gh login: octocat from GH_TOKEN, not forge.github.user octocat-acme: a token in the environment wins; make it octocat-acme's, or unset it. - Cursor's login is
CURSOR_API_KEY, everywhere. - Under
isolation: userorcontainer, Claude'sconfig_diris refused (set CLAUDE_CODE_OAUTH_TOKEN instead). Codex's works: theauth.jsonit lends comes from there. - A pin that can't be made stops
run,plan,approve,replay,sourcesandschedule installbefore they touch anything (forge.github.user: octocat-acme isn't logged in to gh; log in with gh auth login, as octocat-acme), andwatchstarts nothing new until it can; working pins still apply to runs already going.
Bring your own CLI
opencode, Amp and friends need no code in awaywork. Name a provider and give it a command; your CLI, your login:
providers:
- claude
- opencode:
command: opencode run --auto "$(cat "$AWAYWORK_PROMPT")"
probe: ./opencode-quota.sh # optional; without it, windows only
limits:
unit_minutes: 45It runs in the clone like any worker, the prompt in the file $AWAYWORK_PROMPT, output to worker.log; exit 0 is a finished unit. (--auto approves whatever your opencode config doesn't deny; check your version's --help.) There's no turn cap, so limits.unit_minutes is required. The keys are in the reference.
A probe prints the quota as JSON. awaywork paces against the long window; short only feeds the session stop:
{
"windows": [
{ "name": "5h", "span": "short", "used": 0.2, "resetsAt": 1793021152, "minutes": 300 },
{ "name": "7d", "span": "long", "used": 0.3, "resetsAt": 1793539552, "minutes": 10080 }
]
}Details:
secretsare withheld from other workers and listed in Actions;skills_dir(default.agents/skills) is where the unit's skills go.- No
modelkey (put it in the command) and nomcp: a rule it may run can't name MCP servers, so route those elsewhere withprovider. - Nothing installs it on a GitHub-hosted runner, so there
planroutes around it andschedule installleaves it out. fallback.turnsdoesn't apply; its 60 is for Claude, Codex and Cursor without a quota reading.- Without a
probe(or one with nolongwindow) it runs on thefallbackcaps and sits out every run outside the windows,run --unitincluded. With no live rate-limit events, it can't stop mid-unit at the ceiling. - The probe runs in the config's folder once per
planorrun, after each unit and indoctor, with 60 s and your whole environment.usedis 0 to 1,resetsAtunix seconds,minutesoptional (alongwindow without it is 7 days). - Whether the vendor allows unattended use is your call;
doctorflags itunattended: grey.