aiq pools several paid Claude Code and Codex subscriptions, plus an
Antigravity CLI (agy) login, and routes every claude, codex and agy
invocation to the account that is about to waste the most quota. Interactive
sessions, claude -p / codex exec / agy -p workers, and the workers those
agents spawn in turn all go through it. Nobody has to know which subscription
they are on.
$ claude # interactive: sticky per workspace
aiq: using claude/work — 5h 43%, weekly 8%
$ codex exec "review the diff" # worker: scored per call, silent on success
A small daemon keeps telemetry fresh and serves a status page on
http://127.0.0.1:7379/. When you come back to a directory, aiq resume
lists the sessions that ran there, shows what each turn asked and answered,
and resumes the one you pick. Works on macOS and Linux.
Accounts are overlay homes. Each Claude account is a CLAUDE_CONFIG_DIR
under ~/.local/share/aiq/claude/<name>/; each Codex account is a
CODEX_HOME under ~/.local/share/aiq/codex/<name>/. Every entry of the
real ~/.claude or ~/.codex is symlinked into the overlay (settings,
plugins, projects, sessions, history, sqlite state), so all accounts share
one configuration and --resume works on any of them. Only the credential
is private: the Keychain entry Claude Code keys by config dir on macOS
(.credentials.json on Linux), and auth.json for Codex. The overlay is
re-synced before every launch under a per-account lock; a file the CLI
created or rewrote in the overlay is moved back into the real home and
re-linked, a directory two sessions created independently is merged, and a
displaced copy is kept as a rotated .aiq-bak next to the real file. Two
things stay private per account: the credential, and .claude.json (Claude
Code rewrites it with a rename on every change, so sharing it would lose
writes). It is seeded from your ~/.claude.json when the account is created,
which means project trust and user-scoped MCP servers are per account.
The account matching the current ~/.claude login is registered as
native and launches without CLAUDE_CONFIG_DIR at all.
Shims. aiq shim install writes claude and codex scripts into
~/.local/share/aiq/shims/; put that directory first on PATH. Each script
runs aiq run <provider> -- args, which picks an account, syncs its
overlay, records a lease, and execs the real CLI. Nested calls carry
AIQ_DEPTH, AIQ_PARENT_ACCOUNT and a root lease id; max_depth stops
runaway recursion. Wrappers other tools put on PATH are handled by an
exec chain: a wrapper that bounces back into the shim with the same pid (or
as the direct child of a shell holding that pid) is recognised and the next
candidate on PATH is exec'd instead. Only entries after the shim directory
are candidates: a wrapper ahead of the shims has already run and delegated to
them, and running it again would apply its flags twice.
Two kinds of launch.
- Interactive (a terminal, no
-p/exec): sticky per workspace, i.e. a git checkout keeps its account until a binding window crossesswitch_pct. The shim execs the CLI, so the lease follows its pid. - Worker (
claude -p,codex exec, or a non-terminal stdout): scored per call, kept off accounts that carry an interactive session when an alternative exists, capped atmax_workers_per_account, and skipped on accounts whose weekly remaining is at or belowweekly_reserve_pct. The worker runs as a child with its output tee'd; if the provider rejects it with a usage-limit message early (little output, or within seconds) and withinretry_max_seconds, the account is benched and the same command reruns on the next account. The bench lasts until the reset of a window telemetry shows near its cap, otherwise one hour.
Telemetry. aiq polls every account itself, every interval_seconds
and right after any limit event. Claude usage comes from Anthropic's OAuth
usage endpoint with a token aiq mints once per account through the same
PKCE flow Claude Code uses (aiq account authorize); Claude Code's own
credential is never read. Codex usage comes from the official
codex app-server rate-limits RPC, run against the account's home, so Codex
keeps refreshing its own auth.json. Claude's status line adds live updates
for the account in use through a multiplexer that chains to whatever status
line you had. That status line may itself run aiq claude-statusline to
append aiq's quota segment: the multiplexer marks the chained command with
AIQ_STATUSLINE_CHAINED=1, and the inner call prints aiq's line instead of
chaining again. Shims never depend on the daemon: state lives in SQLite, so
with the daemon down they route on the last poll.
A QC loop or an orchestration overseer can run for days and burn a week of
quota. aiq long claude / aiq long codex starts such a session in a tmux
session named after the workspace and supervises it:
- Hooks, not a startup instruction. The session is launched with aiq's
SessionStart,UserPromptSubmitandStophooks (per launch, via--settingsfor Claude and-c hooks.…for Codex); ordinary sessions are untouched. When the account's tightest binding window hasdrain_pctor less remaining, the daemon flags the lease and the next hook tells the agent to keep working, write a handoff note at the next natural break and keep it current. The session moves at its next turn end if another account has headroom, or when the account runs out, whichever comes first. The successor must have more thandrain_pctleft; while none does, the agent spends what is left instead of moving to another nearly spent account, and once the current account is blocked, any positive headroom is enough. - Recovered quota cancels the move. A drain aiq started is called off
when the account has more than twice
drain_pctleft again: a reset credit spent by aiq, by another host, or in the CLI (/limit-reset), or a window that rolled over. aiq re-polls the account right before every move, so a reading taken before the credit was spent cannot move a refilled session. A drain asked for withaiq long drainalways goes through. - Supervised for as long as it runs. A long session's lease lives as
long as its process. A pane whose lease is gone anyway (aiq before this
fix dropped every lease 36 hours after it started) is re-adopted on the
next check: the daemon reads
AIQ_LEASEfrom the CLI's environment and rebuilds the lease under that id from the launch that started it. - Launches start above the floor.
aiq longranks accounts at or belowdrain_pctafter every other account and ignores the reset-credit "drain first" preference, since the session would be moved off such an account before its credit could redeem. With nothing above the floor it still starts on the best of what is left. - Idle sessions do not strand quota. An agent can stop on its own above
drain_pct(a quota floor, nothing it thinks it can afford), and then the account never runs out and nothing moves it. A long session that has been idle foridle_rotate_minutes, with nothing written to its transcript or its subagents' in that time, on an account withidle_rotate_pctor less left, moves to an account with more than that. - Resume, not summarize. The overlay shares transcripts, so the same
provider's successor gets
--resume <session-id>on the next account and keeps the whole conversation; the note is a supplement. A different provider (every Claude out, a Codex takes over, or the reverse) starts fresh from the note plus the working tree. The permission bypass carries over in the successor's spelling (--dangerously-skip-permissions↔--dangerously-bypass-approvals-and-sandbox), and so do a tier model (--model opus↔--model gpt-6.1-sol, see the tier table below) and an effort level (--effort high↔-c model_reasoning_effort=high). Every other flag is provider-specific and dropped. Fallback order islong.fallback, less any provider with no model of the session's tier. - Keep the session name. Claude and Codex handoffs carry the latest saved
display name, including a name changed with
/rename. Fresh Claude successors receive--name; fresh Codex successors start from a named, empty thread, so the handoff prompt does not become the thread's title. Ordinary same-provider resumes keep their existing name. - Takeover in place. The daemon
respawn-panes the same tmux pane with the successor, copies the project's trust entry between Claude accounts so no dialog blocks the restart, and nudges the successor with a first prompt. An idle session is moved without waiting for a wrap-up turn; a session on an account that is already blocked is moved at once; when no account has headroom the session waits and is moved when a window resets.
aiq long claude [--account <name>] [--model-tier N] [--effort N] [-- claude args...]
start or attach
aiq long auto [--model-tier 0] [--providers claude,codex]
start on whichever pool auto picks, or attach
aiq long auto resume [--account <name>] [<id>]
pick a session here and resume it as a long one
aiq long resume [--account <name>] [<id>]
the same, as it ran: no forced bypass or tier
aiq long list leases, pane, drain state, idle/busy
aiq long drain . move this workspace's session now
aiq long attach re-attach
aiq long stop . end it
Choose the initial account with aiq long codex --account acme -- --yolo
(or --account=acme). This also works with Claude and named launchers.
Put --account before CLI arguments; -- ends aiq's options. The daemon
can still move the session to another account later. If a long session already
exists for the workspace, the command attaches to it instead of changing its account.
aiq long auto chooses the first account the way aiq run auto does:
across both pools, at the requested tier (default 1), with YOLO. Its
takeover order is the chosen provider first, so a successor resumes the same
transcript while that pool has headroom, then the other provider. It takes no
prompt, because a takeover replays the launch arguments; type the task in the
session.
aiq long auto resume opens the aiq resume browser for this directory;
r on a session reopens it in a long session instead of the current
terminal (or give the id: aiq long auto resume 3f2a). The transcript fixes
the provider; the account is routed unless --account names one (a bare
name, --account claude3, or an id, --account claude/claude3, which must
belong to the session's provider), the model tier and effort apply as for
auto, and the bypass is on. As with aiq resume, a session that ran under a
launcher resumes through it (b or --bare skips it), and then gets the
bypass only if its earlier launch passed it. A session already running in
aiq long is attached. The pane starts in the current directory, so the CLI
finds the transcript.
aiq run auto resume is the same command for this terminal: the same
browser, flags and defaults (bypass on, tier 1, --account on the session's
provider), but the session reopens here instead of in tmux. aiq long resume
is the other way round, the long twin of aiq resume: the session reopens in
tmux as it ran, with the bypass only if it had one and the CLI's own model
unless --model-tier or --effort say otherwise. Each start has both forms:
| this terminal | tmux, supervised | |
|---|---|---|
| new, on any pool | aiq run auto |
aiq long auto |
| resume, as auto starts | aiq run auto resume |
aiq long auto resume |
| resume, as it ran | aiq resume |
aiq long resume |
Handoff notes live in ~/.local/share/aiq/handoff/, in a directory only you
can read. A note quotes prompts and tool output, and the next session reads it,
so its contents can reach another account or vendor when a session moves.
- aiq replaces values matching known secret formats with
[redacted ...]: invite strings, common API keys and tokens, JWTs, private keys, credentials in URLs, andpassword=-style values. It does this when it rebuilds a note, and when it offers or archives a note the session wrote. - It matches formats only. A password in plain prose or a token in an unknown
format stays in the note. Session transcripts, which a rebuilt note names, and
worker prompts in
state.dbare not redacted. - Archived notes are deleted 30 days after they were archived. A workspace's current note stays until its next takeover.
- A note is a record of earlier work, not an instruction from you. The takeover
prompt tells the next session to leave anything beyond the task, and to list what it
left in its next message and in any note
8000
it writes. This wording is advice to the
model, and its effect is untested. A permission you gave during a session may not
carry over to a fresh successor, so you may need to repeat it.
aiq convoshows what a session said it left. A session that cannot tell what the task was is told to say so instead of guessing, and nothing alerts you if it then waits. Where hard links do not work, aiq writes no rebuilt note and says so in the daemon log. A rebuilt note quotes what it copies, may name earlier archived notes, and gives its transcript's path only to say where it came from. The transcript is not redacted, and the note does not ask the successor to read it. In a partial clone it leaves out the log, stashes and worktrees and says so, and the successor can run git for itself. aiq handoff redactlists what would change in the notes already on disk, by kind and count.aiq handoff redact --applyrewrites them, keeping no backup.
Fresh CLIs without stopping work. Once a day the daemon checks whether
an npm-installed Codex or Claude Code has a newer release and, if so,
installs that exact version with the installation's own npm (npm install -g --prefix <its prefix> <package>@<version>), then runs <cli> --version; a
release that will not start is replaced by the version that was there.
Running sessions keep the binary they started with; the next start, by hand
or by a takeover, gets the new one. While a reinstall runs, a marker in
~/.local/share/aiq/updating/ holds every launch (up to five minutes) so
nothing starts a half-installed CLI. Claude Code from the native installer
updates itself and is left alone. [updates] enabled = false turns this off.
agy, Google's Antigravity CLI, joins the pool as a third provider with one
difference: it runs on your real login. The CLI reads no variable for another
home (only HOME moves ~/.gemini), and it keeps its token in the OS
keyring, so in this release aiq registers the one ~/.gemini login as a
native account and does not build overlay homes for it:
aiq account add agy main # registers ~/.gemini; opens the sign-in if it has none
aiq run auto -p "fix the test" # may now land on agy/main when it has the most to spare
aiq long agy # supervised like the others; moves to Claude or Codex when dry
Quota polls use the read-only usage command. With agy 1.1.11 or newer,
agy -p "/usage" --output-format json returns the quota buckets
(gemini-5h, gemini-weekly, 3p-* for third-party models, and the separate
Argon weekly bucket) without starting a model turn or spending tokens.
Polling needs no custom statusline; agy 1.4.7 stopped running those in print
mode. Failed polls retain the last good reading.
aiq account add agy also points ~/.gemini/antigravity-cli/settings.json
at aiq agy-statusline for live interactive quota, identity, and plan
telemetry. A command of your own keeps rendering behind it; with none,
aiq's line is stacked under the built-in one. aiq statusline uninstall agy
removes this interactive integration; quota polls continue to work.
Long sessions use global hooks. The CLI takes no per-launch hook flag, so
aiq long agy installs an aiq-long entry in ~/.gemini/config/hooks.json
(PreInvocation and Stop) that does nothing unless the session carries an aiq
long lease, and adds the workspace to the CLI's trusted list so an unattended
pane is not stopped at the trust prompt. A same-provider takeover resumes with
--conversation <id>.
Not yet: several agy accounts (the keyring, not the home, holds the login),
and a live "open right now" marker for aiq resume.
After a day of sessions in one directory, several of them run by aiq long,
aiq resume shows what each agent did there before you start the next one.
aiq resume browse this directory's sessions
aiq resume --all include workers (claude -p, codex exec)
aiq resume <id> resume one directly (a unique prefix is enough)
aiq resume --print [<id>] the same lists as plain text, for scripts and agents
aiq resume --bare <id> resume without the launcher the session ran under
The first screen lists every Claude and Codex session whose working directory
is the current one, newest first, with the model, turn and tool counts, and
the session's name or first prompt. Enter opens a session's turns: each prompt
with the agent's final reply for that turn. Enter on a turn shows it in full,
and ←/→ move between turns. r resumes the selected session on an account
the pool picks, with claude --resume <id> or codex resume <id>.
- A session is resumed through the launcher it ran under, so a
safe-claudesession goes back into the same nono sandbox. aiq records every launch (account, launcher, directory, arguments) instate.dbbefore the CLI starts. For a new Claude session it chooses the id itself (--session-id), so the match is exact. Codex cannot be given an id, so a new Codex thread is matched to the latest aiq launch in the same directory before it; the list marks such a guess with?([via safe-codex?]). Once a session has run under a launcher, it keeps resuming there:bin the browser, or--bare, resumes it without one, for that time only.--launcher <name>picks another launcher. Sessions from before this record existed, and anything started without aiq, resume bare. - A bare resume adds the permission bypass if the transcript shows the
session ran with it. Through a launcher, aiq adds it only if you passed it
to that launcher yourself: the safe-* wrappers add it on their own, and
Codex refuses the flag twice. Anything after
--is added to the CLI's arguments. - A session still running in
aiq longis attached, not started a second time. For a Claude session that is open in another terminal (● in the list),rasks once more before it opens a second copy. - Subagent transcripts are not listed. Their work appears in the parent session's turns as tool calls.
A long session buries the conversation under tool calls. aiq convo prints
only what was typed into the session and what the agent answered, in the
order it happened: each prompt, anything typed while the agent worked, and
the agent's final reply to each turn, headed with how long the turn took
and how many tools it used. The agent's first message after something you
typed is its reply to it and shows in full (one message after several of
your messages answers them all); a reply said on the way is headed with
the time it came, while one that is an answer shows under that answer's
own heading. What else it said on the way (Codex commentary, Claude's text
before a tool call) shows as one dim line each, and a line that turns out
to be an answer shows as that answer instead. Answers it gave before going
on (after a background task or another agent's message) show as earlier.
Prompts that aiq, another agent or a notifier typed in show as one dim
line (an Agent Orchestra inbox notice as orchestra inbox · <time>); what
the agent did with one shows by the same rules as your own turns, since
its answers report work you want to know about. Agent-team idle
notifications are left out.
aiq convo the agent in this tmux pane, else the one live session here
aiq convo --pane %5 the agent in tmux pane %5
aiq convo <id> any session of this directory (a unique prefix is enough)
aiq convo --last 3 only the last three prompts and what followed
aiq convo --follow keep printing as the conversation goes on
aiq convo --theme light colours for a light background (auto|light|dark)
aiq convo --hide-markup style the Markdown without its **, backticks and marks
On a terminal the conversation opens at its end in $PAGER (else
less -R +G), with the agent's Markdown styled: bold as bold, code in its
own colour. The Markdown stays in the text (**, backticks, #, list and
quote marks, code fences, pipe tables), so a copy from the terminal pastes
into Slack or a document as Markdown. --hide-markup draws it the way
Claude Code does instead: the marks hidden, pipe tables as boxes that fit
the terminal's width. What you typed sits on a grey band across the terminal, so it stands
out from the answers. Anywhere else it prints plain text, Markdown as written. --follow never redraws:
new prompts and replies are appended, a status line at the bottom says how
long the agent has been working, and each prompt carries a mark that tmux
copy mode's previous-prompt / next-prompt jump to. When the pane moves to
a new transcript (/clear, Codex /new, a takeover by another provider),
--follow prints a divider and goes on with the new one. It is read-only:
what you type into it is swallowed, not echoed, and does not reach the
session or the shell afterwards; Ctrl-C quits.
The colours suit a dark background unless the terminal says it is light.
--theme auto (the default) asks the terminal for its background colour
(OSC 11), else asks tmux which theme the terminals showing the pane
reported (#{client_theme}, tmux 3.7; $AIQ_TMUX_CLIENT picks one client
when they disagree), else reads $COLORFGBG, else takes dark; --theme light|dark, or
AIQ_THEME, settles it (the flag wins). tmux answers that query in a pane
when it knows the colour (from the outer terminal, or a window-style
background) but not in a popup; there the client theme usually settles it,
and otherwise set-environment -g AIQ_THEME light or --theme light in the
binding does.
A pane is matched through the agent's own records: Claude Code's
sessions/<pid>.json, else the pane's aiq long lease. Either counts only
while its process is alive and runs in that pane, since tmux reuses pane ids
after a restart. A claude -p worker the agent starts is never taken for the
agent. Outside the agent's pane, aiq convo looks for the session running in
this directory or one holding it: an open Claude session or a session aiq
launched. A short Codex or Antigravity session reports no transcript, so its
lease takes the newest interactive session of that CLI in its directory
written since it started. A Codex session started without aiq is found by id
only.
--follow does not support Antigravity sessions yet.
Two tmux bindings make it a keystroke from any agent pane. aiq never installs
them; aiq convo --help prints them with the path of your aiq binary. Use the
absolute path: tmux runs them with its own PATH.
# prefix a: this pane's conversation in a popup; q closes it, and an error stays up until Escape
bind-key a run-shell "tmux display-popup -c '#{client_name}' -w 90% -h 90% -EE '/opt/homebrew/bin/aiq convo --pane #{pane_id}' || true"
# prefix A: the same, live, in a split beside the agent (focus stays on the agent)
bind-key A run-shell "tmux split-window -h -d -l 40% -t '#{pane_id}' '/opt/homebrew/bin/aiq convo --follow --pane #{pane_id}'"Both go through run-shell because it is where tmux expands #{pane_id}:
display-popup and split-window pass their command on unexpanded, and
inside a popup $TMUX_PANE is empty.
aiq run auto -p "do this thing"
aiq run auto --model-tier 0 --effort 6 -p "analyze this design"
aiq run auto --model-tier 2 --effort 2 -p "fix the failing test"
aiq run auto --model-tier 1 # interactive session
aiq run auto --providers claude,codex # only these poolsauto ranks eligible accounts of every provider together (Claude, Codex,
Antigravity, Copilot) using the quota score, worker reserve and concurrency
limits, after the cost order (plan quota, then Claude extra usage, then the
last-resort providers; see Cost order). It skips providers whose CLI is missing and providers with no model at
the requested tier. --providers claude,codex limits one launch to those
pools; auto.providers in config.toml sets the default (empty means every
provider), and the flag overrides it. A worker's retry and a long session's
takeover stay inside the same set.
Selection uses stored telemetry, as ordinary routing does; unknown or stale
telemetry cannot guarantee remaining quota. An early worker quota rejection
can retry on another account, including the other provider, under the existing
worker retry policy. Interactive sessions choose once at launch.
-p translates to Claude's print mode or Codex's exec command. The prompt is
preserved as one literal argument. Without -p, auto opens an interactive
session; add -- "initial prompt" to start it with a prompt. Auto always
enables permission bypass: --dangerously-skip-permissions for Claude or
--yolo for Codex, including workers and cross-provider retries. An explicit
--yolo is still accepted but is redundant. Native provider options require
aiq run claude or aiq run codex; auto does not translate arbitrary flags,
resume sessions (use aiq run auto resume, aiq resume, or aiq long auto resume), named launchers,
or forced accounts. --wait and --mode remain available, with worker mode
requiring -p. For a supervised session that moves between accounts, use
aiq long auto (see Long-running sessions).
| Tier | Claude | Codex | Antigravity | Copilot |
|---|---|---|---|---|
| 0 | Fable 5.1 (fable) |
Astra (gpt-6-astra) |
— | Astra (gpt-6-astra) |
| 1 (default for auto) | Opus 5.5 (opus) |
Sol 6.1 (gpt-6.1-sol) |
Gemini 4 Argon (gemini-4-argon-high) |
Sol 5.6 (gpt-5.6-sol) |
| 2 | Sonnet 5.5 (sonnet) |
Terra (gpt-5.6-terra) |
Claude Sonnet 5.5 (claude-sonnet-5-5-high) |
Terra (gpt-5.6-terra) |
| 3 | Haiku 5.5 (haiku) |
Luna 6 (gpt-6-luna) |
Gemini 3.8 Flash (gemini-3.8-flash-high) |
Luna (gpt-5.6-luna) |
A tier is a class of model, the same on every provider, not a provider's own
ranking of its models. Claude's aliases follow the newest model of each
family; the other names are versions, current as of 2026-10-10 (Antigravity's
Argon as of 2026-11-01), kept in internal/tier/tier.go. A model the table used
to name (gpt-5.6-sol, claude-opus-5-5-high) keeps its tier, and a long
session started on one is restarted on the tier's current model at its next
same-provider takeover. Antigravity's Claude Opus stays available with an
explicit --model, but a long session started that way moves to Argon at that
takeover.
Antigravity has no frontier model, so it has no tier 0: aiq run auto --model-tier 0 skips it, aiq run agy --model-tier 0 is an error, and a long
session on a tier-0 model never moves to it. Copilot serves the Codex models,
but no newer than the 5.6 generation below Astra; which of them an account may
pick depends on its Copilot plan (on a Copilot Free login every named model
was rejected as not available).
The requested tier stays fixed during retries; aiq never silently downgrades it. Model-scoped quota windows bind only the selected model, so an exhausted Fable cap does not disqualify an otherwise eligible Opus account. On Antigravity the Gemini models share the account's main windows, including the Argon weekly one; Claude models there draw on separate "3p" windows, which bind only tier 2.
| Effort | Claude | Codex | Antigravity | Copilot |
|---|---|---|---|---|
| 1 | low | low | low | low |
| 2 | medium | medium | medium | medium |
| 3 | high | high | high | high |
| 4 | xhigh | xhigh | high | xhigh |
| 5 | max | max | high | max |
| 6 | ultracode | ultra | high | max |
Effort defaults to high (3) for aiq run auto and for a model a takeover
translates; with an explicit provider, omitting it preserves the CLI default. Claude's sixth setting
means xhigh plus dynamic workflows, rather than a sixth model reasoning level;
it requires Claude Code 2.1.203 or later. See the
Claude CLI reference.
Model and client support still apply to each setting. Antigravity spells the
level as the model name's suffix (gemini-3.8-flash-low), so a tier and an
effort become one --model; the Pro models have no medium and take high.
Both shared options also work with an explicit provider, before --:
aiq run claude --model-tier 0 --effort 6 -- -p "review this design"
aiq run codex --model-tier 2 --effort 3 -- exec "fix the failing test"Explicit providers keep their configured model when no tier is supplied.
Do not combine a tier with --model-scope or a native model override; the tier
sets both the model and its quota scope. Pass native named effort values after
--, for example aiq run claude -- --effort high -p "review this".
Habits that make the pool disappear from day-to-day work.
aiqWith no arguments on a terminal, aiq opens a start menu instead of the command list:
aiq 0.7.13 · ~/personal/aiq
› Session New Resume
Agent Any Claude Codex boxed
Length Short Long
More model tier, effort, account, permissions
Start a new conversation in this directory.
$ aiq run auto
Three rows cover most starts: a new session or one of this directory's to
resume, any agent or a named one (your launchers included), and short (this
terminal) or long (supervised in tmux). m opens the rest: model tier,
effort, a specific account, and the permission bypass. Rows that do not
apply to the current choice dim and say why, and the line under the rows
explains the focused choice, including which account a routed start would
take right now.
The bottom line is the ordinary aiq command the choices add up to. Enter
prints it and runs it, so the menu doubles as a way to learn the commands.
The menu remembers the last start, so Enter alone repeats it. When a long
session already runs in the workspace, a attaches to it. ? prints the
full command list, which aiq help also prints; piped or scripted, bare
aiq prints it too.
aiq run autoWhen the question is "can I finish this session" rather than "which model
do I want", let aiq decide. It picks the provider and account with the most
quota about to expire, opens an interactive session on tier 1 (Opus or Sol)
with permission bypass on, and stays there for the session. Pin the tier for
the session with --model-tier:
aiq run aut
8BD0
o --model-tier 0 # the strongest model that has quota
aiq run auto --model-tier 2 --effort 2 # cheap and quickAdd -- "initial prompt" to open the session with a first message already
sent.
aiq long autoFor work that will outlast one account's quota (an overnight refactor, a QC
loop, an overseer that babysits PRs), start it as a long session instead. aiq
picks the account as aiq run auto does and opens the CLI in a tmux session
named after the repository. Type the task there, then detach (tmux prefix,
then d; Ctrl-b d by default). The session keeps running after you close
the terminal or drop an SSH connection. When the account runs low, the daemon
has the agent write a handoff note and restarts it on another account in the
same pane.
aiq long list # which accounts your long sessions are on, busy or idle
aiq long attach # from anywhere in the repository: back into its session
aiq long drain . # move it to another account now
aiq long stop . # end itaiq long auto --model-tier 0 keeps the strongest model, on whichever
provider it moves to; providers with no tier-0 model are passed over. Run aiq long auto again in the same repository and it
attaches to the running session rather than starting a second one.
A session started with aiq run auto sometimes turns into hours of work.
Exit it (/exit), then:
aiq long auto resumeThe browser from aiq resume opens on this directory's sessions. Pick the
one you left and press r: the same conversation reopens in a supervised
tmux session, on an account aiq picks. It continues on other accounts, as in
the previous recipe. If you already know the id, skip the browser:
aiq long auto resume 3f2a.
A one-shot question should cost one line, with no quoting. This shell
function turns everything after aip into a single aiq run auto -p prompt,
and a line widget quotes the rest of the line at Enter so apostrophes,
question marks, globs and ! history references pass through untouched:
# ~/.zshrc
aip() {
aiq run auto -p "$*"
}
# Shorthands whose arguments get passed verbatim when typed at the prompt.
verbatim_cmds=(aip)
_verbatim_accept_line() {
local name rest
for name in $verbatim_cmds; do
[[ $BUFFER == "$name "* ]] || continue
rest=${BUFFER#"$name "}
# Skip lines already quoted, e.g. recalled from history.
[[ $rest == "${(qq)${(Q)rest}}" ]] || BUFFER="$name ${(qq)rest}"
break
done
zle .accept-line
}
zle -N accept-line _verbatim_accept_lineThen:
$ aip how do I run a command on a remote machine over ssh without a tty?
$ aip what's the git command to drop the last commit but keep its changes?
$ aip why does !ls fail on this box?
Each call is a worker: scored per call, kept off accounts that carry an
interactive session when another is available, and retried on the next
account if the provider rejects it with a usage limit. The widget rewrites
the buffer before it is accepted, so the quoting is visible on the line as
it runs, history keeps the quoted form, and recalling a line does not quote
it twice. Add other functions to
verbatim_cmds to give them the same treatment.
For every binding window w of an account (the 5h and weekly windows,
plus a model-scoped weekly cap such as "Fable" when that model is your
default):
remaining_w = 100 − used_w
hours_w = time until w resets, floored at min_hours
perish_w = min(remaining_w, remaining_weekly) / hours_w
credits = reset_credits × 100 / hours_to_credit_expiry × weekly_weight
score = (Σ perish_w × weight_w + credits) ÷ (1 + worker leases on the account)
perish_w is the number of percentage points that vanish per hour if the
account sits idle. weight_w is 1 for the session window and
weekly_weight (default 5) for weekly windows, because one weekly point is
several session points' worth of tokens: an account whose week rolls over
in ten hours with 40% unspent gets the work all afternoon, not only in the
last hour. Highest score normally wins. Exhausted accounts (a binding window
at 100%, or a limit hit), disabled accounts, accounts without a login, and
Codex accounts whose latest poll definitively rejected their authentication
are filtered first; workers additionally respect the weekly reserve and the
per-account cap. Telemetry older than stale_after_seconds falls back to a
neutral prior. aiq status --explain prints the ranking with every term.
Reset credits expire, and each one is a full weekly window. Codex earns
them; Anthropic grants Claude accounts limit resets (Claude Code's
/limit-reset, clearing the session and weekly windows). An
account holding them scores as if that quota perished at the soonest
credit's expiry (a week out when the expiry is unknown). Once its weekly
allowance crosses switch_pct, it takes priority over a fresh account and
stays sticky until the remaining allowance is gone; this makes the credit
redeemable instead of leaving it stranded behind the final few percent.
Workers may drain such an account past the weekly reserve, and take it even
while it carries an interactive session. When it then hits its cap, the poll
that sees the block redeems a credit on its own — always for a weekly
block, and for a shorter window only when the credit would expire first or
within a day. Set auto_reset_credits = false to leave spending to
aiq reset codex/<name>.
Ranking goes by cost class before score:
- Plan quota. Every account with subscription quota left, on any provider, Antigravity included (it serves Claude Opus 5.5).
- Extra usage. A Claude account whose plan is spent, or nearly spent,
but which can run on prepaid usage credits. Where one exists, plan quota
that is nearly spent (at or below
drain_pctfor a long session, pastswitch_pctfor an interactive pick) ranks after it: a few points of plan buy a short stretch and another move, the credits run on. A long session on such an account is not drained when its plan runs low. Claude Code switches to them on its own once the plan's windows run out ("Extra usage is now covering your requests"). aiq polls the balance and the monthly spend limit, and uses such an account only whenselection.extra_usage = true, extra usage is on, the prepaid balance is above zero, auto-reload is off (so no card is charged), and the monthly limit is not reached. Workers may spend it past the weekly reserve. A worker rejected with "You're out of extra usage" or "You've hit your monthly spend limit" benches the account for an hour. - Last-resort providers,
selection.last_resortin order (default Copilot), forautoand long-session takeovers once neither of the above is left. A provider still has to be allowed (auto.providers,--providers,long.fallback), and a long session still never moves to a provider with no model of its tier.
aiq reads the balance and the auto-reload setting together; a reading
older than 15 minutes, or an auto-reload setting it cannot read as off,
stops the spending. The switch only governs where aiq routes: a session
already running on an account with extra usage turned on spills into it
by itself when the plan runs out, whatever extra_usage says.
A sticky interactive session on extra usage moves back to plan quota at
its next launch, and keeps its account (one balance, not each in turn)
while no plan quota exists. A long session on extra usage moves once an
account has plan quota above drain_pct; a nearly spent one would only
bounce it back. A long session on a last-resort provider moves back to
such an account after idle_rotate_minutes of quiet. aiq status shows
the balance and marks accounts running on extra usage extra and
last-resort ones last.
For a first install on macOS or Ubuntu, install Git and Go (the version in
go.mod or newer), then clone and run:
git clone https://github.com/orlenko/aiq.git
cd aiq
./install-or-update.shFor updates, run the same script from your existing checkout, from any directory:
~/code/aiq/install-or-update.sh # use your checkout's actual pathThe script fast-forwards the checkout's tracking branch, builds and atomically
installs ~/.local/bin/aiq, refreshes provider and launcher shims, and installs
or restarts the current user's daemon (launchd on macOS, systemd on Ubuntu).
Run it as your regular user, without sudo. It refuses to pull over local
changes. Use --no-pull to install the current working tree, or --no-daemon
to install only the CLI and shims. On Ubuntu, daemon installation needs a
running systemd user manager. Add the printed shim directory and ~/.local/bin
to your shell's PATH; the script does not edit shell startup files.
Alternatively, install manually:
go install github.com/orlenko/aiq/cmd/aiq@latest # or: go build -o ~/.local/bin/aiq ./cmd/aiq
aiq shim install # then add the printed PATH line to your shell rc
aiq daemon install # launchd (macOS) or systemd --user (Linux)After either installation method, add your first accounts:
aiq account add claude work # Claude Code login + quota poll grant (two browser steps)
aiq account add claude home
aiq account add codex work # codex login (one browser step)
aiq doctorOn a headless machine (an SSH session, or Linux with no DISPLAY or
WAYLAND_DISPLAY), the Codex login uses codex login --device-auth: open
the printed link on any device and enter the code. The browser flow needs a
callback to localhost:1455 on the machine running aiq, which a browser
elsewhere cannot reach. Pass --device-auth or --browser to aiq account add or aiq account login to override the detection, which reads the
environment. A tmux pane keeps what it started with, so a pane created over SSH
and later used at the machine still counts as headless, and a pane created at the
machine and later attached over SSH does not; use the flags in those panes. ChatGPT may require
device code authorization to be enabled in its security settings first. If a
login fails during aiq account add, the account is already saved, so retry
with aiq account login codex/<name>.
A definitive Codex authentication rejection from the periodic quota poll
automatically removes that account from routing. aiq account login codex/<name>
verifies the replacement login with a fresh poll and restores it when
successful. For a manual pause, use aiq account disable codex/<name> and later
aiq account enable codex/<name>.
Sign in as the intended account at each browser step: sign out of claude.ai or chatgpt.com first, or use a private window, so the browser does not hand back the account it is already signed in as. aiq warns when two accounts turn out to be the same login.
If you run aiquota on the same machine,
aiq account import adopts its accounts instead: Codex auth.json files and
Claude poll grants move into the aiq homes and aiquota is re-pointed at them,
labels and grid order come along, and the Claude account matching the
current ~/.claude login is registered as native. aiquota is not needed
for anything else.
aiq start menu (on a terminal)
aiq claude [args...] same as the shim: route and launch
aiq codex [args...]
aiq run <provider> [--account <name>] [--next] [--mode interactive|worker] -- [args...]
aiq status [--json] [--refresh] [--explain] [--events N]
aiq top live console view
aiq account list
aiq account add <provider> <name> [--device-auth|--browser]
new overlay home + login (+ poll grant for Claude)
aiq account login <provider>/<name> [--device-auth|--browser]
redo the CLI login
aiq account authorize claude/<name> redo the quota poll grant
aiq account poll [<id>...] poll now
aiq account label <id> <text> label shown on the status page
aiq account order [<id>...] display order within each provider column
aiq account rename <id> <new-name> [--keep-home]
rename everywhere; moves the home and its login
aiq account enable|disable|remove <provider>/<name> [--purge]
disable temporarily removes it from routing
aiq account use <provider>/<name> pin this workspace
aiq account next <provider> rotate this workspace
aiq account import [id[=name]...] adopt aiquota accounts, if you have aiquota
aiq mark <provider>/<name> exhausted [--until 14:42|+2h|RFC3339]
aiq mark <provider>/<name> ready
aiq reset <provider>/<name> consume a Codex or Claude reset credit
aiq resume [--all] [--print] [--launcher <name> | --bare] [<id>] [-- args]
this directory's sessions: browse turns, resume one
aiq convo [--pane <id>] [--follow] [--last N] [--theme auto|light|dark] [--hide-markup] [<id>]
only the prompts and replies of a running session
aiq shim install|uninstall|path
aiq statusline install|uninstall|status Claude status-line multiplexer
aiq daemon run|install|uninstall|status
aiq doctor [-v] [--device-auth|--browser]
failed checks and how to fix them
aiq doctor prints only the checks that failed (-v prints every one), then
groups them into an ordered list of fixes, one per account, so a Claude
account that needs a new login and a new poll grant is one step with both
sign-ins. On a terminal it offers to go through the list: for each step it
says which account to sign in as and what to paste, runs the fix when you
press Enter (s skips, q stops), then checks everything again.
aiq account rename claude/claude4 fourth carries the account's usage,
leases, workspace pins, launch history, label and display order to the new
name. An overlay home at the default path moves with it, along with the
macOS keychain login Claude Code keeps for that path and any aiquota
credential path inside it, so no new login is needed. --keep-home renames
the account and leaves the home where it is; a native home never moves. A
running session holds the old name in its environment, so the rename waits
until no session is using the account.
Launch flags for orchestrators (also as AIQ_MODEL_SCOPE, AIQ_WAIT,
AIQ_MODE in the environment of a child):
--model-scope <name>binds the named model-scoped window for this launch (fableskips accounts whose Fable weekly cap is dry;opusornoneignores it). Default comes fromproviders.<p>.model_scope.--wait <duration>(600,600s,10m) makes a worker poll for a free slot when every eligible account is atmax_workers_per_account, instead of refusing; it still refuses at once when the pool is exhausted, since waiting cannot fix that. A value that does not parse isbad-flags.[worker] wait_for_slot_secondssets a default.
Escape hatches: AIQ_BYPASS=1 claude ... runs the real CLI on your real
home with no routing; claude auth ..., claude setup-token, claude update, codex login and codex logout always pass through.
Two things are stable for other tools: exit codes and the status JSON.
Exit codes. A child's own exit code passes through unchanged. aiq's own
refusals exit with a distinct code and print one machine-readable line,
aiq: refused: <token>, before the human-readable message:
| code | tokens | meaning |
|---|---|---|
| 75 | pool-exhausted, at-worker-cap, wait-timeout |
nothing available right now; try later or elsewhere |
| 78 | no-accounts, no-binary, bad-flags, max-depth, account-not-found |
nothing to route to; a human has to change something |
bad-flags covers aiq's own flags only (--mode, --wait). An unknown
flag before -- is not aiq's: it is forwarded to the CLI, which fails in its
own way with its own exit code.
Status JSON. aiq status --json and GET http://127.0.0.1:7379/api/state
(under view) return the same document, except that since v0.7.15 the API leaves
out events and each lease's args, and GET /api/events is gone. Launch
arguments and the audit log can hold prompts, and any local user can reach this
port. To read them, use aiq status --json, which runs as you and reads your
files. It shows the last 15 events, and --events N shows more; the daemon trims
the log to about 2000 rows. args is a string: the arguments joined by spaces and
shortened (80 characters each, about 300 in all), or for a long session a
JSON-encoded array in full.
schema_version is 1 from the command and 2 from the API. Additions do not bump
it; a field that changes meaning or goes away does. Stable fields:
schema_version, generated_at, hostname
worker_capacity{<provider>: n} free worker slots per provider, summed over eligible accounts
worker_capacity_by_scope{<provider>: {none: n, <scope>: n, …}}
same, with that model-scoped window binding ("none" = ignored)
accounts[]:
id, provider, name, enabled, native, identity, plan
has_credential, eligible, ineligible, exhausted, reason, cooldown_until
score, rank, terms[] worker ranking (see Scoring)
worker_slots free slots on this account (0 when ineligible)
workers, interactive live leases by kind
windows[]: key, label, kind, scope, binding, used_pct, resets_at, window_seconds, source, observed_at
leases[]: pid, mode, cwd, depth, started_at, drain, pane, workspace
rankings{"<provider>/<mode>": [{id, score, eligible, reason, terms}]}
events[]: ts, provider, account, type, detail aiq status --json only
Size a batch fleet from worker_capacity_by_scope[provider][scope]; pick a
provider per model tier from the same map.
Example: a review fleet spawning Codex seats. An orchestrator that runs
inside a routed interactive claude session (depth 0) launches one process
per seat, which spawns codex by bare name with the prompt on stdin and the
result in a file:
env: AIQ_MODEL_SCOPE=<tier scope> AIQ_WAIT=600 (inherited by the spawn)
argv: codex exec --ephemeral -C <repo> -s read-only -m <model> \
-c model_reasoning_effort=<effort> --color never -o <result.md> -
stdio: stdin ← prompt (written once, then closed); stdout ignored; stderr tail kept
What aiq does with it: the shim classifies it as a worker (exec first,
stdout a pipe), routes it to an eligible account under that scope, forwards
stdin unchanged, tees stderr and stdout for the usage-limit patterns, and
reruns the same command on the next account if the provider rejected it
early. Six seats launched at once on a one-account pool run three at a
time with AIQ_WAIT set; without it, seats four to six exit 75
at-worker-cap immediately. A timeout that signals the process group reaches
both aiq and its child; aiq also forwards SIGTERM/SIGHUP to the child.
Depth is 1, well under max_depth.
| What | Where |
|---|---|
Overlay homes (credential, poll grant, .claude.json private; everything else symlinked) |
~/.local/share/aiq/claude/<name>/, ~/.local/share/aiq/codex/<name>/ |
Antigravity: the native account is ~/.gemini; quota polls run in |
~/.local/share/aiq/agy-probe/ |
| Shims | ~/.local/share/aiq/shims/ |
| State (accounts, windows, leases, affinity, launches, events; no credentials) | ~/.local/share/aiq/state.db |
| Daemon log | ~/.local/share/aiq/log/daemon.log |
| Config | ~/.config/aiq/config.toml |
Session transcripts aiq resume reads (never writes) |
~/.claude/projects/, ~/.codex/sessions/, plus any unsynced copies in the overlay homes |
| Service | ~/Library/LaunchAgents/dev.aiq.daemon.plist or ~/.config/systemd/user/aiq.service |
AIQ_DATA_DIR and AIQ_CONFIG relocate the data directory and the config
file.
Everything has defaults; the file is optional.
version = 2
[providers.claude]
binary = "" # discovered on PATH (outside the shim dir) when unset
model_scope = "auto" # scoped weekly cap that binds; "auto" reads settings.json model
wrapper = "" # launcher for interactive and long sessions (see below)
[providers.codex]
binary = ""
model_scope = "auto" # reads config.toml model
wrapper = ""
[selection]
interactive_policy = "sticky" # sticky | score
switch_pct = 95.0
stale_after_seconds = 900
weekly_reserve_pct = 5.0
max_workers_per_account = 3
max_depth = 3
min_hours = 0.25
weekly_weight = 5.0
auto_reset_credits = true # redeem a Codex or Claude reset credit when a holder is blocked
extra_usage = false # spend Claude prepaid extra usage once plan quota is gone everywhere
last_resort = ["copilot"] # providers used only after plan quota and extra usage
[worker]
retry = true
retry_max_seconds = 60.0
wait_for_slot_seconds = 0 # workers poll this long for a free slot at the cap
# limit_patterns = ["(?i)hit your (usage|session|weekly) limit", ...]
[poll]
interval_seconds = 300
timeout_seconds = 60
[display] # status page and `aiq status`
order = ["claude/work", "claude/home", "codex/work"]
[display.labels]
"claude/work" = "Claude · work"
[daemon]
listen = "127.0.0.1:7379" # 127.0.0.1 or [::1] only; see below
[auto]
providers = ["claude", "codex"] # pools auto may pick; empty = every provider
[long]
drain_pct = 4.0 # ask for a handoff note at this much remaining
fallback = ["claude", "codex", "agy"] # takeover order after the session's own provider
check_interval_seconds = 30
idle_grace_seconds = 20
idle_rotate_pct = 15.0 # move a quiet session off an account this low (0 = off)
idle_rotate_minutes = 15 # ...once it has been quiet this long
tmux_prefix = "aiq"
[updates]
enabled = true # keep npm-installed codex/claude current
interval_hours = 24
[telemetry]
claude_statusline = truedaemon.listen must be 127.0.0.1 or [::1] with a plain port. With anything else,
including localhost or 0.0.0.0, aiq daemon install refuses, and a daemon that is
already installed keeps polling and supervising long sessions but serves no status page
or API, so scripts calling its URL get connection refused. To read the page from another
machine, use a tunnel with a local port of its own: ssh -L 17379:127.0.0.1:7379 host,
then open http://127.0.0.1:17379/. A running daemon keeps the address it started with
until it restarts. A daemon from before 0.7.13 on the same port also stops a newer one
starting, which then logs why and takes over once the older one stops. aiq doctor and
aiq daemon status say what they can observe, and
lsof -nP -a -c aiq -iTCP -sTCP:LISTEN lists aiq's listeners. With a refused address, the
install script replaces the binary, then stops at the daemon step with the same advice.
A launcher is a named program that starts the CLI in an environment of its own: a sandbox, a recorder, a profiler. Routing happens first, so the launcher inherits a session that already has an account.
Nothing is ever wrapped unless its name is the word you typed. Plain
claude and codex keep exec'ing the CLI directly.
aiq launcher add boxed --provider claude --credential file \
--env BOX_ALLOW='$CLAUDE_CONFIG_DIR' -- /usr/local/bin/boxed --profile strictwhich registers
[launchers.boxed]
provider = "claude"
command = "/usr/local/bin/boxed"
args = ["--profile", "strict"]
credential = "file"
fallback = ["boxed-codex"]
[launchers.boxed.env]
BOX_ALLOW = "$CLAUDE_CONFIG_DIR"and writes ~/.local/share/aiq/shims/boxed, so all three of these start it:
boxed [args...] the shim
aiq boxed [args...] the same, spelled out
aiq long boxed [args...] supervised, with drain and takeover
The contract:
- The launcher receives the CLI's arguments, after any
argsof its own, and starts the CLI itself. CLAUDE_CONFIG_DIR(orCODEX_HOME) already names the chosen account's overlay home. A launcher that confines the CLI has to let it reach that directory, and the real~/.claude/~/.codexthe overlay links into. Anenvvalue may name those variables, asBOX_ALLOWdoes above; they expand when the launcher runs.- aiq drops its own shim directory from
PATHfirst, so a launcher that startsclaudeorcodexby bare name reaches the real CLI instead of routing a second time. A launcher may therefore be named after the very command it wraps.AIQ_BINARYholds the resolved path,AIQ_LAUNCHERthe name in use. credential = "file"writes the account's credential into the overlay home before the launch, for a launcher that cuts the CLI off from the system credential store. Without it such a launcher gets a login prompt. A token the CLI then refreshes stays in that file;aiq doctorsays so when the copy in the store has fallen behind.- Workers, logins, passthrough commands and telemetry probes never use a launcher. They are not sessions a person is sitting in front of, and some have no terminal at all.
A long session started under a launcher keeps it across a takeover.
fallback names the launchers it may move to, in order; the providers of
those launchers are the only ones it moves between, so a session never
quietly loses the launcher it was started with. aiq long <name> prints the
order it will use.
Not in this version: agy (Antigravity) accounts.
Codex quota comes from the documented codex app-server RPC. Claude has no
documented read path for subscription usage, so aiq uses the same OAuth
usage endpoint Claude Code's own /usage calls, with a grant minted through
Claude Code's OAuth client. That endpoint is not a public API and may change
or be restricted without notice; if it stops answering, routing degrades to
the status-line feed (live for the account in use) and worker rejections,
and aiq doctor says so.
See AGENTS.md and the quality gates.