Skip to content

Latest commit

 

History

24 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

yeet

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.

Quick start

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, serialized

chat 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.

The command panels

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.

Opening them with ⌘ keys

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

How it works

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 attach resurrects via claude --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. Your verify command (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-only land. 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 (--edit to adjust in $EDITOR first). Capped at 2 attempts.

Config (.yeet/config.json)

{
  "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
}

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.

Keeping the fleet alive with the lid closed

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.

Development

bun test              # unit + E2E (E2E drives real tmux + a fake agent)
./yeet ls             # run from source

archive/ 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.

About

Agents in seconds, for plebs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages