A terminal multiplexer with one opinion: the screen is split down the middle, Claude Code lives on the left, Codex lives on the right. Each side keeps its own list of sessions in its own sidebar, and either side can be split horizontally so you can work on two Claude sessions and two Codex sessions at once.
┌─CLAUDE────┬──────────────────╥──────────────────┬─CODEX─────┐
│▸ atlas │ claude · atlas ║ codex · atlas │▸ atlas │
│ ·mobile │ ║ │ checkout │
│ checkout*│ > implementing ║ > reviewing … │ ·mobile │
│ ├──────────────────╫──────────────────┤ │
│ │ claude · mobile ║ codex · mobile │ │
└───────────┴──────────────────╨──────────────────┴───────────┘
Both sidebars are the same width, so the divider falls on the true centre of the
window. In the sidebar, ▸ marks the session in the focused pane and · one
that is on screen elsewhere. After the name:
● |
working — it said something recently |
▲ |
wants you — it went quiet, or rang the bell, while off screen |
✗ |
the process exited |
▲ is the one to watch. It appears when an agent stops, not when it speaks,
so it means "this one is waiting on you" rather than "this one is busy".
Download a prebuilt binary from Releases
— yakka-macos-universal.tar.gz runs on both Apple Silicon and Intel:
tar xzf yakka-macos-universal.tar.gz
chmod +x yakka
xattr -d com.apple.quarantine yakka # macOS Gatekeeper; the binary is unsigned
mv yakka ~/.local/bin/ # or anywhere on your PATHEach release also ships a .sha256 next to the archive:
shasum -a 256 -c yakka-macos-universal.tar.gz.sha256Or build it yourself:
cargo install --git https://github.com/romankhadka/yakka --root ~/.localNeeds claude and codex on your PATH. Run yakka.
Ctrl-a is the prefix, configurable via prefix. It is a leader, not a
chord: press and release Ctrl-a, then press the command key on its own.
Everything else goes straight to the focused agent, so the agents keep all their
own bindings.
| Key | Action |
|---|---|
Ctrl-a h / l |
Focus the left / right half |
Ctrl-a j / k |
Focus the pane below / above |
Ctrl-a Tab / S-Tab |
Next / previous session on this side |
Ctrl-a 1–9 |
Show the nth session of this side |
Ctrl-a n |
New session (directory picker) |
Ctrl-a s |
Split this half horizontally |
Ctrl-a w |
Close this pane, leave the session running |
Ctrl-a x |
Kill this session |
Ctrl-a z |
Zoom this pane to the whole window |
Ctrl-a b |
Show or hide the sidebars |
Ctrl-a r |
Restart the process in this pane |
Ctrl-a [ |
Scroll back through history |
Ctrl-a d |
Show what this session changed |
Ctrl-a * |
Broadcast keys to every visible pane |
Ctrl-a m |
Move this session to the other side |
Ctrl-a , |
Rename this session |
Ctrl-a ? |
Help |
Ctrl-a q |
Detach — leave, with the agents still running |
Ctrl-a Q |
Quit and stop every agent |
Ctrl-a Ctrl-a |
Send a literal Ctrl-a to the agent |
In scroll mode: j/k by line, Ctrl-d/Ctrl-u by half page, g/G for the
ends, / to search back through history, q back to live.
The picker offers recently used directories first; typing filters by fuzzy match, and recency only breaks ties between equally good matches.
Selecting a session that's already in this side's other pane swaps the two
rather than showing it twice, so Ctrl-a Tab with a split is a quick way to
trade places.
Ctrl-a n lists your scan roots and their immediate children, filtered as you
type. Choosing one starts that side's default agent there.
If the highlighted directory is inside a git repository, Tab switches to
worktree mode: yakka branches yakka/<agent>-N off the default branch,
creates a worktree beside the repository, and runs the agent there. Two agents
working at once then cannot edit the same files — which is the point of running
them at once.
Killing a session removes its worktree, unless it has uncommitted changes, in which case it is left alone and says so.
Quitting saves the layout to state.json, so the next launch rebuilds the same
panes: reattaching to any agent the session host still has, and otherwise
resuming the conversation with claude --continue or codex resume --last —
both scoped to the session's directory, so nothing picks up an unrelated
conversation from another repo. yakka --fresh starts empty.
If a resumed session dies immediately because there was nothing to resume, it is
restarted once without the flag. A process that exits keeps its final screen and
offers Ctrl-a r; one that never started explains why in its pane.
The agents do not run inside the yakka you are looking at. They run in a per-user session host that yakka starts on first use and talks to over a unix socket, so leaving is a detach rather than a kill:
Ctrl-a q |
Leave. The agents keep working. |
yakka |
Come back to them, screens and scrollback intact. |
Ctrl-a Q |
Stop every agent and close the host. |
yakka kill |
The same, from outside. |
This is the thing --continue cannot do: a resumed conversation replays what
was said, while reattaching finds the same process still running — mid-task,
mid-output, with everything it had printed still on screen.
The host keeps the last couple of megabytes of each session's output. Beyond that the scrollback is dropped and reattaching restores the visible screen only.
If yakka cannot reach the host it says so and runs the agents itself, so a
broken socket costs you detaching rather than the session. --no-server asks
for that on purpose, and server = false makes it the default; either way
quitting stops the agents, and yakka asks before it does.
~/.config/yakka/config.toml, all optional:
scan_roots = ["~/code"] # directories whose children fill the picker
sidebar_width = 22 # columns per sidebar
sidebars = true # show them on startup
prefix = "ctrl-a" # leader key: ctrl-/alt-/shift- plus a key, or f1..f20
max_panes = 2 # panes a side can be split into, up to 4
mouse = true # capture the mouse (see Notes)
idle_after = 2.0 # seconds of silence before an agent counts as idle
server = true # run the agents in a session host (see Detaching)
left_agent = "claude" # which agent new sessions on each side start with
right_agent = "codex"A side has a default agent, not a fixed one — any session can run any agent. Adding one takes no code:
right_agent = "gemini"
[[agents]]
name = "gemini"
command = "gemini"
resume = ["--continue"] # omit if it cannot resume; it will start freshAn [[agents]] entry named claude or codex replaces that builtin, which is
how you point yakka at a wrapper or a project-local build.
Environment overrides:
| Variable | Effect |
|---|---|
YAKKA_CLAUDE_BIN |
Binary to run for the claude agent |
YAKKA_CODEX_BIN |
Binary to run for the codex agent |
YAKKA_<NAME>_BIN |
Same, for any agent you define |
YAKKA_CONFIG_DIR |
Where config.toml and state.json live |
The mouse is captured: clicking focuses a pane, clicking a sidebar row shows that session, and the wheel scrolls a pane's history. An agent that asks for mouse reports gets them instead — without capture the agents would never see the mouse at all, so their own scrolling would be dead.
Hold Option (macOS) or Shift for your terminal's native selection and
copy while capture is on. Set mouse = false to hand the mouse back entirely.
Where the terminal supports the Kitty keyboard protocol (Ghostty, Kitty, WezTerm)
yakka enables key disambiguation so Shift+Enter reaches Claude Code as a soft
newline. Elsewhere it behaves as it always has.
Sidebars hide themselves below 100 columns. Below 60x20 yakka says so rather than drawing something unusable.
cargo test # unit + end-to-end
cargo test --test real_agents -- --ignored --nocapture # against the real CLIs
cargo clippy --all-targets -- -D warningsThe end-to-end tests run the real binary inside a PTY, press real keys, and read
the rendered screen back, with stub agents substituted via YAKKA_CLAUDE_BIN and
YAKKA_CODEX_BIN.
Design notes are in docs/superpowers/specs/2026-07-24-yakka-design.md.
Releases are cut by tagging: git tag v0.2.0 && git push --tags builds a
universal macOS binary and a Linux x86_64 binary and publishes both with
checksums.
MIT — see LICENSE.