Skip to content

Working with Agents

artuditu treats your AI agents as first-class citizens of the terminal: it knows which panes run agents, what state each is in, and how to bring a conversation back after a restart, all without wrapping or modifying the agents themselves.

Every pane running a recognized agent (Claude Code, Copilot, Codex, opencode, Kimi, Grok, Pi, Cursor, Gemini, Qwen, Kiro, Aider, Amp, Droid, or fx) appears in the sidebar with a live state:

StateMeaningHow it’s detected
🔴 blockedwaiting on you: approve/deny is on screena permission prompt is visible
🟠 workingthe agent is generatinga spinner or “esc to interrupt” hint on screen
🟢 donefinished while unfocusedsustained quiet after working
idlequiet, nothing pendingno recent activity

A working agent shows a rotating-circle spinner in its status dot, so you can tell at a glance which agents are still busy. When one finishes or blocks, artuditu can play a short retro chime. The sound is optional and off by default, turn it on in Settings → General.

Click a row to jump to that agent’s pane from anywhere, across workspaces.

States are debounced: an agent that pauses mid-turn (thinking, tool calls, API latency) holds at working instead of flickering. You get one clean working → done per turn, not a dozen flaps.

Working needs proof. An agent counts as working only when a real generating indicator is on screen, like a spinner or an interrupt hint. Output alone proves nothing. A CLI printing its whole welcome screen at launch is idle, your keystrokes echoing while you compose a prompt are idle, a scrolling log is idle. That way you never get a false state or a false completion chime.

The All / Active toggle in the header switches between live agents only (default) and the full resumable history.

Mission Control reads the structured usage counters that each agent already stores for its own session. It shows input, output, and cache tokens, context usage when the agent records its context limit, and cost when the agent records one or Artuditu has pricing for the model.

Native usage readerData source
Claude Codeproject transcript
Codexrollout token counters
GitHub Copilot CLIsession shutdown metrics
opencoderead-only session database
Kimisession status records
Grokcompleted-turn usage records
Piassistant message usage
Gemini and Qwenproject chat records
fxsession usage snapshot

Aider, Kiro, Cursor, Amp, Droid, and manifest-defined agents still receive live identity and state detection, but show for usage until they expose a stable per-session counter or report one through an integration. Artuditu never estimates tokens from transcript text.

Usage parsing runs off the render path only when Mission Control becomes active, its scope changes, or you choose Refresh. A hidden dashboard schedules no usage work. Unchanged sessions are cached by agent, session ID, and source modification time. JSONL readers cap individual records, so an unusually large tool result cannot cause an equally large temporary allocation.

How artuditu knows which agent a pane is running

Section titled “How artuditu knows which agent a pane is running”

An agent is a program, so artuditu asks the operating system rather than guessing from what a pane happens to be showing. It scans the processes running under each pane and matches the program name. A pane running claude is Claude Code; a pane that merely prints the word “claude” is a shell.

That distinction matters more than it sounds. Names like amp, cursor, droid, grok and pi are ordinary English words, so reading them off the screen turns “for example” into Amp and “cursor is out of bounds” into Cursor. Your sidebar fills with agents that were never running.

The scan covers every pane at once, a couple of times a second, off the render path, so it costs nothing you can feel.

Where artuditu cannot see processes, it falls back to reading text, ranked by how deliberate that text is: the command the pane was started with, then the window title the agent sets for itself, then its output. Names that double as ordinary words are only ever believed from the first two, never from output. This fallback covers Windows and remote panes, so detection degrades there, it does not break.

artuditu can play a short retro chime when an agent finishes a working stretch or blocks on a permission prompt. Both sounds are off by default because sound is a matter of taste. Turn them on in Settings → General, and use the Test sound row to hear the chime before you commit to it. The blocked chime rings at most once until you’ve actually looked at the pane, so a repeating prompt can’t spam you.

artuditu reopens each agent’s own conversation after a restart, with zero configuration. It discovers the agent’s most recent session from the agent’s native on-disk store, keyed to the pane’s folder, and runs the right resume command for you:

AgentResumes via
Claude Codeits project transcript store
GitHub Copilot CLIits session-state store
Codexits rollout files
opencodeits session storage
Kimiits session index (~/.kimi-code/session_index.jsonl)
Grokits session directory (~/.grok/sessions)
Piits session store (~/.pi/agent/sessions)
Geminiits project chat store (~/.gemini/tmp)
Qwenits project chat store (~/.qwen/tmp)
fxits session store (~/.fx/sessions)
Cursorresume command (when the session id is known)

You’ll also see recent sessions listed at the bottom of the AGENTS sidebar (toggle All): click one to reopen it into a new pane, even sessions from before you installed artuditu. The ✕ hides an entry from the list. It never touches the agent’s stored session.

Sometimes you want to try a different direction without losing where you are. Fork to New Pane branches the agent conversation in the current pane into a brand new pane beside it. The fork inherits the entire context of the original session, but under a new session id, so the two run independently: keep the original going in the left pane and explore the branch on the right, with no crossed wires.

Two ways to trigger it:

  • Right-click the pane and choose Fork to New Pane.
  • Press Ctrl+Space f to fork the focused pane.
  • Run artu agent fork <target> from the CLI. Add --name <alias> to name the new fork or --no-focus to leave the current view unchanged.

The new pane opens straight into the agent, already caught up on the whole conversation, exactly as if you had resumed it, except the original session is untouched and the two diverge from here.

Forking uses the agent’s own fork command, so it is available for agents that support it natively:

AgentForks via
Claude Codeclaude --resume <id> --fork-session
Grok Buildgrok --resume <id> --fork-session
Codexcodex fork <id>
Pipi --fork <id>

The TUI action only appears for supported agents and otherwise remains a no-op. The CLI reports a structured error instead, so automation never mistakes an unsupported or unresolved fork for success.

Codex forks require the exact session identity reported by its integration or recorded when Artuditu resumes a session. Artuditu never guesses the newest Codex rollout in a shared folder, because that could fork another pane’s active conversation. Install or refresh the hook with artu integration install codex; its SessionStart and prompt hooks bind new and resumed Codex panes to their exact rollout.

Screen-based detection needs no setup and works for everything. The optional hook adds precision: the agent itself reports its exact session id and lifecycle events (permission prompt raised, turn ended) into artuditu:

Terminal window
artu integration install claude # or: copilot · codex · opencode · kimi

or toggle it in Settings → Integrations. What it does per agent:

  • claude / copilot / codex: registers a small session-start hook script in the agent’s own settings file.
  • opencode: installs a tiny plugin file.
  • kimi: adds a [[hooks]] entry to ~/.kimi-code/config.toml, edited in place so your API keys, comments, and own hooks are left untouched.

Uninstalling (artu integration uninstall <agent>, or the same Settings toggle) surgically removes only artuditu’s hook entry. Your other settings and the agent installation itself are never touched.

Detection ships with built-in defaults that already cover the known agents. If you run an agent artuditu does not recognize, or you want to tune how an existing one reads, add your own TOML files in ~/.artuditu/manifests/. Name each file after its agent (myagent.toml) to keep things findable. Every *.toml in that folder merges on top of the built-ins, and the first time artuditu runs it drops an annotated example.toml.txt there to copy from.

A manifest controls two separate things:

  • [identity] decides which agent a pane is running
  • [[rule]] decides what state that agent is in

[identity] lists the names that identify the agent, matched as whole words. The two lists differ in how far each name is trusted:

ListTrusted in
distinctanywhere, including whatever the pane prints
ambiguousonly the command that started the pane, or the agent’s own window title

Put a name in ambiguous when it is also an ordinary word, so a pane that happens to print it is not mistaken for the agent. Add replace = true to drop artuditu’s built-in names instead of adding to them, which is how you remove a default you disagree with.

~/.artuditu/manifests/cursor.toml
agent = "cursor"
[identity]
distinct = ["cursor-agent"] # the CLI binary: unmistakable
ambiguous = ["cursor"] # also a normal English word

Naming an agent artuditu does not ship teaches it a new one, no rebuild and no waiting for a release:

~/.artuditu/manifests/myagent.toml
agent = "myagent"
[identity]
distinct = ["myagent-cli"]

A rule matches text on the pane’s screen (or its window title) and, when it holds, sets the agent’s state. Rules carry a priority, so the highest-priority match wins and a rule of yours can override a built-in one for the same agent.

~/.artuditu/manifests/myagent.toml
# Which agent this file applies to. "generic" (the default) means every agent,
# and is only valid for rules: identity always needs a specific agent.
agent = "myagent"
[[rule]]
state = "working" # working | blocked | idle
priority = 200
region = "screen" # screen (recent output, default) or title (window title)
any = ["esc to interrupt", "esc to cancel"]
[[rule]]
state = "blocked"
priority = 300
region = "screen"
all = ["do you want to proceed"]
not = ["cancelled"]

Each rule holds when all of the conditions you list are true:

FieldHolds when
anyany listed substring is present (case insensitive)
allevery listed substring is present
notnone of the listed substrings are present
spinnera running braille spinner glyph is visible on a line

Priorities follow the built-ins: blocked prompts sit around 300, working hints around 100 to 200. Give a rule a higher number than the built-in you want it to beat. A file that fails to parse is skipped with a warning, so a typo never takes detection down.

Inside every pane, artuditu injects $ARTUDITU_PANE_ID and $ARTUDITU_SOCKET_PATH, so the agent in the pane can call the same API you do:

Terminal window
artu pane status # my own status
artu wait output 7 --match "tests passed" --timeout 300
artu wait agent-status 7 --status done

See Scripting artuditu for the patterns, and Multi-Agent Orchestration for running a whole team.