Features
A customizable interface #
There is no interface compiled into the binary. thurbox boots a kernel, reads a directory, and draws whatever Lua it finds there — so every pane, the session list included, is a file you can move, turn off, delete, rewrite, or install from someone else. You do not have to write any of it yourself: ask the agent you already have running. The full tour is The Interface.
Where each surface lives #
| Surface | Kind | Where it lives |
|---|---|---|
| Session list | bundled | 10_sessions.lua |
| Agent terminal | bundled | 20_agent.lua |
| Global search | bundled | 65_search.lua |
| Session creation | bundled | 70_new_session.lua (a float) |
| Restore list | bundled | 80_restore.lua (a float) |
| The arrangement | bundled | ui/layout.lua |
| Themes · settings · help | kernel chrome | Ctrl+Y · Ctrl+, · F1 |
| Code review | installable | thurbox-code-review |
| Info panel | installable | thurbox-info-panel |
| CPU / RAM gauges | installable | thurbox-cli plugin install top |
| Panes anyone else wrote | installable | ui/plugins.toml |
| Tasks | CLI only | thurbox-cli task |
| Automations | CLI only | thurbox-cli automation |
Bundled
means it ships with thurbox and is yours to edit, move or delete — the
arrangement included.
Installable
ones sit in
plugins.toml
on exactly the same terms: the review pane claims its chord by itself, and the info
panel wants one
layout.lua
line.
CLI only
means the records and the commands are all there and nobody has written the pane yet;
examples/panes/tasks
is an example pane over the same task records.
Sessions #
Heads up: The session list display is not yet perfect and will keep improving — what it can show is heavily dependent on the signals each agent CLI exposes.
Ctrl+N
: pick a repo, name it, choose an agent.
Run multiple coding-agent CLIs side-by-side, each in its own tmux pane. A session runs one agent (Claude Code, Codex, Antigravity, opencode, aider, GitHub Copilot CLI, Vibe, pi, Oh My Pi, or your own), each with its own default config. Sessions persist across crashes, restarts, and even multiple concurrent Thurbox instances — tmux keeps them alive in the background.
-
Create a session with
Ctrl+N: repo picker → session name → agent picker. The agent picker is skipped when only one agent is defined. -
Restart a session with
Ctrl+Rto preserve conversation history when the agent supports resume (e.g. Claude's--resume). - Each session row shows the agent's live activity when the CLI reports one (via its terminal title), next to a colored status dot
-
Open URLs in terminal output with
Ctrl+Click -
Select text with mouse drag, copy with
Ctrl+C, paste withCtrl+V. With no selection,Ctrl+Csends SIGINT to the active session. -
Toggle a shell pane alongside the agent session with
Ctrl+T -
Reorder the list by hand: with the session list focused,
Shift+J/Shift+Kmove the selected session (and its nested children) down/up, andShift+Ssorts sessions alphabetically by name within each repo group. Manual order wins — a status change only recolors the dot, never moves a row — and it survives restarts. -
Recover sessions externally at any time with
tmux -L thurbox attach
Agent Definitions #
The set of available agents is data, not code. On first run Thurbox seeds
~/.config/thurbox/agents.toml
with built-ins (claude, codex, antigravity, opencode, aider, copilot, vibe, pi, omp).
Edit the file to tweak an agent or add a new one — no recompile required. See
Built-in agents
for each one's recommended config and behavior.
-
Each
[[agents]]entry has acommand,args(always passed — bake in flags like a model here if you want), and argument-template groups (resume_args,fork_args,new_session_args). -
A group is appended only when its driving value is present, with
{id}substituted. Selection precedence is fork > resume > new-session id. - A session is fully described by its repos and agent. There is no per-session model selection, permissions, prompt, tool, or skill configuration — those belong to the agent CLI itself, which runs with its own default config.
No pane, but they still fire.
Scheduling does not live in the interface: a detached tmux heartbeat keeper runs due
automations whether or not thurbox is open. Author and inspect them with
thurbox-cli automation.
Automations #
Named, scheduled agent runs. An automation fires on a schedule and either sends a prompt to a running session, spawns a fresh session (optionally on a new git worktree) and prompts it, or runs a shell command headlessly with no agent at all — the deterministic scheduled job the issue-tracker extensions are built on.
-
Schedules are one-shot (a relative delay) or recurring cron, with friendly
hourly/daily/weekdays/weeklypresets and an optional timezone. - Automations fire even when the TUI is closed — a detached tmux heartbeat keeper runs them every minute (and keeps the tmux server alive), with opt-in systemd/launchd units for reboot-proof firing. Concurrent firers are de-duplicated by an atomic claim, so nothing double-fires.
-
Everything is persisted in SQLite (
automations+automation_runshistory) and scriptable headlessly viathurbox-cli automation create/list/edit/run/runs/tick.
No pane.
Tasks work through thurbox-cli task. A pane over the same records is one
anybody can write — examples/panes/tasks is an example.
Tasks #
A built-in todo list whose items can be connected to a coding agent with the same Send/Spawn model as automations: Send pastes the task title into an existing session, Spawn creates a fresh session (optionally on a new worktree) seeded with the title, and an unconnected task is a plain local todo. Triggering a task runs its action and advances it to in progress .
- A task carries a title, a markdown description, and a status (☐ todo · ◐ in progress · ☑ done). Triggering one runs its action and advances it to in progress .
-
Persisted in SQLite and driven from
thurbox-cli task(aliastodo):create/list/show/edit/remove/run. -
Importable from a tracker, without thurbox knowing which one.
A task carries a
sourcetag and an external id and url, andtask create --source … --external-id …upserts on(source, external_id)— so a scheduledExecautomation running your own script keeps a tracker and this list in step. Only open-vs-done is authoritative on the way in, so a local in progress is never clobbered. thurbox ships no provider integration: keeping the binary free of provider names is the point (ADR-20).
Git Worktrees #
Optionally spawn sessions inside git worktrees for branch isolation.
-
When creating a session (
Ctrl+N), choose "Worktree" mode to select a base branch and name a new branch - Thurbox creates the worktree and launches the agent inside it
-
Worktrees the repo
already
has appear as
↵child rows under it, each showing its branch.Enteron one opens it — nogit worktree addruns, so it works even for a branch already checked out -
Press
Ctrl+Sto sync all worktree sessions withorigin/main - On rebase conflicts, Thurbox automatically sends a resolution prompt to the agent
- Closing a session removes the worktrees Thurbox created. One it merely opened is left exactly where it was, since removing it would take any uncommitted work in it along too
- Worktree sessions show the branch name in the terminal title and session list
Worktree storage
Worktrees are created at
<repo>/.git/thurbox-worktrees/<sanitized-branch>
, where
/
in branch names is replaced by
-
.
Multi-repo sessions
A session can span several repositories. The repo picker lets you select more than one,
and headless callers pass
--add-repo PATH[@BASE]
(each gets its own isolated worktree on the session's branch) or
--add-dir PATH
(attached as-is, no branch). Because agent CLIs differ wildly in how they accept extra
directories, Thurbox launches a multi-repo session in a per-session
symlink workspace
(
~/.local/share/thurbox/workspaces/<id>/
, one symlink per repo) and starts the agent there, so every agent sees each repo as a
subdirectory — fully agent-neutral. Single-repo sessions are unchanged.
Remote SSH Sessions #
Run an agent on a remote machine over SSH while the TUI stays local. The agent process, its tmux window, and any git worktrees all live on the remote host.
-
Declare hosts in
~/.config/thurbox/hosts.toml(seeded commented-out, so a fresh install has none); each entry registers a selectable backend namedssh:<name> -
The new-session flow (
Ctrl+N) shows a host picker first; remote sessions are marked with a☁glyph in the session list -
Thurbox shells out to your system
ssh, so authentication, keys, and connection multiplexing come from~/.ssh/config— thurbox never handles credentials - Remote sessions get the same persistence, multi-instance sharing, and restore-on-startup as local ones
- The remote host needs tmux ≥ 3.2 and git
[[hosts]]
name = "devbox" # backend "ssh:devbox"; what --host expects
destination = "me@devbox" # ssh target or a ~/.ssh/config alias
ssh_opts = ["-o", "ControlMaster=auto", "-o", "ControlPersist=10m"]
# socket / session — optional remote tmux -L / session-name overrides
# worktrees_dir — optional absolute remote worktrees dir
Spawn remotely from the CLI with
thurbox-cli session create --host devbox …
.
Worktree Sync #
Ctrl+S
synchronizes all worktree sessions with their upstream default branch.
- Sessions are grouped by repository to avoid git lock contention. Different repos sync in parallel.
-
Per-worktree: stash uncommitted changes, fetch, rebase
origin/main, stash pop. -
Stale
.git/index.lockfiles from crashed git processes are automatically cleaned up before syncing. - Runs on background threads — the TUI remains fully responsive.
- Results shown per-session: synced, conflict (sent to Claude for resolution), or error.
Session Forking #
Ctrl+F
: children nest under their parent session.
Press
Ctrl+F
to fork the active session. This creates a new session that resumes from the same
conversation history, allowing you to explore alternative approaches without losing the
original session's context. The fork records the source session as its
parent
: the session list nests children under their parent with a tree prefix, and the info
panel shows a
Parent:
row.
Parent Sessions (Lead / Worker) #
Every session carries an optional
parent
, so orchestration scripts can model a
lead
session that spawns
workers
. A
Ctrl+F
fork records its source as the parent automatically; headless,
thurbox-cli session create --parent <uuid>
sets it explicitly, and
session list --parent <uuid>
lists a lead's direct children.
-
In the session list, children nest under their parent
within the same repo group
(muted
└tree prefix; a child whose parent lives in another group keeps its place with a↳mark instead).Ctrl+J/Ctrl+Knavigation walks the tree exactly as rendered. - The link is informational only — deleting a lead never deletes or orphans its workers (they routinely outlive the lead). A dangling parent just renders the child as a top-level session again.
Session Lifecycle Hooks #
Run your own command at the moments thurbox creates, deletes, restarts or restores a
session — copy an
.env
into a fresh worktree, warm the dependencies before the agent boots, post to a
channel when a session lands, refuse a session on a branch nobody should touch, clean
a scratch directory when one is deleted. Hooks are declared as data in
~/.config/thurbox/hooks.toml
, one
[[hooks]]
entry per event and command, seeded commented-out.
[[hooks]]
event = "session.post_create"
command = 'npm ci --prefer-offline' # runs in the new worktree's repository; $THURBOX_CWD is the worktree
timeout_secs = 300-
Eight events
, pre and post for each operation:
session.pre_create/post_create,pre_delete/post_delete,pre_restart/post_restart,pre_restore/post_restore. A fork is a create; an undo is a restore. -
Pre hooks veto, post hooks inform
— git's
pre-commitmodel. Apre_*hook that exits non-zero (or hangs past its timeout, default 30 s) aborts the operation before its first side effect, with its stderr as the reason shown in the TUI or returned by the CLI. Apost_*hook fires only after full success; every one runs, and a failure is logged and reported but cannot undo what happened. -
Once per operation, whichever interface asked.
The creation flow,
Ctrl+F,Ctrl+D,Ctrl+R, undo,thurbox-cli, aspawnautomation and an extension's sessions all end in the same four pipelines, so a hook fires exactly once for each of them and never on the render thread. -
The session's facts, two ways.
THURBOX_*environment variables for a one-liner (THURBOX_HOOK_EVENT,THURBOX_SESSION,THURBOX_SESSION_NAME,THURBOX_AGENT,THURBOX_REPO,THURBOX_CWD,THURBOX_BRANCH,THURBOX_HOST, …) and one JSON object on stdin for anything structured. Athurbox-clicall inside a hook hits the same database as the thurbox that fired it. -
Runs in the session's primary repository with
no terminal
(output is captured, only its tail reported), and
locally
even for a remote session —
THURBOX_HOSTsays where the session actually is.
This is the reverse of the built-in agent hooks extension, which installs status hooks into the agent CLIs so they can report to thurbox. Full reference: configuration → hooks.toml .
Inter-Session Messages #
A general, agent-neutral
message queue
lets one session hand another a structured payload — addressed to a session, with
a free-form
--kind
tag, a
--body
, and optional sender/task provenance — instead of scraping its rendered
terminal. It is the channel extensions use for agent↔agent coordination —
an orchestration lead collecting its workers' questions, plans and results is the shape
it was built for.
-
Push, not poll.
A worker pushes a clean payload with
thurbox-cli message send; a wake nudge types a shortinboxtoken into the recipient's pane so it drains immediately. The payload always travels through the durable database, never the pane. -
Exactly-once drain.
message inbox --claimis a single atomicUPDATE … RETURNING, so the TUI, a cron tick, and a wake nudge can drain the same inbox concurrently without ever double-processing or dropping a message. -
Self-knowable identity.
Thurbox injects a stable
THURBOX_SESSION(and, for task-spawned sessions,THURBOX_TASK) into each agent, so a CLI call inside a session proves its own identity: an agent sends and reads its own mail passing no ids, andmessage reply <id>routes back to a message's original sender without ever mapping a peer to a session id.
Global Search #
Ctrl+/
— matches highlight in the pane rather than being reprinted in the strip.
Press
Ctrl+/
to open a non-modal search strip across the top of the interface. A result carries the
pane it belongs to, so
search grows a scope whenever a pane returns
— sessions is the scope that has a pane today.
- Sessions match on name, agent, worktree branch and repository and on the text on the agent's screen — the half that finds a session by the error in it.
-
Matches highlight
live in the pane itself
— matching rows accented, the rest dimmed — rather than being reprinted in
the strip. Moving the selection previews it in the list;
Escputs back exactly what you were looking at. -
Metadata matching is subsequence (typing
oc52findsfix-osc52); screen text is matched as a substring, because fuzzy matching against a whole terminal screen matches everything. A session whose metadata already matched is not scanned twice. - Nobody pays for it while it is closed. Reading every agent's screen every frame would be a permanent cost for a feature in occasional use, so the strip leaves its query where the kernel can see it and the screen text is served only while it is actually asking.
Responsive layout #
The arrangement adapts to the terminal size, and
the thresholds are a file you can edit
—
ui/layout.lua
, not a breakpoint compiled into the binary. It ships arranging two panes between three
chrome bands:
| Terminal size | Arrangement |
|---|---|
| < 80 cols | Centre pane only — the session column is dropped |
| >= 80 cols | Session column (25%, min 20 cols) + centre pane |
| < 20 rows | The header band is dropped to keep the panes usable |
The centre pane is never dropped — it takes whatever the session column leaves. Everything else is yours: change a percentage, reorder the bands, or delete a line to drop one. The function only arranges , so it never calls a plugin and a mistake in it cannot break one. See the interface for how it is resolved before any plugin draws.
Themes #
Thirty-six built-in palettes (twenty-eight dark, eight light) switch live with
Ctrl+Y
(or
F4
) and persist across restarts.
Ctrl+Y
).
OS Notifications #
When a session crosses into a
needs-you
state — the agent rang the terminal bell or emitted an OSC 9 / OSC 777
notification (usually because it is waiting on an answer or has finished a task)
— Thurbox fires an OS desktop notification, so you can react without watching
the TUI. The notification body is the agent's last OSC message, or
Waiting for input
otherwise.
- Linux dispatches over dbus and supports click-to-focus : clicking the banner switches the TUI to that session on its next tick. macOS shows the banner but ignores clicks (the modern notification API requires a signed app bundle, which Thurbox is not).
- It fires only while the TUI is open — the terminal parser that watches for the bell does not run headless — and is deduplicated per session, with the session you are currently viewing skipped by default.
-
Tune it in the
[notifications]block ofsettings.toml( see Configuration ): opt into the timing-onlyBusy → Waitingedge for agents that never ring a bell, toggle the sound, or change the dedup floor. Turn the whole feature off with[features] notifications = false.
Mouse Navigation #
The whole TUI is clickable (on by default; disable with the
mouse
feature flag below):
- Click a row in any pane to select it and focus that pane. A plugin gets the hit for free: it returns the rects it drew, and the kernel matches the click against them.
- Click a picker row (theme, agent, host, branch, …) to select and confirm in one click. Open modals swallow stray clicks, so a misclick never discards typed input.
- Hover underlines the row under the pointer; the mouse wheel scrolls the focused terminal and steps the selection while a modal is open (with a draggable scrollbar on long lists).
-
Drag
selects terminal text (
Ctrl+Ccopies);Ctrl+Clickopens a URL.
Feature Flags #
Behaviour that reaches outside the interface is switched in
~/.config/thurbox/settings.toml
under
[features]
(seeded commented-out, so the defaults apply when absent). Every flag defaults to
true
— including
version_check
and
auto_update
, which were opt-in before 1.0 and reach the network, so they are the two most worth
knowing you can turn off.
A pane is no longer switched off here.
Panes are files, so turning one off is
space
on its row in the Interface tab — a decision recorded in
ui.json
beside your rebindings. That is strictly more than a flag could do: it works for a
plugin you wrote, which no compiled-in flag could ever have known about.
[features]
shell_pane = true # Ctrl+T per-session shell
soft_delete = true # Ctrl+D soft-deletes (Ctrl+Z undo); false = hard-delete behind a confirm
automations = true # schedule firing + arming the tmux heartbeat
mouse = true # mouse capture: clicks, wheel, drag-select, hover
notifications = true # OS desktop alerts when a session needs attention
perf_hud = true # F12 live counters
version_check = true # GitHub update check (makes a network call)
auto_update = true # download+verify+replace the binaries on startup (never across a major)
automations = false
is the flag with teeth beyond the interface: it also stops due schedules from firing and
stops the tmux heartbeat being armed at startup (explicit
thurbox-cli automation
commands still work).
Five keys are accepted and ignored.
tasks, file_viewer, info_panel,
code_review and global_search gated panes the binary does
not draw, and nothing reads them — a pane is turned on and off from the
Interface tab instead. They are still parsed rather than rejected, so an existing
settings.toml keeps loading instead of failing on an unknown key —
but setting one has no effect, in either direction.
You don't have to hand-edit the file: press
Ctrl+,
(or
F6
) to open the
Settings panel
— a modal that views and edits all of
settings.toml
(the
[features]
toggles, the
[notifications]
knobs, and the scalars). Edits apply on
Ctrl+S
(
Esc
discards); the file is also live-reloaded, so feature flags and notification knobs take
effect immediately.
Session Persistence #
Sessions run inside a dedicated tmux server (
tmux -L thurbox
) and survive crashes, restarts, and multiple concurrent instances.
How it works
- Sessions spawn as tmux windows. The tmux pane keeps running regardless of Thurbox's lifecycle.
-
On shutdown (
Ctrl+Q), session metadata is saved to SQLite. Thurbox detaches without killing sessions. - On restart, Thurbox discovers existing sessions from tmux, matches them to persisted metadata, and adopts them with terminal content intact.
-
External recovery is always possible via
tmux -L thurbox attach.
Close vs Quit
| Action | Behavior |
|---|---|
Ctrl+Q
(Quit)
|
Detaches all sessions, saves metadata. Sessions resume on next launch. |
Ctrl+D
(Close)
|
Permanently kills the tmux pane. Worktree (if any) is removed immediately. |
Headless CLI #
The
thurbox-cli
binary drives the same sessions, automations, and editor settings without the TUI
— ideal for scripting. It shares the TUI's SQLite database and
tmux -L thurbox
server, so changes made by either show up live in the other. Output is
human-readable in a terminal
and TOON — a compact, agent-oriented encoding — when stdout
is piped; force a format with
--json
(compact, every field, the shape scripts parse),
--pretty
(indented JSON),
--toon
or
--text
(human even when piped). Run it with no subcommand and it prints live state — every
session with the state its hooks last reported — rather than a usage dump.
thurbox-cli session list
thurbox-cli session create --name reviewer --repo-path /path/to/repo --agent codex
thurbox-cli session send <uuid> "run the test suite"
thurbox-cli session send <uuid> "steer it here" --no-enter # typed, not submitted
thurbox-cli session key <uuid> enter # ...and now submit it
thurbox-cli session key <uuid> escape # interrupt the turn
thurbox-cli session capture <uuid> --lines 500
thurbox-cli session capture <uuid> --ansi --json
thurbox-cli session get reviewer # a name works anywhere an id does
thurbox-cli session create --name build --repo-path . \
--command npm --arg run --arg watch --env NODE_ENV=dev # any command is a session
thurbox-cli session stop reviewer # park it: pane gone, work kept
thurbox-cli session start reviewer # and put it back
thurbox-cli session exec reviewer -- git status --porcelain # run in its dir, not in its pane
thurbox-cli session meta set reviewer fm.task_id T-1043 # your own key/value space
thurbox-cli agent launch-args claude # what to run so its hooks report
thurbox-cli watch --json --initial # one event per line, no polling
thurbox-cli message send --to lead --kind questions --task 5 --body "scope?"
thurbox-cli message inbox --for lead --claimEvery session verb takes a name, a UUID, or a unique id prefix. They resolve in that order, and a reference matching several sessions is refused with every candidate named rather than resolved by whichever sorted first — so a driver never has to keep its own name-to-id map.
-
session
—
list,get,create(synchronous — the tmux window is live on return; supports--worktree-branch/--base-branch),send(append--no-enterto type the text and leave it unsubmitted — the text goes out as one bracketed paste, so it arrives literally),key(one named special key:enter,escape,tab,backspace,space, the arrows,home/end,page-up/page-down,delete, orctrl-<letter>—ctrl-c,ctrl+candC-care the same key, and a name thurbox does not know is refused rather than typed into the pane),capture,doctor,restart,delete,restore,stop/start(park a session: the pane goes, the row, checkout and conversation stay — the verb between "leave it running" and "delete it"),fork,exec,meta.send,key,captureandexecwork on a--hostsession too: they delegate to that host's ownthurbox-cli. A refusal is left only where delegation is genuinely impossible — nohosts.tomlentry, or no reachable CLI there — and it says which. -
session create
— beyond
--worktree-branch/--base-branch:--command/--arg/--envmake any executable a session (its launch recipe is stored on the row, so a restart replays it — there is noagents.tomlentry to re-resolve);--resume <id>adopts a conversation that began elsewhere; and--on-existing <allow|adopt|replace|fail>answers "a session of this name already exists" —allowis the default because a database mirroring a shareable host carries that host's rows beside its own, so Thurbox cannot make names unique; uniqueness is something a caller asks for. The question is asked of the backend the creation lands on, so a namesake on another host is never adopted or torn down, andreplaceputs the old session back if the replacement cannot be spawned.--reports-as <agent>records which agent a--commandpane actually runs, so hook coverage is read against it. -
session exec
— runs a command in the session's directory, on the machine
it lives on, and under the session's own environment (whatever
session create --envrecorded, plus theTHURBOX_*identity its pane carries — the caller'sTHURBOX_*is scrubbed, so a driver inside one session cannot lend the child that session's identity). Deliberately not typed into the pane: the pane belongs to the agent, and borrowing it would interleave with whatever it is doing and leave the answer in its scrollback. The command's exit code is always in the output;--exit-passthroughadditionally makes it this invocation's own. -
agent launch-args
— prints the
command,argsandenvthurbox would launch a registered agent with. Status hooks are arguments — the hooks extension installs them by appending to the agent'sargsinagents.toml— so an agent a driver launches itself has none, and its session reports no state. Pass these through and the hooks are there;--session <ref>resolves it for one session, pinning the conversation id and carrying the identity itssession signalwill report under. -
session stop / start
— park a session and put it back: the pane goes, the row,
the checkout and the conversation stay. A parked session stays in
session listand is told apart there —stopped: trueandstate: "stopped"on both read verbs, the same keywatchpublishes — so a liveness check needs no pane probe.send,keyandcapturerefuse a parked session by name. -
watch
— streams the session event log, one event per line (
created,changed,gone, pluspresentwith--initial), so nothing driving Thurbox has to poll. It works with no TUI running, because everything worth waking on is already in the database. Each writer appends its own event in the same transaction as the change, so two transitions in the same instant are two events rather than one — which is whatworking → blocked → workingaround an auto-answered permission looks like. Every line carries a monotonicseq(--since <seq>resumes exactly where a driver left off), areason(spawned/registered/restored,state/stopped/started/updated,soft_deleted/force_deleted— a soft delete can be restored, a force delete cannot), thefrom_state→to_stateof the transition, and the samestate/hook_coveragegating fieldssession getpublishes. -
runtime
—
status/stopfor what Thurbox runs that is not a session: the automation heartbeat keeper, a detached window created implicitly by anything that arms an automation, which no session listing showed and no delete reclaimed. -
session capture
— dumps the rendered pane as text (
--linesdefaults to 200, max 10000);--ansikeeps tmux's styling instead of flattening it. Under--jsonit also reports the pane's live state —cursor_row/cursor_col(0-based, relative to the visible pane),foreground_processandforeground_command(what is running in the pane's tty, as argv0 and as its full command line), andforeground_cwd(where that process is now, unlikesession get'scwd, which is where the session was launched). Each isnullwhen it cannot be determined, never guessed. A session created with--hostis captured through that host's ownthurbox-clirather than refused. -
agent state, and how far to trust it
— agents report
working/blocked/done/idlethrough their lifecycle hooks, and that report is latched : whatever was written last, by an agent that may since have crashed, been interrupted, or never have been wired at all. Sosession get/list --jsoncarry the rawhook_statebeside what it takes to judge it —hook_state_at/hook_state_age_secs(when, and how long ago),hook_reported(silence is notidle),hook_coverage/hook_states_reportable(what this agent can report at all —aidercan only ever sayblocked),hook_blocked_is_heuristic, andstate/state_source, andstopped. There is deliberately no staleness timeout: a turn may run for an hour, so a guessed bound would report live work as finished. The decisive check is the pane instead —session getresolves its foreground process and flags aworkingsession sitting over a bare shell (hook_corroboration,hook_state_contradicted), reporting the contradiction rather than overwriting the agent's own word.session listskips that probe unless you pass--verify, and a remote session answersunavailable. A pane whose foreground is an agent thurbox did not launch reportsstate: "running"withstate_source: "process"— coarser than a hook by design, but not silence. -
session doctor
— answers “are this session's hooks actually
working?”: whether the built-in hooks extension is active,
what this agent's hooks can report, whether its payload is really
on disk where the agent reads it, whether a hook command could
resolve
thurbox-clionPATHat all, what was last reported and when, and whether the pane agrees. Every shipped hook command ends in|| true, so a signal that never lands is otherwise indistinguishable from an agent that has not signalled. Exits non-zero when no state can reach thurbox from a session. -
automation
(alias
auto) —create/list/show/edit/remove/run/runs, plustick(the headless entry point that fires all due automations — what the tmux heartbeat keeper and any systemd/cron timer call). -
message
(alias
msg) —send/inbox/prune: a general, agent-neutral inter-session message queue so one session hands another a structured payload (a free-form--kind, a body, optional task/sender provenance) instead of scraping its terminal.sendenqueues and, by default, wakes the recipient;inbox --claimdrains it exactly once. An orchestration lead collecting its workers' answers is the shape it was built for. -
task
(alias
todo) —create/list/show/edit/remove/runthe todo list :createwith neither--sessionnor--repois a plain local todo, andruntriggers its Send/Spawn agent action headlessly. -
extension
(alias
ext) —install/uninstall/reinstall/list/available/update/activate/deactivate/statusmanage the opt-in extensions . -
editor
—
get/setthe commandCtrl+Oruns (the worktree path is appended as the final argument). -
config
—
validatestrict-parses every config file (exit1on a problem — handy in dotfiles CI), andshowprints the effective resolved configuration. -
version
— prints the running version;
--checkqueries GitHub's latest release (gated on theversion_checkfeature). -
update
— downloads, verifies, and replaces the installed binaries with the latest
release (
--forcebypasses the up-to-date / dev-build guards; gated on theauto_updatefeature). -
notify
— diagnoses OS desktop notifications: prints the detected delivery backend and
last error;
--testfires a sample.
How session delete is handled
session delete <uuid>
is a
soft-delete
by default: it only marks the database row deleted. Its tmux windows — the agent's
and the companion shell's — come down once the 10-second undo window closes: a
running TUI does it, the
automation tick
heartbeat does it with no TUI open, and
session reap <session>
does it on demand. The worktrees stay, which is what makes
session restore <uuid>
— or
Ctrl+Z
in the interface, which undoes the delete you just made — lossless.
Pass
--force
to tear down the session's runtime resources in the same call — for headless
cleanup when no TUI is running to observe the deletion. A forced delete also:
- kills the tmux window,
- removes the session's git worktrees (the underlying repos are left intact),
- removes the multi-repo symlink workspace, if any (only the symlinks — never the linked repos), and
- disables any send automations that target the session.
Teardown is
best-effort
: individual tmux/worktree failures are recorded in the JSON report (
killed_window
,
removed_worktrees
,
worktree_errors
,
disabled_automations
) but never abort the delete. The DB row is always marked deleted last, in one write, so even a forced
delete stays restorable — it just re-spawns from a clean slate.