CLI
ovn is the command-line client inside every workspace. It talks to the
workspace's runtime - the process that owns every agent's terminal - over a
local socket, so it works from any agent's shell without a login. Its main user
is an agent: Claude Code in a workspace already knows ovn, and uses it to
split a task across other agents and supervise them.
ovn spawn codex 'add a regression test for the auth timeout bug'
ovn list
ovn wait ag_01J8… --until idle --until blocked
ovn read ag_01J8…
ovn input ag_01J8… 1
ovn kill ag_01J8…
Every command is also spelled ovn agent <command> (ovn agent spawn …); the
names follow Herdr's, so scripts written
for one read naturally on the other.
Commands
| Command | Does |
|---|---|
ovn list | every agent: id, kind, status, task |
ovn spawn <kind> [task] | start an agent (alias start) |
ovn read <id> | print its screen |
ovn wait <id> | block until it reaches a status |
ovn input <id> <text> | type into it, then Enter (alias prompt) |
ovn kill <id> | stop it |
Global flags
| Flag | |
|---|---|
--json | print the result as JSON; every agent carries a lifecycle field |
--socket <path> | talk to another socket |
-h, --help | help, overall or for one command (ovn spawn --help) |
-V, --version | the version |
Flags can go anywhere; -- ends them.
ovn spawn <kind> [task]
<kind> is claude, codex, opencode or gemini, started exactly as the
cockpit starts them (Agents).
| Flag | Default | |
|---|---|---|
--task <text> | the task; wins over the positional one | |
--no-worktree | work in the shared checkout instead of a worktree of its own | |
--ask | ask before every command and edit, instead of Auto (Agents) | |
--cwd <path> | start in this directory, with no worktree | |
--cols, --rows | 120 × 40 | terminal size |
--wait | wait for the agent before returning | |
--until <status> | wait for this status; repeatable; implies --wait | |
--timeout <ms> | 300000 | how long to wait; 0 waits forever |
Without --no-worktree the agent gets /workspace/wt-<id> on branch
ovn/<id>. When the agent takes its task as input rather than an argument,
ovn types it in once the agent is ready.
ovn read <id>
| Flag | Default | |
|---|---|---|
--lines <n> | all | only the last n non-empty lines |
--format text|ansi | text | ansi keeps colours and cursor codes (also --ansi) |
--json adds the terminal title and progress the agent reported.
ovn wait <id>
| Flag | Default | |
|---|---|---|
--until <status> | idle, blocked, done | repeatable |
--timeout <ms> | 300000 | 0 waits forever |
Statuses are working, blocked, idle, error and done - the process
exited. wait answers at once when the agent is already there.
A freshly started agent is not idle: until its screen is recognised it is
starting, and wait keeps waiting through it. Otherwise
wait --until idle would return the moment an agent launched.
ovn input <id> <text>
| Flag | |
|---|---|
--no-enter | type without pressing Enter |
--wait, --until, --timeout | as for spawn |
The rest of the line is the text. - reads it from standard input. Input sent
before the agent is ready is queued, not lost.
ovn kill <id>
--signal <name>, default SIGTERM.
Exit codes
| Code | Meaning |
|---|---|
0 | done |
1 | refused or failed - including an agent that exited before the status you waited for |
2 | usage error, or an unknown agent id |
3 | wait timed out |
4 | no runtime on the socket |
Agent to agent
An agent that supervises others follows one loop: start, wait, look, answer, repeat.
id=$(ovn spawn claude 'add a regression test for the auth timeout bug' --json | jq -r .agent.id)
while true; do
ovn wait "$id" --until idle --until blocked --until done --timeout 600000 --json > /tmp/state.json
case "$(jq -r .agent.lifecycle /tmp/state.json)" in
blocked) ovn read "$id" ;; # look, decide, then `ovn input`
idle) break ;; # it finished its turn
exited) break ;;
esac
done
Answering a question:
ovn read ag_01J8… # see what it is asking
ovn input ag_01J8… 1 # numbered options: send the number
ovn input ag_01J8… yes # free text: send the text
ovn input ag_01J8… 1 --until idle --until blocked --timeout 300000
Branching on how a wait ended:
ovn wait "$id" --until idle --timeout 60000 || case $? in
3) echo "still working" ;;
1) echo "it exited first" ;;
esac
The rules an agent in a workspace is taught:
- Wait, do not poll.
ovn waitreturns the moment something changes; looping overovn listdoes not. - Read before you answer, and leave a destructive prompt blocked for a person to decide.
- Merge its work with
git merge ovn/<id>from your own worktree, or let it push and open its own pull request -git pushandgh pr createalready work. - Kill what you start when you are done with it.
Agents started with ovn appear in the cockpit like any other: you can watch
their terminals and answer them from your phone. They get no connector tokens of
their own.
The socket
ovn finds the runtime at --socket, then $OVERNITE_SOCKET, then
$XDG_RUNTIME_DIR/overnite/server.sock. In a workspace OVERNITE_SOCKET is
/run/overnite.sock, and every agent inherits it.
The socket speaks newline-delimited JSON - { "id", "method", "params" } in,
{ "id", "ok", "result" | "error" } out - with the methods agent.list,
agent.spawn, agent.read, agent.wait, agent.input and agent.kill, and
pushes agent.spawned, agent.status and agent.exited events.