Working with Agents
luvus 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.
The AGENTS sidebar
Section titled “The AGENTS sidebar”Every pane running a recognized agent (Claude Code, Copilot, Codex, opencode, Kimi, Grok, Pi, Cursor, Gemini, Aider, Amp, Droid) appears in the sidebar with a live state:
| State | Meaning | How it’s detected |
|---|---|---|
| 🔴 blocked | waiting on you: approve/deny is on screen | a permission prompt is visible |
| 🟠 working | the agent is generating | a spinner or “esc to interrupt” hint on screen |
| 🟢 done | finished while unfocused | sustained quiet after working |
| idle | quiet, nothing pending | no 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, luvus 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.
How luvus knows which agent a pane is running
Section titled “How luvus knows which agent a pane is running”An agent is a program, so luvus 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 luvus 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.
Sound alerts
Section titled “Sound alerts”luvus 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.
Session resume: the flagship
Section titled “Session resume: the flagship”luvus 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:
| Agent | Resumes via |
|---|---|
| Claude Code | its project transcript store |
| GitHub Copilot CLI | its session-state store |
| Codex | its rollout files |
| opencode | its session storage |
| Kimi | its session index (~/.kimi-code/session_index.jsonl) |
| Grok | its session directory (~/.grok/sessions) |
| Pi | its session store (~/.pi/agent/sessions) |
| Cursor | resume 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 luvus. The ✕ hides an entry from the list. It never touches the agent’s stored session.
Fork a session into a new pane
Section titled “Fork a session into a new pane”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 fto fork the focused pane. - Run
luvus agent fork <target>from the CLI. Add--name <alias>to name the new fork or--no-focusto 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:
| Agent | Forks via |
|---|---|
| Claude Code | claude --resume <id> --fork-session |
| Codex | codex fork <id> |
| Pi | pi --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 Luvus resumes a session. Luvus 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
luvus integration install codex; its SessionStart hook binds both new and
resumed Codex panes to their exact rollout.
Precise events: the integration hook
Section titled “Precise events: the integration hook”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 luvus:
luvus integration install claude # or: copilot · codex · opencode · kimior 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 (luvus integration uninstall <agent>, or the same Settings
toggle) surgically removes only luvus’s hook entry. Your other settings
and the agent installation itself are never touched.
Custom detection rules
Section titled “Custom detection rules”Detection ships with built-in defaults that already cover the known agents. If
you run an agent luvus does not recognize, or you want to tune how an existing
one reads, add your own TOML files in ~/.luvus/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 luvus 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
Teaching luvus a new agent
Section titled “Teaching luvus a new agent”[identity] lists the names that identify the agent, matched as whole words.
The two lists differ in how far each name is trusted:
| List | Trusted in |
|---|---|
distinct | anywhere, including whatever the pane prints |
ambiguous | only 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
luvus’s built-in names instead of adding to them, which is how you remove a
default you disagree with.
agent = "cursor"
[identity]distinct = ["cursor-agent"] # the CLI binary: unmistakableambiguous = ["cursor"] # also a normal English wordNaming an agent luvus does not ship teaches it a new one, no rebuild and no waiting for a release:
agent = "myagent"
[identity]distinct = ["myagent-cli"]State rules
Section titled “State rules”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.
# 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 | idlepriority = 200region = "screen" # screen (recent output, default) or title (window title)any = ["esc to interrupt", "esc to cancel"]
[[rule]]state = "blocked"priority = 300region = "screen"all = ["do you want to proceed"]not = ["cancelled"]Each rule holds when all of the conditions you list are true:
| Field | Holds when |
|---|---|
any | any listed substring is present (case insensitive) |
all | every listed substring is present |
not | none of the listed substrings are present |
spinner | a 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.
Agents can drive luvus
Section titled “Agents can drive luvus”Inside every pane, luvus injects $LUVUS_PANE_ID and $LUVUS_SOCKET_PATH, so
the agent in the pane can call the same API you do:
luvus pane status # my own statusluvus wait output 7 --match "tests passed" --timeout 300luvus wait agent-status 7 --status doneSee Scripting luvus for the patterns, and Multi-Agent Orchestration for running a whole team.