On September 10 someone posted this:
Someone please tell me this exists: a meta-harness + kanban board to orchestrate my agents across platforms (Claude, Codex, Grok, Muse, etc.) tracking progress and handing off projects to each other cleanly when one hits its limits. What's the best way to do this?
That's Sophia Myang. It picked up 1,024 bookmarks and 340 replies. Bookmarks are the interesting number there. A like means someone agreed. A bookmark means someone wants to come back and use the thing.
She wasn't asking me. She was asking the internet, and a thousand people bookmarked the question instead of answering it. So I built it. Four days later baton-agents is on npm.
This post isn't really about my thing though. It's about the part of that request nobody mentions, which is that "hands off cleanly when one hits its limits" requires you to know a limit is coming, and every agent tells you in a completely different way. One of them doesn't tell you at all.
The part that sounds easy and isn't
The handoff itself is a solved problem. You write the state to a file and the next agent reads it. Fine.
The trigger is the hard part. If you wait until the agent says "you've hit your limit" you've already lost the session, and on Claude Code you've lost it mid-tool-call. You want to move at 90%, not at 100%. Which means you need a number, continuously, from a tool that wasn't built to give you one.
Three agents, three taps, and they don't resemble each other even slightly.
Claude Code: the number is there, just not where the docs point you
Claude Code has a status line that shows your 5-hour and 7-day usage. The obvious move is to pass your own statusLine command through --settings and read what it prints.
That doesn't work. On 2.1.268 Claude Code renders its own built-in status line and ignores a custom statusLine command passed via --settings or a project settings file. I verified it with a plain echo at both levels. Hooks from that exact same settings file do run, which is what makes it confusing. You get half the file honored and half ignored, with no error.
So you skip the status line and ask the source directly:
GET https://api.anthropic.com/api/oauth/usage
Authorization: Bearer <token from ~/.claude/.credentials.json>
That's the same endpoint /usage reads. The token is the OAuth token Claude Code already stored when you logged in, under claudeAiOauth.accessToken. You get back a 5-hour window and a 7-day window, each with a utilization percentage and a reset timestamp.
Two things worth saying out loud. That's your own token, on your own machine, going to the same host Claude Code already talks to. It shouldn't leave the process that reads it and it shouldn't get written to a log. Scrub bearer tokens on the way out of anything that records events.
The second signal is the StopFailure hook, whose error enum includes rate_limit. That's the wall itself rather than a warning, so it's the backstop, not the trigger.
Codex: it writes everything down, so read what it wrote
Codex doesn't need a hook. An interactive codex session writes its entire thread to a rollout file as JSONL, flushed per event. You tail the file.
The line you want is event_msg.token_count.rate_limits:
{
"primary": { "used_percent": 62.4, "window_minutes": 300, "resets_at": "..." },
"secondary": { "used_percent": 41.1, "window_minutes": 10080, "resets_at": "..." }
}
primary is 300 minutes, so that's the 5-hour window. secondary is 10,080 minutes, which is the week. Observed live on codex-cli 0.153.4.
Not injecting a hook is a feature, not laziness. Codex prompts you to review new hooks, and a wrapper that makes your agent ask you to approve something every time you start it is a wrapper you'll uninstall.
One trap cost me real time. Codex names the day directory from local time, but the rollout's own session_meta timestamp is UTC. A session I started at 20:33 EDT landed in sessions/2026/09/10 stamped 2026-09-11T00:33:44Z. If you match the directory against the timestamp you find nothing for four hours every evening. Compare local days, and keep the previous day as slack around midnight.
Antigravity: there is no number
agy is a closed Go binary. No hooks, no status line you can read, and the usage percentage genuinely is not written anywhere on disk. Its own status line fetches a quota summary from the backend and never persists it.
What you get is --log-file, and strings. RESOURCE_EXHAUSTED, out of quota, it resets in .... You match on those, which means you find out at the wall with no warning at all.
Then there's the detail that will bite anyone who writes this quickly. The reset text looks like:
Resets in 71h19m42s
Grab the first unit and you compute 71 seconds, restart the agent immediately, and hit the same wall. You have to sum every unit in the string. Three days versus seventy-one seconds is the kind of bug that looks fine in a test and wastes your evening in real life.
I'll be straight about the confidence here: those limit strings are in the binary and in the docs, and I have not watched an agy limit fire live. Everything about the Claude and Codex paths above I've seen with my own eyes.
So you don't build a meter, you build three
The instinct is to normalize all this into one usage number. You can't, honestly, because the three sources don't carry the same information.
Claude gives you a live percentage on two windows. Codex gives you a live percentage on two windows but only after it writes an event, so it updates in steps rather than continuously. agy gives you nothing until it's over.
What that means in practice is that a chain should put the agents that can warn you first and the blind one last. Not because agy is worse at coding, but because you can plan around a number and you can't plan around a wall.
The handoff, once you have the trigger
With a number you can act early. The shape that works:
- Keep a context bundle current as the session runs, not at the end. A bundle written after the limit hits is a bundle written by a process that's already dead.
- Warn at a threshold, hand off before the wall.
- Start the next option in the same terminal from that bundle. Another login of the same agent if you have one, otherwise the next agent.
- When everything is out, say which resets first and when, then wait for it instead of failing.
That last one is a change I made after watching my own chain fail a card the moment every agent was exhausted. Failing at the limit is technically correct and completely useless. You know exactly when the window reopens, so wait for it.
What's actually verified, and what isn't
I'd rather say this than have someone find it.
Verified live on my machine: the Claude usage endpoint and its percentages, the --settings status line being ignored on 2.1.268 while hooks run, the Codex rollout shape on codex-cli 0.153.4, the local-day versus UTC directory quirk, and a forced Claude to Codex handoff continuing in the same terminal.
Not verified live: a real Claude StopFailure rate limit firing, and every agy limit string. Those come from the CLI source and docs. They're in the code with fixtures behind them, but I haven't watched them happen.
That distinction matters more than it sounds. A limit handler you've never seen fire is a guess with good syntax.
The thing itself
All of the above is inside baton. You type baton claude, baton codex or baton agy instead of the bare command and get the same interactive agent, with a board next to it, the usage tap running, a bundle kept current, and the handoff when the limit lands.
It's subscription logins only. It strips ANTHROPIC_API_KEY, OPENAI_API_KEY and the rest of that family before any agent starts, because the whole point is using the plan you already pay for. It never edits your ~/.claude/settings.json or your ~/.codex/config.toml.
npm install -g baton-agents
But if you just want the three taps and you'd rather wire them yourself, they're all above. That was the actual answer to the question, and nobody in 340 replies wrote it down.
