Usage¶
aegis opens a full-screen TUI. Type in the input box and press
Enter to send. Each tab is an independent agent session.
Keys¶
| Key | Action |
|---|---|
Enter |
Send the input |
Ctrl+T |
New tab with the default agent profile |
Ctrl+N |
New tab — pick an agent profile from a modal |
Ctrl+W |
Close the active tab (closing the last quits) |
Ctrl+1..Ctrl+9 |
Jump directly to tab N |
Ctrl+Tab / Ctrl+→ / Ctrl+← |
Next / previous tab |
Ctrl+Shift+→ / Ctrl+Shift+← |
Move the active tab one slot along the bar (Alt+Shift+←→ alias) |
Drag a tab |
Reorder with the mouse — the tab follows the pointer across its neighbours |
Ctrl+D |
Open / close the queue dashboard |
F3 |
Open / close the dashboard sidebar — every tab at once, since it's a reading mode, not a per-tab widget (/tasks does the same) |
Ctrl+R |
Session history — reopen a prior session (jump / resume / fresh) |
Ctrl+O |
Fuzzy file picker |
Escape |
Interrupt the active turn (or dismiss the dashboard / agent picker) |
Click on a block |
Copy that message / tool result to clipboard |
Ctrl+Q |
Quit |
Input prefixes: ! shell and / commands¶
Two leading characters route the input line to aegis itself instead of the agent. The input outline and text change colour so you can tell at a glance what a line will do: green message · magenta shell · blue command.
! — shell escape¶
!<command> runs <command> in a local shell (in the session's project
root) and injects $ <command> plus its combined stdout/stderr into the
conversation as your next message — so the agent sees the result. The input
turns magenta while the line starts with !.
Output is capped, merges stderr, notes a non-zero exit, and times out after
60s. A bare ! is a no-op.
/ — slash commands¶
/<command> is a control command aegis executes directly — it never
reaches the agent. The result renders as a /-glyph block in the transcript
(red when it fails). The input turns bright blue while the line starts
with /.
| Command | What it does |
|---|---|
/help |
List the available slash commands |
/sessions |
List live agent tabs (handle · agent · state) |
/agents |
List configured agent profiles (name · harness · model · permission) |
/spawn <agent> [prompt] |
Start a new top-level agent, from where you're standing |
/queue new <name> [agent] |
Create a queue |
/enqueue <queue> <payload> |
Drop a task on a queue |
/fork [prompt] |
Branch this conversation into a new tab |
/btw <question> |
Answer a side question from the recent window |
/peer <handle> [--cc] <question> |
Ask an idle peer (@handle … is sugar) |
Precedence: ! shell escape > / slash command > a plain message to the
agent. An unknown command shows an error block pointing at /help.
Asking without spending the conversation¶
Four commands sit on the same idea — work you want done near this conversation without paying for it in context, money, or the turn you are in the middle of. What separates them is how much of where you are standing travels with the question, and what it costs to send it.
/fork [prompt]branches this pane into a new tab: a worker that already knows everything you have said. Refused mid-turn (a live turn's tail is atool_usewith no matching result, and a fork would inherit the dangling call), and the parent is left byte-identical. It carries the whole conversation, and costs about a dollar./btw <question>answers from this pane's recent window and disappears. It never touches the harness session — it reads aegis's own log and makes one independent call — which is why it is legal mid-turn. The answer goes into the pane's scrollback and is never written to the session log, so side notes do not compound: the fifth/btwstill sees real conversation rather than four of its own prior musings.-
@handle <question>asks an idle peer, sending it a cheap slice of where you are standing. The answer lands as a real turn in their transcript and a transient block in yours; your own agent neither sees it nor pays for it. Idle-only is the domain, not a limitation — the point is extracting value from a warm context that is currently producing nothing, and it is legal while your own tab is mid-turn, which is exactly when you have dead time to spend. Add--cc(at send time — the peer never decides this) to also land the answer in your own conversation. -
/spawn <agent> <prompt>is the fourth, and the only one that starts a cold agent — no inherited conversation, no inherited bill. With a prompt it now carries the same cheap slice@peersends: where it was spawned from, a bounded tail of this pane, andaegis_read_peerto pull the rest. That is what makes/spawn opus please verify this testa sentence — the new agent can see which test, and go read the conversation when the tail is not enough. Unlike@peer, it is told to go do the work and hand the result back, which is what you were paying a new agent for. With no prompt, or from a pane with nothing in it yet, nothing is attached.
/btw and /peer are deferred: they mount a placeholder that echoes
your question and answer in place, so nothing freezes while they run, and
Esc cancels the note before it clears a half-typed line or interrupts the
turn.
Tabs¶
Each tab is an independent agent session with:
- A generated alliterating handle (
adjective-laureate—lucid-knuth,wry-hopper,brisk-blum). Handles maximize variety within a session: no laureate is reused until the pool is exhausted, no adjective is reused until its pool is exhausted, and initial letters cycle so one letter never dominates. - A state dot: green idle, amber working, red error.
- A sticky
*when a backgrounded tab finishes — plus a terminal bell — so you notice background work completing. - A scrolling tab bar that keeps the active tab in view, and that you
can reorder —
Ctrl+Shift+←/→moves the active tab one slot, or drag a tab with the mouse. The order is saved with the roster, so it survives a restart.
Session titles¶
A handle tells you which session; a title tells you what it is about.
Ten tabs reading lucid-knuth, deep-dijkstra, wry-hopper are fast to
switch between and impossible to choose between, so a session also carries
a short title — beside the handle, never instead of it.
That distinction is the whole design. The handle is identity: it is
from_handle on every MCP call, the inbox routing key, and half the
immutable log id. The title is only a label, so setting one moves nothing
and breaks no routing.
You get one three ways:
-
Automatically. When a session's first turn finishes, aegis makes one cheap call to summarize what you asked for, and titles the session with it. Once per session, not per turn — a label that churns is worse than one that doesn't. Point
text_generation:at a small profile in.aegis.yamlto keep this off your main model's bill: -
By hand.
/title fix the eviction race. Bare/titleregenerates from where the conversation is now — worth doing when a thread has wandered eight turns from what it opened with./title --cleardrops it. - By the agent. An agent can name itself with
aegis_title, or set a name and a title together withaegis_rename(new_handle, title=…).
Writes are ordered human > agent > auto. What you typed wins; an
agent cannot overwrite it, and the refusal says so rather than failing
quietly. That ordering is also the entire concurrency story — a title that
loses simply never lands, so a slow auto-generation arriving after you
typed one is discarded on arrival.
Titles are stored in the transcript, so they survive a restart, and
Ctrl+R shows and filters on them — which is where they earn the most,
since a history row has a whole line to spend. The active session's title
also sits in the status line.
Generation is best-effort by contract: if the model is having a bad day the session simply stays untitled. It can never disturb a turn.
The transcript¶
Each agent message, tool call, and tool result is a separate block. Hover any block to see a tooltip; click to copy that block verbatim to your clipboard — useful for grabbing tool outputs, error messages, or generated code snippets.
A tool-call block clicks to expand its full arguments instead. On a
Read, Write, or Edit block, Ctrl+click opens the file that
call touched in a FileTab — a Read with an offset and an Edit
both land on their line (the edit's line is found by looking for the
text it replaced; if the file has moved on too far, the file still
opens, at the top). Ctrl+click on a backtick-wrapped filename in
prose does the same thing for whatever the agent named.
While an agent is working, an inline spinner + rotating verb + elapsed timer appears at the bottom of the transcript:
The verb rotates every few seconds (Thinking → Pondering → Crystallizing → Synthesizing → …) so you can see the agent is still alive even when it's silent.
The aegis layer in the transcript (aegis comms)¶
When an agent calls into the MCP plane, the call reads as what it is — an act aimed at a counterpart, in the layer's own colour:
⇄ weary-turing · "the render is yours"
⇉ general · task#01K2CA0F
⊙ exclusive · src/aegis/mcp/ · 3 paths
∘ claims
Nineteen glyphs name semantic acts, not tools, so spawn, fork and
group_spawn share one mark: what matters when you scan a transcript is
that a new agent appeared, not which verb produced it. Everything that
merely reads the room — list_sessions, claims, every config_* — takes
one pale ∘, because polling is the single largest slice of aegis traffic
and giving it the weight of a conversation was the thing worth fixing.
Every one of those calls also leaves an envelope on disk: a daily JSONL
under .aegis/state/comms/, one record per call, carrying who called, the
typed counterpart, the family, the verb, a thread id, the outcome and the
duration. Read it back with:
aegis comms list # every call, oldest first
aegis comms list --handle weary-turing # either end of the call
aegis comms list --thread 01K2CA0F # one conversation across agents
aegis comms list --family conversation # conversation | coordination
# | introspection | admin
aegis comms list --since 2026-08-11T09:00Z
2026-08-11T15:22:04.881Z aegis-release-cut handoff agent:weary-turing 01K2C9T1
2026-08-11T15:22:41.019Z aegis-release-cut enqueue queue:general 01K2CA0F
2026-08-11T15:31:12.402Z (unattributed) claims - 01K2CB33
thread adopts the substrate's own ids (task_id, monitor_id,
claim_id, workflow_run_id, …), so an enqueue and the callback that
lands in another agent's inbox twenty minutes later share one — which is
how you follow a delegation across two agents that never mention each
other.
from is best effort, and the ledger says so. The MCP server is
co-resident and shared, so there is no transport identity: a handle is a
parameter the agent passes by convention, and the tools that take none
(aegis_list_sessions, aegis_claims, aegis_meta, every config_*)
cannot be attributed at all. Those rows print (unattributed) rather than
a guess, because a fabricated attribution in an audit record is worse than
an honest gap.
Status line & metrics¶
handle ·profile· model · permission state ctx <n> (<p>%) · ✂<n> · ↑<input> (<n>% cached) ↓<output> · ⚒ <tools> · <turn> / <session>
↑is the true input the model ingests — uncached input plus cache creation plus cache read. On a typical Claude session this is often >90% cached.<n>% cachedis the fraction of↑that came from cache (not re-billed at full rate).↓is total output tokens this session.⚒is the count of tool calls this session.ctxis how full the model's context window is — the largest single sub-turn of the most recent turn against the window for that model. It turns yellow past 50% and red past 75%. Note what it is not: the session's accumulated input. An agentic turn is many round trips, and charging the gauge for all of them made a 30-sub-turn turn read 1052%.✂counts compactions — how many times the harness has dropped older context to keep going. It appears only once one has happened, yellow at one and red at two or more, and each one re-baselinesctxto the post-compaction size. Claude Code reports these exactly (acompact_boundaryevent); ACP harnesses have no equivalent signal, so there the counter simply stays absent rather than guessing.<turn>is the wall-clock time of the most recent turn;<session>is the total wall-clock since this tab opened.
Numbers are provisional (~ prefix) while a turn is streaming and
exact at turn end.
Usage analytics (aegis usage)¶
Where the status line reports this session, aegis usage aggregates
every session log under .aegis/state/sessions/ into a cost and
usage dashboard. It is read-only and recomputes on each run. The same
dashboard is available inside a running session as the /usage slash
command (/usage, /usage tools, /usage sessions, /usage month|dow|hour),
rendered as a transcript block — identical data in the TUI and web client.
aegis usage # dashboard: cost, averages, models, tools, top sessions
aegis usage --by month|dow|hour # turns bucketed over time (local timezone)
aegis usage --sessions # cost-per-session distribution + top 15
aegis usage --tools # per-tool average turn cost vs. baseline
aegis usage --since 2026-07-01 # only sessions active on/after a date
aegis usage --session <handle> # a single session
aegis usage --model <id> # filter to one model
aegis usage --tz <IANA> # override the local timezone for bucketing
Two-layer cost model. The headline billed figure is authoritative
— the segment-aware sum of each turn's cost_usd (claude-code's own
total_cost_usd, which is cumulative-with-resets across resumes). Beneath
it, an analytical split prices the token counts against the model
registry to separate generation (input + cache-write + output) from
context replay (cache reads). Replay is a genuine billed cost but is
context re-read, not new work — so it is shown apart from generation
rather than folded into the headline. On long Claude sessions replay is
often the majority of the token-priced total.
Sessions with no recorded cost_usd (older logs) fall back to a
token-priced estimate, flagged ~est. The model per session comes from
each log's SystemInit.model; sessions predating that field are attributed
to the .aegis.yaml default_agent's model.
Themes¶
The default Ink theme is calm near-black with one amber accent. Themes are a Textual-native registry; more are drop-in additions.
Interrupting¶
Press Escape to interrupt the active turn. The harness is notified;
the agent stops at the next safe point (after the in-flight tool call,
typically within a second). The TUI returns to idle and you can send
again.
If a modal screen is on top (the queue dashboard, the agent picker),
Escape dismisses the modal instead — the interrupt path only fires
on the default screen.
The task list¶
When an agent is working through a plan, a one-line strip above the status bar shows a circle per task, how many are done, and what it is on right now with a running clock:
The clock is working time, not wall clock — it accrues only while the session is mid-turn, so a task left in progress overnight reports the minute of work it actually got rather than nine hours of idling. The in-progress circle spins on exactly that condition, so the rotation is a literal rendering of the clock running.
Press F3 (or type /tasks) for the dock beside the transcript:
one row per task with its working time, and any subagent's plan nested
underneath. It is a mode, and the mode is app-wide — every tab opens it
together, and a tab you open later comes up already in it — which is what makes a fan-out legible, since it shows which
of several parallel agents is still grinding. A task that never started
reads —, not 0:00; the two mean different things.
The plan survives a restart: a resumed session replays its own transcript and comes back with the tasks and their banked time intact.
Above it sits REPOS — which git repos the live agents are actually writing to:
● marks a repo this tab's agent is in, · one only its peers are in,
and the count turns amber when more than one live agent is writing to the
same tree — the collision you would otherwise find at git diff, hours
later. ~n is uncommitted files, ↑n unpushed commits (the "a job clones
origin and silently gets the old tree" failure, made visible), and a
detached HEAD or a rebase in flight replaces the branch in the error
colour.
Membership is learned from writes only — Write / Edit /
NotebookEdit, or their ACP equivalents by tool kind. Reads do not put
a repo on the board, or every repo an agent merely grepped would show up
and the section would stop meaning work is happening here. Bash is
deliberately missed: guessing write targets out of a shell command is a
heuristic, and one wrong row would make the whole section untrusted. A
repo on a remote execution host is listed but never probed —
the same path names a different tree there, so a local git status would
answer confidently and wrongly.
The panel's foot is a SYSTEM block: the CPU/RAM/disk meters, then the
date, time and locale, the directory this aegis is rooted at, and the build
it is actually running (aegis 0.32.0+b78cb3d). The last two are the pair
you go looking for rather than notice — under an editable checkout that
keeps moving, "which version am I running" and "which version is on disk"
diverge the moment a commit lands beneath a live TUI.
Other agents can see it too. The tab bar carries a compact 3/8 for any
tab with a plan, aegis_list_sessions rolls it up, and
aegis_peer_plan drills into a peer's full list — so an agent
deciding who to hand work to learns not just that a peer is busy but how
far along it is.
Queue dashboard¶
When queues are configured in .aegis.yaml, a one-line strip sits
just above the status bar in every conversation showing live per-queue
depth and the most recent in-flight worker. Press Ctrl+D to expand
into a full-screen modal with QUEUES / IN-FLIGHT / QUEUED / RECENT
bands and a detail panel that tails the selected worker's assistant
text. See Queues → Dashboard for the full
key map.
When a handoff or queue callback lands on the active agent, a
distinct ✉ block appears in the transcript with sender, status,
timestamp, and a body preview — before the agent reacts.
Persistence¶
aegis reopens the last workspace by default — same tabs, same
profiles, same order, with each underlying model session genuinely
resumed (the agent's memory is intact, not a transcript replay).
Pass --clean to start fresh and overwrite the persisted workspace.
What's persisted, under .aegis/state/ next to your .aegis.yaml:
workspace.json— the tabs that were open: handle, profile, display order, which tab was active.sessions/<handle>.jsonl— per-tab append-only event log used by the TUI to rebuild each pane's transcript.
Limitations:
- Driver support. Claude Code resumes via
claude --resume; Gemini and OpenCode resume via ACPloadSession(the spawned agent must implement it — if it doesn't, the tab opens with a failure banner). - Cwd-bound. Claude resume is tied to the working directory of the
original session. Moving or renaming the project breaks resume for
that workspace; use
--cleanto recover. - Terminals + file tabs. Terminals re-spawn as fresh shells over their existing ledger; file tabs re-open the file at the saved path (dirty buffers and cursor position are NOT preserved). Both restore whether or not any agent tabs resumed.
- Workers not resumed. Queue workers and workflow runs are not part of the workspace snapshot; only interactive tabs are restored.
Headless mode¶
If you want the routing plane (sessions, queues, MCP) without the TUI,
run aegis serve. See Configuration
for the Telegram bridge.