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.

Creating a session with 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+R to 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 with Ctrl+V . With no selection, Ctrl+C sends 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+K move the selected session (and its nested children) down/up, and Shift+S sorts 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 a command , 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 / weekly presets 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_runs history) and scriptable headlessly via thurbox-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 (alias todo ): create/list/show/edit/remove/run .
  • Importable from a tracker, without thurbox knowing which one. A task carries a source tag and an external id and url, and task create --source … --external-id … upserts on (source, external_id) — so a scheduled Exec automation 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. Enter on one opens it — no git worktree add runs, so it works even for a branch already checked out
  • Press Ctrl+S to sync all worktree sessions with origin/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 named ssh:<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
~/.config/thurbox/hosts.toml
[[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.lock files 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 #

Forking with 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+K navigation 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.toml
[[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-commit model. A pre_* 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. A post_* 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 , a spawn automation 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. A thurbox-cli call 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_HOST says 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 short inbox token 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 --claim is a single atomic UPDATE … 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, and message reply <id> routes back to a message's original sender without ever mapping a peer to a session id.
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; Esc puts back exactly what you were looking at.
  • Metadata matching is subsequence (typing oc52 finds fix-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 editui/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.

Cycling palettes with the theme picker ( 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 of settings.toml ( see Configuration ): opt into the timing-only Busy → Waiting edge 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+C copies); Ctrl+Click opens 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.

settings.toml
[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.

bash
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 --claim

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

  • sessionlist , get , create (synchronous — the tmux window is live on return; supports --worktree-branch / --base-branch ), send (append --no-enter to 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 , or ctrl-<letter>ctrl-c , ctrl+c and C-c are 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 , capture and exec work on a --host session too: they delegate to that host's own thurbox-cli . A refusal is left only where delegation is genuinely impossible — no hosts.toml entry, or no reachable CLI there — and it says which.
  • session create — beyond --worktree-branch / --base-branch : --command / --arg / --env make any executable a session (its launch recipe is stored on the row, so a restart replays it — there is no agents.toml entry 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" — allow is 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, and replace puts the old session back if the replacement cannot be spawned. --reports-as <agent> records which agent a --command pane 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 --env recorded, plus the THURBOX_* identity its pane carries — the caller's THURBOX_* 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-passthrough additionally makes it this invocation's own.
  • agent launch-args — prints the command , args and env thurbox would launch a registered agent with. Status hooks are arguments — the hooks extension installs them by appending to the agent's args in agents.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 its session signal will 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 list and is told apart there — stopped: true and state: "stopped" on both read verbs, the same key watch publishes — so a liveness check needs no pane probe. send , key and capture refuse a parked session by name.
  • watch — streams the session event log, one event per line ( created , changed , gone , plus present with --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 what working → blocked → working around an auto-answered permission looks like. Every line carries a monotonic seq ( --since <seq> resumes exactly where a driver left off), a reason ( spawned / registered / restored , state / stopped / started / updated , soft_deleted / force_deleted — a soft delete can be restored, a force delete cannot), the from_stateto_state of the transition, and the same state / hook_coverage gating fields session get publishes.
  • runtimestatus / stop for 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 ( --lines defaults to 200, max 10000); --ansi keeps tmux's styling instead of flattening it. Under --json it also reports the pane's live state — cursor_row / cursor_col (0-based, relative to the visible pane), foreground_process and foreground_command (what is running in the pane's tty, as argv0 and as its full command line), and foreground_cwd (where that process is now, unlike session get 's cwd , which is where the session was launched). Each is null when it cannot be determined, never guessed. A session created with --host is captured through that host's own thurbox-cli rather than refused.
  • agent state, and how far to trust it — agents report working / blocked / done / idle through 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. So session get / list --json carry the raw hook_state beside what it takes to judge it — hook_state_at / hook_state_age_secs (when, and how long ago), hook_reported (silence is not idle ), hook_coverage / hook_states_reportable (what this agent can report at all — aider can only ever say blocked ), hook_blocked_is_heuristic , and state / state_source , and stopped . 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 get resolves its foreground process and flags a working session sitting over a bare shell ( hook_corroboration , hook_state_contradicted ), reporting the contradiction rather than overwriting the agent's own word. session list skips that probe unless you pass --verify , and a remote session answers unavailable . A pane whose foreground is an agent thurbox did not launch reports state: "running" with state_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-cli on PATH at 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 , plus tick (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. send enqueues and, by default, wakes the recipient; inbox --claim drains 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 / run the todo list : create with neither --session nor --repo is a plain local todo, and run triggers its Send/Spawn agent action headlessly.
  • extension (alias ext ) — install / uninstall / reinstall / list / available / update / activate / deactivate / status manage the opt-in extensions .
  • editorget / set the command Ctrl+O runs (the worktree path is appended as the final argument).
  • configvalidate strict-parses every config file (exit 1 on a problem — handy in dotfiles CI), and show prints the effective resolved configuration.
  • version — prints the running version; --check queries GitHub's latest release (gated on the version_check feature).
  • update — downloads, verifies, and replaces the installed binaries with the latest release ( --force bypasses the up-to-date / dev-build guards; gated on the auto_update feature).
  • notify — diagnoses OS desktop notifications: prints the detected delivery backend and last error; --test fires 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.