Agent fleets on a better job. Run many Claude Code sessions in parallel git worktrees under tmux, then land only work that survives verification.
yeet is not a terminal multiplexer — tmux (on a dedicated socket, invisible to your own tmux setup) holds the sessions so they survive closing your terminal. yeet is the layer tmux doesn't have: worktree lifecycle, a serialized merge queue, claims-vs-diff verification (does the agent's story match its code?), and targeted retry dispatch.
cd your-repo
yeet # opens the fleet menu; first run self-initializes .yeet/Everything is also a command:
yeet new "add rate limiting to the API"
yeet new -a codex "fix the flaky auth test"
yeet ls # your sessions, by title; --watched adds the agents yeet only observes
yeet enter 1 # go to a session (ctrl-q returns to the menu)
yeet summary # work-so-far across the fleet, from the transcripts
yeet chat 1 # converse with a session without entering it
yeet ask "how is the rate limiter coming along?" # chat with yeet itself
yeet merge 1 2 # rebase → verify → squash → land, serializedchat injects your message and reads the agent's reply back out of its
transcript. ask is the fleet concierge: a headless claude turn primed with
the aggregated state of every repo yeet knows (statuses, diffs, claims,
costs, recent history — landed and archived sessions included) plus read-only
access to the worktrees, so it grounds progress reports in the actual diffs —
and it remembers the conversation between questions. It answers in a shape
built for a terminal: a headline sentence, then short bullets, a fenced code
or diff excerpt when the code is the point, and a small ASCII diagram when the
answer is about a shape. Both surfaces render that markdown rather than
flattening it. / in the command panel is the same conversation from inside a
session.
History & search. Every lifecycle act (created, verified, flagged, landed,
retried…) lands in .yeet/events.jsonl; yeet log [id] shows the timeline.
yeet search "token bucket" is hybrid keyword+semantic search over the
fleet's accumulated history — events, prompts, agent replies, claims, and
landed commit messages — backed by tennis
(one static binary, free local embedder by default; set
"search": {"embedder": "openai:text-embedding-3-small"} to use OpenAI).
The index lives at .yeet/tennis.db and refreshes automatically; ask uses
it for retrieval, so "have we solved something like this before?" works.
Three keys, three surfaces, each a box across the bottom of the terminal over whatever you were looking at — a live agent session or the fleet view. Same in both places.
ctrl-s — the sessions list, with a search box:
yeet · 6 other sessions ctrl-s closes
search› auth 2 of 6
▸ ◉ gx Rewrite the login flow
#4 · waiting · +142 −8 · ✓ 3/4 · $0.420
● console Session cookie rotation
adopted · working · +2453 −4947 · $18.44
type to search · ↑/↓ move · ↵ enter · esc clears, then closes
Rows are the fleet list's own blocks — the panel calls the same renderer, so a
session reads identically wherever you meet it. ↵ goes to that session via
switch-client, so the session you were in keeps running and nothing detaches;
one whose tmux died gets resurrected on the way in.
The search box takes repo:gx, status:working, agent:codex, #4, or any
words, all ANDed. Words match by meaning as well as spelling: type "auth"
and a session titled "Rewrite the login flow" comes back, because
tennis indexed that session's prompts,
replies, claims and landed commits and knows they're the same subject. That
pass is debounced and read-only against an index yeet search/ask already
maintain, and it only ever adds rows — a late result never pulls one out from
under the cursor. No index, no tennis, or an agent running in the Claude Code
app: the lexical filter stands on its own.
ctrl-a — chat. The fleet concierge in a proper chat box: turns down the
page, newest against the input line, ↑/↓ to scroll back. Every repo, landed
and archived sessions included — the same conversation yeet ask continues
from the shell.
ctrl-t — a new session. Pick the repo (type to search every repo on the
machine), then the agent, then an optional first prompt; it creates the session
and drops you into it.
The dots are one per session, most urgent first — purple means something is waiting on you. They sit on the status bar with no panel open, so you can see the fleet from inside a session without opening anything.
ctrl-q detaches back to the fleet view. All four keys are configurable
(switchKey, chatKey, newKey, detachKey — tmux key syntax), which
matters because tmux binds them at root level and the agent underneath never
sees them: ctrl-a costs you readline's beginning-of-line inside Claude
Code, given up deliberately, and ctrl-t (transpose) is the cheaper of what
was left after that.
tmux only understands C-/M-/S- modifiers, and macOS terminals don't send
Cmd to the tty at all — so ⌘S can only reach yeet if your terminal rewrites it
into ctrl-s first. One line per key, and yeet doctor prints these too:
| terminal | config (⌘S → ctrl-s; ⌘A is \x01, ⌘T is \x14) |
|---|---|
| Ghostty | keybind = super+s=text:\x13 |
| iTerm2 | Prefs → Keys → + → ⌘S → "Send Hex Code" → 0x13 |
| kitty | map cmd+s send_text all \x13 |
| WezTerm | { key="s", mods="CMD", action=wezterm.action.SendString("\x13") } |
| Terminal.app | no keybinding support — use the ctrl keys directly |
your terminal (disposable)
└─ yeet (CLI/TUI)
└─ [attached] tmux -L yeet attach ← thin client, C-y detaches
tmux server (daemon — survives terminal close)
├─ session 1: claude in .yeet/worktrees/rate-limiting
└─ session 2: claude in .yeet/worktrees/flaky-auth-test
- Each session = a git worktree + branch (
yeet/<slug>) + a claude conversation with a yeet-chosen uuid (--session-id), so a dead session is always resumable (yeet attachresurrects viaclaude --resume). - Verification (
yeet verify <id>): parses the agent's transcript (completed todos = claims), cross-checks claims against the actual diff, and scans added lines for stub bodies, TODO markers, skipped tests, and swallowed exceptions. Yourverifycommand (tests) gates too. - Merge queue (
yeet merge <ids...>): serialized per session — commit wip, rebase onto target, verify on the rebased tree, squash to one commit (message built from the claims),--ff-onlyland. A failing session is flagged and skipped; the target branch is never touched by it. - Retry (
yeet retry <id>): builds a prompt from exactly which claims failed and which findings were planted, injects it into the live session (--editto adjust in $EDITOR first). Capped at 2 attempts.
yeet config set <key> <value> edits one key from the shell. Adapters handle
agent quirks: claude gets --session-id/--resume and its folder-trust dialog
auto-accepted; codex gets its update prompt skipped (never auto-updated) and
trust accepted, with conversations discovered by matching rollout cwd to the
worktree. Codex update_plan steps and Claude todos/tasks both become
verification claims.
tmux keeps sessions independent of your terminal, but it can't keep macOS
awake. On AC power: clamshell mode (external display) or
sudo pmset -a disablesleep 1. If the machine does sleep, sessions freeze and
resume — an in-flight turn's API stream breaks and retries.
bun test # unit + E2E (E2E drives real tmux + a fake agent)
./yeet ls # run from sourcearchive/ holds the previous lives of this project: a hand-rolled C PTY
wrapper (yeet.c — superseded by tmux as the substrate), an OpenTUI
compositing experiment (abandoned: never composite a live agent's output), and
a raylib/libghostty terminal.
{ "agent": "claude", // default adapter: claude | codex | fake (tests) "target": "main", // branch the queue lands on "verify": "bun test", // gates the merge queue "setup": "bun install", // runs once in each fresh worktree "copy": [".env"], // gitignored files copied into worktrees "basePort": 43000, // sessions get YEET_PORT = basePort + id "detachKey": "C-q", // detach from an attached session "switchKey": "C-s", // open the sessions panel "chatKey": "C-a", // open the chat panel "newKey": "C-t", // open the new-session panel "agentArgs": ["--permission-mode", "acceptEdits"] // extra agent argv }