kraal
A terminal workspace manager for people who live in many SSH sessions and run coding agents.
kraal is a multiplexer in the tradition of tmux and Zellij that runs inside your terminal (Ghostty, kitty, WezTerm, iTerm2, …). It brings together what was spread across different tools:
- Workspaces you declare once (from Wave and Zellij): a file lists your servers, kraal opens one pane per server, laid out the way you want. Zellij layout files work for their common part: panes, tabs, splits, commands, directories (templates, swap layouts and plugin panes are skipped with a warning).
- Agent awareness (from herdr): kraal knows which panes run Claude Code, Codex and other agents, and whether each one is working, waiting for you, or done, in the sidebar, the tab bar and as desktop notifications. One key jumps to the next agent that needs you.
- Your servers at hand: an inventory of every machine you SSH to (filled from your
workspaces,
~/.ssh/config, and everysshyou type), grouped, with a picker that opens one server or a whole group in a grid. Copy files local ↔ server or server ↔ server withkraal cp, or by dropping a file on an SSH pane. - Several subscriptions per agent: keep a personal and a work Claude plan, two Codex plans… each in its own config directory, logged in with the agent's own login. kraal shows each account's usage, picks one per workspace or directory, and when one reaches its threshold it alerts you, offers to continue the conversation on another, or switches by itself.
- Git and worktrees for agents: every pane shows its branch and whether it has changes; start an agent on its own branch in a new worktree, then review, commit, open a pull request, merge or remove it from kraal.
- Fleet tools: run a command on a whole group of servers at once, follow logs on every server in a grid, keep SSH tunnels open, and see the dev servers your panes started.
- Tools where you work, and a production guard: lazygit, k9s, htop, the logs… open on the focused pane's server or in its directory. Frames show each pane's Kubernetes context, Terraform workspace and AWS profile; production panes are red, and kraal asks before broadcast typing or a multi-line paste reaches them (based on the names it detects, see below).
- Hints: label the links, paths, commits, IPs, servers and pods on screen; a label copies it,
or acts on it (open, edit at the line,
git show, ssh, pod logs). - Runbooks: procedures kept as files, yours or a repository's. kraal shows the plan, runs the steps server by server, stops at the first failure and logs everything each step printed; Markdown runbooks run their shell blocks one at a time, as you read.
- Events and hooks:
kraal eventsstreams what happens (agents, exits, SSH, production, tunnels, runbooks) as JSON lines, and[hooks]runs a command on any of them. - Persistence and a real UI: sessions keep running when you close the terminal. After a restart or a reboot, the processes are gone but kraal rebuilds the layout, reconnects SSH shells and resumes agent conversations; other commands wait for Enter. Mouse support, floating panes, zoom, scrollback search, rich preview panes.
╭─ WORKSPACES ─────────╮ 1 web01 ● 2 agents ● + ●1 ●1 ctrl+b menu
│ ● prod │ ╭ web01 ───────────────────────────╮ ╭ api ────────── ● blocked · claude ─╮
│ 2 tabs · 4 panes │ │admin@web01:~$ uptime │ │ Do you want to proceed? │
│ ● staging │ │ 10:12:04 up 41 days, 2 users │ │ ❯ 1. Yes │
│ 1 tab · 2 panes │ │admin@web01:~$ │ │ 2. No │
│ ○ lab │ │ │ │ │
│ not running │ │ │ ╰────────────────────────────────────╯
├─ AGENTS ─────────────┤ │ │
│ ● api │ │ │ ╭ docs ───────────── ● done · codex ─╮
│ blocked · claude │ │ │ │ • Updated README.md │
│ ● docs │ │ │ │ ▌ │
│ done · codex │ │ │ │ │
╰──────────────────────╯ ╰──────────────────────────────────╯ ╰────────────────────────────────────╯
Install
kraal is a single binary for Linux (x86_64, arm64) and macOS (Apple Silicon, Intel). Linux is the primary target; macOS passes the test suite but is not used day to day yet.
|
The script takes the archive for your machine from the
releases and checks it against its checksum before
installing anything. KRAAL_VERSION=v0.1.0 picks a release, KRAAL_INSTALL_DIR another folder.
Later, kraal update replaces kraal with the latest release the same way (--check only says
whether there is one, --to v0.2.0 picks one); a server already running keeps the old version
until kraal kill. Or build from source:
Quick start
Keys
One key to remember: ctrl+b opens a menu of commands. Press it, let go, and the menu
shows what each next key does; press that key (or click it).
One rule: after ctrl+b, a theme's letter runs its main action, and the same letter in
capitals opens the theme's menu. ctrl+b s connects to a server; ctrl+b S opens the Servers
menu (upload, download, edit the list). Letters are taken for what they mean, so the rule holds on
any keyboard layout.
| Theme | ctrl+b + letter |
ctrl+b + capital: the menu |
|---|---|---|
| servers | s connect (or a group in a grid) |
S: s connect, u upload, d download, e edit the list |
| broadcast | b typing goes to the whole tab, b again stops |
B: b this tab, w the whole workspace, m mark this pane, s the marked panes, o stop |
| workspaces | w choose one |
W: w choose, n new, ←/→ previous/next, e rename, s save the layout, x close |
| agents | a start one on an account |
A: a start, g go to the agent needing you, c change account |
| tabs | t new tab |
T: t new, ←/→ previous/next, e rename, x close |
| panes | p find a pane |
P: p find, n new, →/↓ split, e rename, r resize with the arrows, f floating, x close |
| view | v copy mode: scroll and select |
V: v copy mode, g search the output, o links and paths, c copy the last command's output, f floating panes, l sidebar, j message log |
| files | f file manager |
F: f file manager, p preview a file, u/d upload/download |
| run | r tools where the pane is |
R: r tools, b runbooks, g git and worktrees |
Inside every menu the same letters do the same things: n new, x close, e rename, arrows
previous and next, and the theme's letter its main action again.
Other keys after ctrl+b |
|
|---|---|
n · x · z |
new pane · close pane · zoom pane |
m |
mark this pane for broadcast |
o |
open a link, a path, a commit on screen (hints) |
g (or /) |
search the output |
arrows · 1…9 |
focus the pane on that side · go to a tab |
space |
search every command by what it does (and see its keys) |
h (or ?) |
help: every key, from your configuration |
d |
detach (programs keep running) |
ctrl+b |
type ctrl+b itself (whatever your prefix is, pressing it twice sends it) |
Without the prefix: alt+arrows move the focus (left/right at the edge go to the next tab),
alt+n opens a new pane, alt+a jumps to the next agent that needs you.
In lists, type to filter, ↑↓ move, Enter chooses, Tab shows what else can be done with the
selected item (connect in a new tab, browse its files, run a command…), Esc closes. Text fields edit
like a shell line (arrows, home/end, alt+←→, ctrl+w, ctrl+u, ctrl+k). Questions show
two buttons, Cancel first: y answers yes, n or Esc no, Enter takes the selected button.
ctrl+b V j lists the messages kraal showed, warnings included.
Used to tmux? [keys] preset = "classic" brings back tmux-style single keys (% " c n p
o s…), still with the menu.
Your own keys. Every command and every menu can move, in ~/.config/kraal/config.toml:
[]
= ["ctrl+b"] # several are fine: ["ctrl+b", "ctrl+space"]
[] # after the prefix; a space separates a sequence ("P right")
= ["y"] # replaces the command's keys
= [] # [] removes them
= ["M"] # a menu moves too: servers, broadcast, workspaces, agents, tabs,
# panes, view, files, run
[] # one key, without the prefix
= ["alt+n"]
kraal config keys prints every key, menu and mode, with the names to use here (--markdown
for a sheet to keep or print); kraal config check reports a key bound twice or a sequence that
hides another; kraal config reload applies the file to the running session (keys, theme,
sidebar, mouse, frames, tools, hints, hooks; new panes take a new shell or scrollback length).
The mouse works too: click to focus panes, tabs, workspaces and agents (a click on the workspace name opens the list); drag borders to resize; drag floating panes by their title; wheel to scroll back; drag to select and copy (double click selects a word, triple click a line); Ctrl+click opens a link (OSC 8 links stay clickable, and plain URLs work too). Hold Shift to use your terminal's own selection.
Keys reach programs unambiguously: kraal speaks the kitty keyboard protocol with terminals that support it (Ghostty, kitty, WezTerm, foot…) and gives each program what it asked for, so Shift+Enter, Ctrl+Enter or Esc arrive as such (Claude Code's Shift+Enter makes a new line), with the keys' alternates and the text they type for programs that want them. Typed text, accents and dead keys included, reaches programs as it was typed.
Shortcuts work on every keyboard layout. Letters are read by the character they type, wherever
the key is. The digit row is read by its place: on AZERTY, the key that types & is 1 after
the prefix, without Shift. On a non-Latin layout, a key stands for the letter at its place on a
US keyboard, so Ctrl+B is still the prefix on a Russian layout. A key the keymap binds by its
character keeps that meaning. Terminals without the protocol send characters only, and shortcuts
read them as such. Key release events are not passed on yet.
Concepts
- A session is one background server (
kraal -s workfor another one). It owns the processes; clients attach and detach. - A session holds workspaces (listed in the sidebar, which shows up once there is more than the one workspace to see: other workspaces, agents, ports, tunnels), each with tabs (named after their focused pane until you rename them), each with panes: tiled, or floating on top.
- A pane is a terminal (a shell, or a command such as
ssh host), or a preview of a file: rendered Markdown, an image, text, or a directory listing. Previews reload when the file changes. - A command pane stays open when its program exits, showing the exit code: press Enter to run
it again (handy when an SSH connection drops) or
xto close it.
Workspace files
Workspaces live in ~/.config/kraal/workspaces/*.kdl. The format is Zellij's layout format, so
existing layouts work unchanged (plugin panes and templates are ignored):
layout {
cwd "~/Dev/api"
tab name="servers" focus=true {
pane split_direction="vertical" {
pane name="db" command="ssh" { args "admin@db.lan"; }
pane name="web" command="ssh" { args "-p" "2222" "admin@web.lan"; }
}
}
tab name="work" {
pane split_direction="vertical" {
pane agent="claude" size="60%" // a shell that starts `claude`
pane preview="README.md" // a live Markdown preview
}
}
}
split_direction="vertical" places children side by side, "horizontal" stacks them. Sizes are
percentages or cells. kraal adds two pane properties: agent="<name>" starts an agent in a shell,
and preview="<path>" shows a file. ctrl+b W s or kraal save <name> writes the current layout
back as a file (<name>.kdl in the definitions directory; the file it replaces is kept as
.kdl.bak). An agent started on an account is saved as agent="claude:pro" with its arguments,
and a command that has not run yet keeps start_suspended=true.
Servers and file transfers
kraal keeps an inventory in ~/.config/kraal/servers.toml:
[[]]
= "db"
= "db.lan"
= "admin"
= 2222 # optional; without it, ~/.ssh/config decides (so 22 is not the same as none)
= "bastion" # optional ProxyJump, by name or user@host; a jump host with its own key works too; "none" turns off one from ~/.ssh/config
= "~/.ssh/db" # optional; more_identities = ["~/.ssh/other"] for keys tried after it
# ("IdentityFile=%d/.ssh/%h" passes ssh's option, tokens and all, not -i)
= ["prod"]
It also knows the ssh panes of your workspace files (grouped by workspace) and the hosts of
~/.ssh/config. When you ssh somewhere new, from the picker or typed in any pane, kraal saves
it in the group discovered.
ctrl+b s opens the same list: Enter connects (or opens a whole group); Tab shows the other
actions: connect in a new tab, browse its files, run a command, forward a port, edit the list,
remove a saved server.
Copying files works in every direction and reuses the SSH connection of open panes:
A move deletes only what rsync copied (a file is compared by content), renames instead on one file system, and refuses when the destination is the source itself, even seen through a link or a server that shares this machine's files. On a server, a move between two file systems is refused: copy, then delete.
ctrl+b f opens a two-panel file manager (Midnight Commander style) on the focused pane's
directory and, for an SSH pane, its server; kraal files ~/Dev db:/var/log from the command line.
Each panel shows this machine or any server (o to choose). space marks, c copies to the
other panel (it asks first when that would replace files there, and what you did not agree to
replace is never replaced, even if it appears during the copy: that copy then waits in a hidden
.kraal-stage-… directory in the destination, and kraal never removes one it cannot tell is over
(a move's, the only copy of what it moved, is named instead); a directory is put in place by a
rename that keeps it whole (owner, permissions, ACLs), which on a server takes GNU coreutils 8.30
or later: elsewhere such a copy says so before it starts), m moves, d deletes, enter
opens a directory or previews a local file, s opens a shell there, / filters. Selected files
carry a ✓. Transfers run in a small window that closes itself when done.
Drop a file from your file manager on an SSH pane and kraal offers to upload it to that server
(ctrl+p pastes the path instead). ctrl+b S d downloads a path from the pane's server
(the selected text is prefilled), into ~/Downloads by default.
Git and worktrees
Panes in a git repository show their branch in their frame (⎇ main*? ↑2: * changes, ?
untracked files, commits ahead).
The agent picker (ctrl+b a) offers to start an agent in a new worktree: kraal creates a branch
kraal/<name> and a directory next to the repository (<repo>.worktrees/<name>), and starts the
agent there in its own tab. Several agents can work on the same repository without stepping on each
other. ctrl+b R g in that tab reviews the changes (your pager, delta included), commits them, opens
a pull request (gh), merges into the base branch or removes the worktree. Removing says first
what would be lost (uncommitted changes, commits the base does not have) and which panes it
closes. Account rules written for the repository also apply in its worktrees.
Fleet tools
In the server picker (ctrl+b s), Tab then "Run a command" runs one on the selected server or
group, and "Forward one of its ports" opens a tunnel. Tunnels come back after a restart and reconnect when the network
drops; the sidebar lists them (click copies localhost:PORT, ctrl+click opens it in the browser).
Ports that programs in your panes announce (http://localhost:5173) and listen on show up in the
sidebar under PORTS; a click opens them.
Tools and contexts
ctrl+b r lists tools and starts one in a floating pane where the focused pane is: on its
server over SSH (the shared connection makes it instant), or here in its directory. From an SSH pane
on web01, htop shows web01's processes; from a local pane in a repository, lazygit opens on
that repository. kraal ships lazygit, k9s, htop, logs (journalctl -f), docker and disk; tools that
run here are hidden when their program is not installed. Add your own in the config:
[]
= "pods of the pane's cluster and namespace"
= "kubectl get pods -o wide"
= "local" # always here, with the pane's KUBECONFIG and AWS_PROFILE
= ["kubernetes"] # only offered where a Kubernetes context is known
= true # keep the output on screen
= "i" # ctrl+b R i starts it directly
[]
= 'git show "$KRAAL_SELECTION"'
= ["git", "selection"]
A tool gets the pane's context in $KRAAL_CWD, $KRAAL_SERVER, $KRAAL_HOST, $KRAAL_REPO,
$KRAAL_BRANCH, $KRAAL_KUBE_CONTEXT, $KRAAL_KUBE_NAMESPACE, $KRAAL_TF_WORKSPACE,
$KRAAL_AWS_PROFILE, $KRAAL_SELECTION and $KRAAL_PRODUCTION. A tool started on a server runs
in the pane's directory there when the remote shell reports it (OSC 7), and fails rather than run
elsewhere if that directory is gone. A tool that fails stays open to
show why. kraal config default documents every field.
Frames show what a pane works on besides its branch: the Terraform workspace of a directory where
terraform init ran (tf:prod, or TF_WORKSPACE), the Kubernetes context and namespace of the
last kubectl, k9s, helm… run in the pane (⎈ prod-eks/payments: its --context, --namespace
and --kubeconfig, else the kubeconfig's current context, following kubectx), and the profile
of the last aws, terraform, cdk… run (--profile, else AWS_PROFILE; nothing for the default
profile). Local tools started from the pane use that same kubeconfig and profile. kraal reads
kubeconfig files and the processes' command lines and environments; it never runs these programs
itself. Commands are seen by looking a few times a second: one that ends in a split second can go
unnoticed until the next.
Production. A pane is production when its server, Kubernetes context or namespace, Terraform
workspace or AWS profile has prod, production or prd as a word of its name (web-prod-1,
prod-eks, db.prod.lan; not products-api), or when its server says env = "prod" in
servers.toml (kraal servers add … --env prod; a server with another env never is). Production
panes get a red frame and a PROD badge, their tab turns red, and kraal asks before:
- broadcast typing reaches a production pane (once per pane and production reason, until the broadcast stops; no stops it);
- several lines are pasted into a production program that would run them as they arrive (a shell in bracketed paste mode shows them first, so it is not asked);
- a tool starts on a production pane, unless the tool says
read_only = true(htop, logs…); - a runbook runs on production targets (its plan says which).
Scripts and agents get no prompt: kraal send and kraal keys refuse a production pane unless
given --production. The [production] section changes the words or turns the questions off.
This guard is a safety net based on names, not a permission system.
Hints
ctrl+b o puts a short label on everything kraal recognises in the focused pane: links, paths
(src/main.rs:42), commits, host:port addresses, IPs and known servers, Kubernetes pods, UUIDs.
The pane holds still meanwhile. Type a label to copy that text; press Tab first to act on it
instead, where the pane is:
| What | Tab, then the label |
|---|---|
| link | open it in your browser |
| path | open it in $EDITOR, at its line |
| commit (or container id) | git show in the pane's repository (or docker logs -f) |
host:port |
open it; a port of the pane's server is reached through a new tunnel |
| IP, server | ssh to it |
| pod | kubectl logs -f with the pane's context and namespace |
| UUID | type it into the pane |
Your own patterns run a tool with the match in $KRAAL_SELECTION:
[]
= 'OPS-\d+'
= "jira" # a [tools.jira] that opens the ticket
Shell commands
A shell can mark where each prompt, command line and output starts, and how each command ended (the OSC 133 marks). kraal then knows the commands run in a pane:
- in copy mode,
pjumps to the prompt above andPto the one below;oselects the output of the command at the cursor,Othe whole command with its prompt; ctrl+b V ccopies the output of the pane's last command (the one running, or the last that ran);- a command that failed shows its exit status in the pane's frame (
✗ 2, with how long it ran when that was 10 seconds or more), until the next one runs; - a command that ran 30 seconds or more and ends in a pane you are not looking at is announced
like an agent that finished, and waits in
alt+a's queue ([notifications] long_command, in seconds; 0 turns it off); - each command that ends is a
command.finishedevent.
A new pane width, or a program that scrolls only part of the screen, moves lines in ways kraal cannot follow: the earlier prompts can no longer be jumped to, but how each command ended stays.
Shells do not send the marks by themselves (fish 4 does). kraal adds a few lines to your shell's startup file, active only in kraal's panes, when you ask:
A startup file that is a link (dotfiles kept in a repository) stays a link: the file it points to changes, and the backup goes next to the link.
Runbooks
A runbook is a procedure kept as a file: yours in ~/.config/kraal/runbooks/, a repository's in
its .kraal/runbooks/ (shared with the code). ctrl+b R b lists both; kraal runbook new <name>
starts one from a template.
Steps (TOML). Steps, parameters, and where it runs:
= "Check the nginx configuration, reload it, check the service"
= "server" # "local", "pane" (where the focused pane is) or "server" (asked)
= 1 # servers at a time (0: all at once)
= true
= [
"sudo nginx -t",
"sudo systemctl reload nginx",
"systemctl is-active nginx",
]
[[]]
= "TICKET"
= "Ticket"
= "$KRAAL_BRANCH" # the focused pane's variables work here
# choices = ["a", "b"] or choices_command = "git tag --sort=-v:refname | head"
kraal asks for the server or group (the focused pane's server first) and the parameters, then
shows the plan: where, how many at a time, the parameters, the steps, and which targets are
production. On yes, a new tab gets one pane per server; the steps run in order, each announced,
rolling servers at a time.
Steps run with set -e (and pipefail where the shell has it): the first command that fails
fails the step, as in a script, and steps share the shell, so a cd carries on. With
stop_on_failure = true (the default), a failure stops the run: the servers still waiting are
marked skipped and never touched. timeout = "10m" stops a server that takes longer. Closing a
pane of the run stops what has not started; servers already running finish. Each run is logged in
~/.local/state/kraal/runs/: who, where, parameters, steps, and everything each server printed
(written as it comes, colours removed, a progress bar in its last state), with the result. If kraal
stops in the middle, the next start says in the log which servers were left unfinished.
Parameters marked secret = true are masked in plans, logs and events, and reach the command
without going through a command line, the saved session or ssh's arguments (on a server, through
ssh's input into a private file the command deletes); what the command itself prints is logged as
printed, so do not echo them.
Documents (Markdown). A Markdown runbook is read like any document. ctrl+b R b opens it next to
the focused pane and lists its shell blocks (```sh, bash, console…); enter sends a
block to that pane and runs it, and ctrl+b R b again offers the next one. kraal refuses while a
program other than the shell runs in that pane (the block would go to it). It knows a block was
sent, not whether it succeeded: watch the pane. Your existing procedure docs become runnable
without being rewritten.
A runbook from a repository runs nothing (not even a choices_command) before you have approved
that version of it; kraal asks again when it changes. On the command line, the run is refused if
the runbook, its targets or its parameters changed between the plan you read and your yes.
Events and hooks
{"v":1,"seq":1201,"boot":1760184000000,"type":"agent.blocked","pane":12,"name":"api","workspace":"dev","agent":"claude",…}
{"v":1,"seq":1202,"boot":1760184000000,"type":"pane.failed","pane":7,"name":"tests","code":1,"command":["cargo","test"],…}
{"v":1,"seq":1203,"boot":1760184000000,"type":"ssh.started","pane":3,"server":"web-prod-1","host":"deploy@web-prod-1","production":true,…}
Types: workspace.opened/closed, pane.opened/exited/failed/closed, agent.working/blocked/done/ idle, ssh.started/ended (an ssh process appeared or ended in a pane, whether or not it got
connected), production.entered/left (entered again when the reason changes),
notification, tunnel.up/down, port.opened/closed, account.limit,
runbook.started/finished, command.finished (a command the shell marks ended: its command
line, exit status and seconds; see Shell commands).
seq grows with each event and boot changes when the server restarts. The server keeps the last
1024 events: a script that was too slow (kraal drops it rather than slow down) or restarted can
come back with --since its last seq; an events.lost line says when some are gone.
[hooks] runs a command when one happens, with the event in $KRAAL_EVENT_JSON and its fields as
$KRAAL_<FIELD>; output goes to ~/.local/state/kraal/hooks.log:
[]
= 'curl -s -d "$KRAAL_AGENT needs you in $KRAAL_NAME" ntfy.sh/my-topic'
= 'notify-send "$KRAAL_NAME failed ($KRAAL_CODE)"'
= 'logger -t kraal "ssh $KRAAL_HOST production=$KRAAL_PRODUCTION"'
At most 20 hooks start per second and 32 run at once; a hook running for 10 minutes is stopped with everything it started; 64 KiB of each hook's output are kept. Hooks and scripts reading the stream run as you, like any script: kraal does not sandbox them.
Remote sessions
Run kraal on a server and attach to it from your machine; the session reconnects by itself when the network drops, and its programs keep running on the server meanwhile:
Ctrl+click on a link in a remote session opens it on your machine. A localhost address there is
the server's, not yours: kraal copies it and says so instead of opening the wrong page.
Images
Programs that draw images in the terminal work in panes, locally or over SSH: the kitty graphics
protocol (kitty's icat, Yazi, timg, chafa…) and sixel (img2sixel, gnuplot, matplotlib
backends…). kraal keeps each image and draws it in the pane like text: it scrolls with the
output, is cut by the pane's frame, hides under a floating pane, and shows on every attached
terminal that can draw it.
- Your terminal must draw kitty graphics over Unicode placeholders: Ghostty and kitty do. kraal asks each terminal when it attaches; on one that cannot (WezTerm and Konsole draw images, but not this way), the image's cells stay blank, and programs in panes are told no images can be shown, as a terminal without them would.
- Programs send the image data itself. Reading an image from a file or shared memory is refused (the program may run on another machine than kraal), and programs then send the data instead.
[graphics] memory_mb(256 by default) bounds the memory all panes keep for images; the oldest go first.enabled = falseturns images off: their sequences then reach the emulator, which ignores them.- Not supported: animations, images under the text (negative z-index), offsets within a cell.
Coding agents
kraal recognises agents by their process (claude, codex, opencode, gemini, aider,
cursor-agent, amp, goose, …) and infers their state from the screen. For exact states,
install the hooks:
The hooks do nothing outside kraal. States are shown as coloured dots, with the state written
next to them in the sidebar and in pane frames: yellow working, red blocked (waiting for you), green
done (not seen yet), ○ idle. A blocked or finished agent you are not
looking at triggers a desktop notification through your terminal (OSC 777/9) or notify-send.
When the server restarts, panes that ran an agent with a known session resume it
(claude --resume <id>, codex resume <id>). A prompt given on the command line is never sent
again, and a one-shot run (claude -p, codex exec) is not resumed. When kraal cannot tell an
option's value from a prompt (an option it does not know), the pane shows the command and waits:
Enter runs it as shown. The same goes for Enter on an ended agent and for an account switch.
Agent accounts
Each account is a separate config directory for an agent (CLAUDE_CONFIG_DIR for Claude Code,
CODEX_HOME for Codex), logged in with the agent's own login. kraal never reads or stores
credentials; it chooses which directory a pane starts with and reads the usage the agents report.
Your existing setup is the default account of each agent. New Claude accounts share
CLAUDE.md, settings.json, skills, plugins, memory and conversations with it, so a conversation
can continue on another account; Codex accounts share nothing, as Codex keeps databases in its home.
Usage. Claude Code passes the 5-hour and weekly usage to its status line;
kraal integration install claude puts a small status line in front of yours that records it,
then runs yours unchanged. Codex accounts are asked through Codex's own app-server every few
minutes (kraal accounts refresh asks now). Limit messages on screen ("You've hit your session
limit") count too.
Rules and thresholds live in ~/.config/kraal/accounts.toml (kraal accounts edit):
[]
= 90 # percent of the 5-hour or weekly window
= "ask" # notify | ask | auto
[[]]
= "pro" # or: path = "~/Dev/client"
= "claude"
= ["pro", "default"] # in order of preference
A new agent starts on the first account of the matching rule that is under its threshold; when
the matching rule names no account that exists any more, no account is used there (rather than
any). The name default is the account in the agent's own home (~/.claude, ~/.codex). When
the account of a running agent reaches its threshold, kraal notifies you (notify), offers to
continue the conversation on another account once the agent finished its turn (ask), or does it
by itself at the end of the turn (auto). auto only switches when the agent's own hooks said
its turn ended (kraal integration install); without them, kraal asks instead, since restarting
an agent mid-turn would lose its work. A Claude conversation continues on the other account; a
Codex one cannot (its history stays in its account's directory), so switching starts a new one.
Workspace files can say agent="claude" (rules apply) or agent="claude:pro".
auto is off by default. Providers set usage limits per subscription; check their terms (for
Anthropic, the Usage Policy's section on bypassing product restrictions) before letting a tool
move your work between subscriptions automatically.
Scripting and agent control
Every pane gets KRAAL_SOCKET, KRAAL_SESSION and KRAAL_PANE, so scripts and agents can drive
kraal from inside: commands act on the session they run in (kraal kill too), and on their own pane
when none is named, unless -s names another session:
Panes are referred to by id, by name, or as workspace:name.
Configuration
~/.config/kraal/config.toml; kraal config default prints every setting with its default, and a
config only needs the keys it changes:
= true
= true
[]
= true
= 28
[]
= "terminal" # terminal, system, both, off
[]
= "kraal" # or "classic" for tmux-style keys
= ["ctrl+b"]
[] # keys after the prefix; a space separates a sequence
= ["%", "P right"]
[] # keys without the prefix
= ["alt+f"]
kraal config check validates the config, every workspace file, accounts.toml and
servers.toml; it fails when one cannot be read. kraal config reload makes the running session
read the config again: keys, theme, sidebar, mouse, pane frames, notifications, tools, hints, hooks,
production words and contexts apply at once; panes already open keep their shell and scrollback
length.
Themes: [theme] takes a preset (default, high-contrast, ascii for terminals or fonts
without box drawing) and optional accent, dim, sync, production colours (names, #rrggbb,
0-255) and a frames style (rounded, plain, thick, double, ascii).
Status
kraal is young. Known gaps:
- Programs' images show only on terminals that draw kitty graphics over Unicode placeholders (Ghostty, kitty); preview panes still draw images with half-block characters.
- Remote sessions need kraal on the server; agents running there are not yet listed in your local sidebar, and the Kubernetes, Terraform and AWS contexts are read only for local panes.
- Agent states come from hooks for Claude Code (and Codex's turn ends); for other agents they are guessed from the screen and can lag or be wrong.
- Only Linux is used day to day. macOS reads processes through the kernel (sysctl, libproc, lsof) and passes the whole test suite on GitHub's macOS machines, but nobody uses it daily yet. Windows is not supported.
Development
License
Copyright 2026 vodhash and the kraal contributors.
Licensed under the Apache License, Version 2.0.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in kraal by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.