Skip to content

Agents Talking to Agents

luvus lets one agent give work to another. Because the server owns every pane’s terminal, “send a prompt to another agent” means luvus pastes the text into that agent’s input and submits it, then reports back its detected state. There is no separate message channel: the target agent’s own input is the channel, and the host is the broker.

Every pane already has $LUVUS_PANE_ID and $LUVUS_SOCKET_PATH injected, so an agent running inside a pane can drive its neighbors with the same luvus commands you would type yourself.

Terminal window
luvus agent list # every live agent: name, pane, kind, status, cwd
luvus agent name reviewer # alias the current agent (or --pane <id>)
luvus agent send reviewer "Review the diff" --wait --until idle --timeout 600
luvus agent read reviewer --lines 120 # read what it produced
luvus agent keys reviewer enter # answer a blocked approval (esc, ctrl+c, up, ...)

A <target> is one of three things:

  • a live name you set (grammar [a-z][a-z0-9_-]{0,31}), set with agent name or pane name,
  • a pane id (the pane field from agent list),
  • an agent kind (claude, kimi, codex, …) when exactly one agent of that kind is running. Two of the same kind is ambiguous, and the error names the candidates so you pick a pane id or a name.

agent send and agent keys only target a pane that is actually running an agent, so a plain shell is refused.

For line-specific feedback, the native DIFF review uses the same target validation. It groups selected local notes into one bounded message, quotes source context as untrusted review data, and records delivery without resolving the notes.

Run luvus agent list (each row has pane, agent kind, name, cwd, workspace_name). Two Claudes share the kind claude but differ by pane id and folder. Give each a name so you can mention it:

Terminal window
luvus agent name api --pane 1 # or run `luvus pane name api` inside that pane
luvus agent name web --pane 4
luvus agent send api "add the /health endpoint" --wait

The AGENTS sidebar also shows each agent’s delegation token on its second line: =name once you name it, otherwise =<id>.

Start an agent in a sibling pane, name it, hand it the task, and collect the result:

Terminal window
new=$(luvus pane split "$LUVUS_PANE_ID" | jq -r .result.pane) # split beside you
luvus pane focus "$LUVUS_PANE_ID" # keep your own focus
luvus pane run "$new" codex # launch the agent
luvus wait agent-status "$new" --status idle --timeout 60 # wait until it is ready
luvus agent name codex --pane "$new" # give it a name
luvus agent send codex "Implement the CSV parser in src/parse.rs and add a test." \
--wait --until idle --timeout 600 # send and wait
luvus agent read codex --lines 120 # read the result

agent send --wait blocks until the agent settles, using luvus’s screen-based detection (idle, working, blocked, done). Treat those as strong hints, not a hard contract. If a send returns while the agent is blocked, inspect it with agent read and answer with agent keys.

By default, delegation does not block the sender. luvus agent send without --wait returns the moment the prompt is queued, so the sender is free. The clean pattern is: name yourself, send without --wait with a report-back instruction, then end the turn.

Terminal window
luvus agent name lead # a name the worker can reply to
luvus agent send codex "Build the parser. When done, run: luvus agent send lead 'done: <summary>'"

The sender is not blocked; it picks the task back up only when the worker runs that agent send lead … and hands it back.

Wait only when you ask for it. Add --wait when the very next step needs the result in the same reply:

Terminal window
luvus agent send codex "Build the CSV parser and add a test." --wait
luvus agent read codex --lines 120

--wait blocks the sender until the worker settles, but it is safe: a stall guard returns stalled (exit 3) if there is no response within ~5s, and it is bounded by --timeout (default 300s, exit 2 on timeout; exit 0 on settle). So a broken worker never traps you — but since it blocks, reach for it only when you actually need to wait.

With the skill installed you rarely type the commands. Write =name at the start of a line and the rest of it goes to that agent:

=codex add tests to src/parse.rs
=reviewer take a look at the diff
=7 run the migration

=codex addresses the pane named codex. An agent name or a pane id work the same way, so =reviewer and =7 both resolve. The same targets agent send takes. The marker is recognized only as the first non-whitespace character on a line, so equations and shell assignments do not trigger delegation. Codex keeps $ for invoking skills such as $luvus or $changelog.

It is a hand-off, not a wait: the agent you typed into keeps working, and the answer comes back when the other one is done. Ask for it to wait and it will.

If the name does not resolve, the agent runs luvus agent list to find it or asks you which pane you meant.

The commands above work without an agent skill. The optional skill teaches a coding agent when and how to use them. Luvus does not install skills, edit agent configuration, or make a skill-network request during startup. Enable only the agents you want:

Terminal window
luvus skill enable claude
luvus skill enable codex
luvus skill enable opencode
# or make the same explicit choice for all three
luvus skill enable --all

Each agent receives its native skill directory:

  • Claude Code: ~/.claude/skills/luvus/
  • Codex: ~/.agents/skills/luvus/
  • opencode: ${XDG_CONFIG_HOME:-~/.config}/opencode/skills/luvus/

Luvus does not add a global AGENTS.md pointer. Supported agents discover the skill from their native directory and load its full instructions only when the skill is relevant or explicitly invoked.

luvus skill enable installs prompt guidance. It is independent from luvus integration install, which adds only the session/status hook needed for precise detection and resume. Enabling either one never enables the other.

The messaging commands themselves work with every agent luvus detects (claude, codex, gemini, opencode, kimi, grok, aider, and more), whether or not that agent has the skill installed. The skill just teaches an agent to reach for them on its own.

Inspect the local state or the installed instructions:

Terminal window
luvus skill status # all supported agents
luvus skill status codex # one agent
luvus skill show codex # installed SKILL.md

Refresh one enabled agent, or every enabled agent, between application releases:

Terminal window
luvus skill update codex
luvus skill update

Updates use a signed manifest, verify the package hash and every file hash, and replace only installations that Luvus already manages. If no agents are enabled, luvus skill update is a local no-op with no network request.

Disable an agent explicitly with luvus skill disable codex, or use --all. Luvus removes only an unchanged managed installation. If its files were edited, the command preserves them and reports the conflict.

When upgrading from Bohay or an earlier Luvus build, version 0.11 performs one local, idempotent cleanup: it removes only the old delimited Codex/opencode AGENTS.md pointer and exact known auto-installed Claude files. Surrounding user instructions, modified files, and independently installed plugins are preserved. The migration leaves every new skill disabled; opt back in with the commands above.

Restart your coding agent so it loads the skill, open it in a luvus pane, and ask in plain language:

Have codex implement the parser in the other pane and tell me when it is done.

The agent recognizes the request, runs the agent send/wait/read loop itself, and reports back. The skill also controls workspaces, tabs, panes, and other Luvus surfaces when asked. It stays out of the way when the request is unrelated to Luvus.

luvus pane name <name> (and its synonym luvus agent name) does two things: it sets a live address for the CLI and API, and it renames the pane’s title strip to that name in place of its cwd path, so a named pane is easy to spot. It shows the same delegation token in the AGENTS sidebar (=name).

A name is keyed to the pane, so it survives the agent restarting inside it and is dropped only when the pane closes. It does not change the pane’s tab label, which is still luvus tab rename. Clear a name with luvus pane name --clear.