Leg
Type leg claude, leg codex, leg agy or leg grok instead of the bare command. You get the same interactive agent; Leg opens a board next to it, watches the usage limit, keeps a handoff bundle current, and when the limit hits it starts the next agent in the same terminal from that bundle.

Claude hits the five-hour wall. The terminal reads handing off to codex, and codex carries on there. Nothing is retyped. (the full 53-second run)

You keep using your coding agents exactly as you do today, in any terminal,
from your own config directory: Leg adds its hooks in a separate per-session
settings file. Client approval and trust behavior is preserved by default.
Permission bypass requires explicit opt-in.
Client config writes require the separate folder-trust opt-in
or portable harness. leg claude --model opus is
claude --model opus with four things running alongside it:
- A board. Opened once in your browser, reused after that. Every Leg
session in every terminal is a row on it, and the row's register reads
status, repo on branch, uncommitted and unpushed counts, agent and model,
and how long it has been quiet:
waiting on you leg on main dirty 3 ahead 2 claude/fable. Under that come the prompt, the one thing worth knowing, the files it is touching and the usage bucket that will stop it. Capacity is one strip at the top rather than a region, with the login panels and their gauges behind Capacity and models. Two sessions editing the same file in one repo are flagged on both rows, and a second session in a checkout that already has one gets its own worktree and a Land button instead of writing over the first. - Usage tracking per agent and account, from what each CLI already
exposes: Claude Code's usage endpoint and its
StopFailurehook, Codex's read-only app-server rate-limit read, agy's log, and Grok's billing proxy endpoint. - A context handoff bundle (context-handoff-bundle) refreshed as the session goes, so the work is always ready to hand off.
- The handoff itself. Near the limit you get a warning. At the limit Leg
saves the bundle, stops the agent, and starts the next option in the same
terminal from that bundle. The options are a ladder of rungs, and a rung is
an agent, a login and a model: the default is
claude/fable,claude/opus,claude/sonnet, then each remaining installed agent. A Fable limit walls Fable and not the login, so the first move is usually to another model on the same subscription, and for claude that move isclaude --resume <id> --model opus, which keeps the conversation instead of replaying the bundle. Only when every claude rung is out does it move to another CLI. Settings holds the ladder for new terminals,leg ladderedits it from a shell, and Details on a row edits the copy that terminal is running. Nothing is retyped. When every rung is out, Leg tells you which resets first and when, waits for that reset with a countdown, and starts that agent from the bundle.
Subscription logins only: Leg strips ANTHROPIC_API_KEY,
ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL, ANTHROPIC_CUSTOM_HEADERS,
OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_API_BASE, GEMINI_API_KEY,
GOOGLE_API_KEY, GOOGLE_GEMINI_BASE_URL, GOOGLE_GENAI_USE_VERTEXAI,
GOOGLE_GENAI_USE_ENTERPRISE, GOOGLE_CLOUD_PROJECT,
GOOGLE_CLOUD_LOCATION, GOOGLE_APPLICATION_CREDENTIALS, GROK_API_KEY,
XAI_API_KEY, CLAUDECODE, CLAUDE_CODE_*, CLAUDE_EFFORT, and
CLAUDE_PLUGIN_DATA before any agent starts. It then sets
CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0 for a detached
Claude print session. Leg's hooks ride in a separate per-session --settings
file. Automatic folder-trust writes are off by default; LEG_TRUST=auto
opts into the client config changes below. The optional
portable harness can also write client configuration,
only after leg harness enable. leg uninstall removes only ~/.leg;
it does not remove existing client trust answers.
The folder-trust answer
Each agent CLI asks once, the first time it runs in a directory, whether you
trust that folder, and Claude Code asks a second question when a CLAUDE.md
above the repo imports a file from outside it. These prompts remain in place
by default, including during handoffs. An unattended handoff may wait for you.
Set LEG_TRUST=auto only if you want Leg to record trust before agent launches
and handoffs. This is independent of auto-approve mode and authorizes these
client config writes, including approval of external CLAUDE.md imports:
| agent | file | what is written |
|---|---|---|
| claude | ~/.claude.json (or $CLAUDE_CONFIG_DIR/.claude.json) |
projects["<repo>"].hasTrustDialogAccepted: true |
| claude | the same entry, only when such an import exists | hasClaudeMdExternalIncludesApproved, hasClaudeMdExternalIncludesWarningShown |
| codex | ~/.codex/config.toml |
[projects."<repo>"] trust_level = "trusted" |
| agy | ~/.gemini/antigravity-cli/settings.json |
trustedWorkspaces: ["<repo>", "<worktree>"] |
| agy | ~/.gemini/config/projects/default-cli-project.json |
writable Git folder resources |
| agy | existing ~/.gemini/trustedFolders.json |
TRUST_FOLDER entries |
For Claude Code this is the documented remedy: its permissions guide says to
set projects["<path>"].hasTrustDialogAccepted to true in ~/.claude.json,
where <path> is the repository root.
Claude and Codex trust updates require an existing config file. agy can create
its settings or project file when the corresponding config directory already
exists. Existing trust decisions are retained. When Leg approves an external
CLAUDE.md import it prints the
full path of every file it approved, to the terminal and to the session
timeline on the board, so the approval is on the record rather than invisible.
LEG_TRUST=never disables future trust writes; it does not revoke trust already
recorded. Unset, empty and unrecognized values also leave automatic trust off.
The legacy BATON_TRUST=auto opt-in remains supported when LEG_TRUST is unset.
Contents
- 60-second run
- What Leg reads from each agent
- How a handoff works
- Two sessions in one repo
- More than one human
- The board
- Every conversation on this machine
- Second accounts, and what the terms say
- What is and is not touched
- CLI reference
- Background tasks: the v0.1 extras
- Troubleshooting
- Documentation
- Contributing
- License
60-second run
Prerequisites: Node 22 or newer, git, Python 3 with pip, and at least one
logged-in agent CLI (claude, codex, agy or grok).
npm install -g @ucsandman/legcli
pip install -U context-handoff-bundle
cd <any repo>
leg claude
npm install -g leg-agents is the same release: it pins this version of
@ucsandman/legcli and exposes the same leg binary.
That is the whole setup. The first leg <agent> starts the board on
http://127.0.0.1:4747 and opens it; later sessions reuse it. Anything after the
agent name passes straight through (leg codex -m gpt-5.3-codex-spark,
leg claude --resume). The agent's own prompt and permission flags pass
through unchanged. Leg adds no permission bypass by default; --auto-approve
opts in and --no-auto-approve overrides a saved opt-in for that terminal.
Leg's hooks ride in a separate per-session --settings file. Folder-trust
and portable-harness writes each require their own opt-in.
Leg is commercial, source-available software, and the source is on
GitHub: every .mjs file that runs is in the package you just installed, at
$(npm root -g)/legcli/src, and the license lets you read it and modify
your own copy. There is nothing compiled, minified or bundled to see through.
What Leg reads from each agent
Nothing is guessed from screen scraping. Each tap was read from the CLI's source or documentation and then checked on a real machine (2026-09-11, Claude Code 2.1.268, codex-cli 0.153.4, agy 1.2.0); the rightmost column says which.
| agent | usage percentages | the wall (limit hit) | how Leg attaches | status |
|---|---|---|---|---|
| claude | GET api.anthropic.com/api/oauth/usage with the login Claude Code stored, the same data as /usage and the built-in status line (five_hour, seven_day, utilization, resets_at); polled every 60 s |
StopFailure hook with error: rate_limit (docs) |
one extra settings file per session via --settings, carrying only Leg's own hooks; autoContinueAtUsageLimit is set to false because Leg owns the handoff |
observed live |
| codex | read-only account/rateLimits/read through the app-server, polled every 60 s by the board and active attach; windows are identified by duration (300 minutes = 5h, 10080 = 7d) |
task_complete.error.codex_error_info: usage_limit_exceeded, message "You've hit your usage limit … try again at …" (codex-rs/protocol/src/error.rs) |
no model turn and no hook are injected; the board reads the CLI backend and records only returned windows | verified by source and regression tests |
| agy | none exposed (agy's own status line fetches a quota summary that is written nowhere) | RESOURCE_EXHAUSTED, "it resets in …", "out of quota" in the log |
--log-file per session; ~/.gemini/antigravity-cli/history.jsonl gives the prompts and conversation id |
observed live (a real RESOURCE_EXHAUSTED with its reset was read from the log on 2026-09-11) |
| grok | GET cli-chat-proxy.grok.com/v1/billing?format=credits and GET cli-chat-proxy.grok.com/v1/user?include=subscription with OAuth token from ~/.grok/auth.json (creditUsagePercent, currentPeriod weekly reset); polled every 60 s |
-32003 rate limit error, "You've hit the rate limit for your plan. Try again later.", "Rate limited (429)", StopFailureKind::RateLimit (xai-org/grok-build) |
--debug-file grok.log per session; ~/.grok/sessions/<encoded-cwd>/prompt_history.jsonl gives prompts and session id |
verified by source and proxy endpoint |
Why not Claude Code's status line JSON (rate_limits.five_hour.used_percentage):
on 2.1.268 the custom statusLine Leg passes through --settings did not
run, so the endpoint poll is the source. Leg still writes a statusLine
entry that records the same fields, so the moment a build honours it the poll
becomes a fallback.
For Codex, Leg does not infer availability from a lower percentage: only an
explicit available answer from the backend clears an earlier wall. The board
labels each bar as <n>% used and marks an old reading as stale rather than
presenting it as current.
How a handoff works
- Warning. At 85 % of any window (
LEG_WARN_PCT) the session card turns amber, the event log names the next option, and the terminal bell rings once. - Limit. claude: the
StopFailurehook fires withrate_limit. codex: the rollout reportsusage_limit_exceeded. agy: the log saysRESOURCE_EXHAUSTED. The account is marked walled until the reset the CLI reported (or the soonest known window reset). - Bundle. Leg writes structured notes (task, the last messages from the
transcript,
git diff --stat, dirty files, files edited this session, recent commits, why it stopped) and runscontext-handoff-bundle save --repo-localwith one slug per session, updated in place (--update <slug>) at every checkpoint, about every two minutes while the session is active, and at every warning, limit and hand-off. If the session maintained.leg/SYNTHESIS-<session-id>.md, Leg inlines it into the resume file as a## Synthesissection ahead of the raw dump. - Switch. The agent process is stopped and the terminal is restored. Two
claude destinations keep the conversation itself instead of the bundle: a
weaker model on the same login (
claude --resume <id> --model <alias>), and another claude login you added withleg accounts add, which sees the same transcript through theprojectsjunction the account carries (claude --resume <id>under that login'sCLAUDE_CONFIG_DIR). A weekly or Fable wall on one login then continues on the other with the conversation it already had, and the timeline sayskept the conversation. Every other rung starts in the same terminal with a short pointer prompt: read.leg/RESUME-<session-id>.md(thecontext-handoff-bundle loadoutput, the## Synthesissection if present, and the reason for the switch), checkgit statusandgit diff, continue, do not ask the human to restate the task. The pointer prompt directs the next agent to read Synthesis first, treat ruled-out approaches as settled, and start from the top-ranked next step. The same text is copied to.leg/RESUME.md, the file people open by habit, and both are stamped with the commit and the live terminals they describe.claude "<prompt>",codex "<prompt>"andagy -i "<prompt>"all open the normal interactive session with that first turn. - The ladder. A rung is an agent, a login and a model, and Leg walks from
rung 1 every time. Other models on the same login come first, then other
accounts of the same agent, then every other agent in the saved order, each
tried once. It is a priority list, not a rotation: put agy at the bottom and
agy is the last rung from a Claude terminal and from a Codex terminal alike.
The board prints the exact sequence with the rung running now marked, plus
the first rung eligible from current install and limit state. Use Change
the ladder in a terminal's expansion to change that terminal,
leg ladderto do it from a shell, or Settings to set the default copied by new terminals. A rung whose CLI is missing, whose wall has not reset, or that shares the window that is already out, is skipped with its reason. - All out. The terminal prints each option with its reset time, soonest
first, then stays open with a countdown to the first reset and starts that
agent from the bundle when it arrives. The row says
waiting for <agent> at <time>. Ctrl-C (or End on the row) quits with exit 3 instead.
You can force a handoff any time: the Hand off now button on the row, or
leg sessions handoff <id>. Verified on this machine: leg claude opened
the real Claude Code TUI with Leg's hooks firing into the session log, the
usage poll recorded 36 % of the 5h window and 74 % of the 7d window, the
warning fired at 96 % of the 7d window and named codex as the next option, and
a limit saved the bundle, stopped claude and started codex in the same
terminal with the pointer prompt. A real StopFailure arrived on 2026-09-11
and is kept at fixtures/live/claude/limit-rate_limit.json; to drive the path
on demand, leg sessions simulate-limit <id> sends the same StopFailure
rate_limit payload Claude Code would send through Leg's hook: verified end
to end on a haiku session, the hook set the limit, the runner saved the
bundle, stopped claude and started codex, which read .leg/RESUME.md on its
first turn. The simulated wall clears after two minutes and is never kept as
evidence. The first real StopFailure was saved that way, with secrets
scrubbed, at fixtures/live/claude/limit-rate_limit.json, and the claude docs
row flipped to observed-live (node scripts/live-limits.mjs). The same
capture is wired for codex usage_limit_exceeded and agy RESOURCE_EXHAUSTED;
no payload for either has been kept yet.
Terminals started by this version can change their ladder while they run: Settings holds the default for new terminals, and Details on a row edits the copy that terminal is running. An older terminal stays on the order it started with; its row says a restart is needed and can save the desired default for the next launch. A normal agent exit ends the terminal. It does not trigger a handoff.
The working environment travels too (optional)
With the portable harness enabled (leg harness enable,
off by default), the hand-off also prepares the destination's environment
before it starts: the source agent's global rules, identity, hooks, skills,
subagents, slash commands, MCP servers and permissions, rendered into the
destination's own files as far as it can represent them. The terminal then
reads, for instance, codex harness partial · 8/8 components, 3 dropped · 1 file(s) written, and the terminal's details list what was dropped and why.
The source is fingerprinted so an unchanged environment costs a few stat
calls; a policy (warn, sync, strict) says whether a hand-off may write,
and strict refuses a destination it cannot make safe. Credentials never move
and which login runs stays the account layer's decision.
Two sessions in one repo
Two agents in one working tree write over each other's files. So when you
start leg codex in a checkout where leg claude is already live, the new
session gets its own git worktree, <repo>/.leg-worktrees/<session-id> on
branch leg/<session-id>, cut from the branch the checkout has out, and the
terminal prints one line saying where it is. The agent starts there; the card,
the usage tracking and the handoff work the same. --no-worktree shares the
checkout on purpose (Leg takes the flag out; the agent never sees it).
The card of a session with its own worktree has a Land button. It sends
the branch through the merge queue: whatever the agent left uncommitted is
committed on the branch, the branch is rebased onto its base, the repo's test
command runs (package.json test, pytest, or none with a warning), and the
base is fast-forwarded, never merged. When a step fails nothing lands and the
card says why: rebase-conflict with the files, tests-red with the end of
the output, dirty-trunk when the checkout has local changes the landing
would overwrite. Local changes it would not touch are left alone. The
landed-on-trunk list says which terminal landed each commit. Remove safely
prunes a finished session only when its worktree is clean and its branch is
already on the base. Remove record is a separate visible button with a
confirmation: it removes only Leg's saved s
