> ## Documentation Index
> Fetch the complete documentation index at: https://tty7.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Orchestrating agents

> One agent opening a pane for another, waiting on it, and reading the result.

Once an agent's status is a thing a program can ask about, one agent can run
another. tty7 gives that loop a primitive instead of leaving it to screen
scraping.

## The loop

```bash theme={null}
# 1. give the worker a pane
PANE=$(tty7 split --v)

# 2. hand it a task
tty7 send "$PANE" 'claude -p "add tests for the parser"' --enter

# 3. sleep until it needs you or finishes
tty7 wait "$PANE" --until waiting,done --changed --timeout 600

# 4. read what happened
tty7 capture "$PANE" --plain

# 5. clean up
tty7 pane close "$PANE"
```

That is the whole shape. The interesting step is the third.

## `tty7 wait`

```bash theme={null}
tty7 wait [%PANE] [--until STATE,…] [--changed] [--timeout SECS] [--interval MS]
```

Blocks until the pane reaches one of the states you named.

| Flag         | Default             |                                                                                                  |
| ------------ | ------------------- | ------------------------------------------------------------------------------------------------ |
| `--until`    | `waiting,done,exit` | Which states end the wait — see below                                                            |
| `--changed`  | off                 | Ignore the state the pane was *already* in — only wake on one it moved into after the wait began |
| `--timeout`  | none                | Give up after this many seconds, exiting 124                                                     |
| `--interval` | 500 ms              | How often to poll                                                                                |

Exit codes are made for scripts:

| Code  | Meaning                                                                                |
| ----- | -------------------------------------------------------------------------------------- |
| `0`   | A state you asked for was reached                                                      |
| `124` | Timed out — the `timeout(1)` convention, so "not yet" is distinguishable from "broken" |
| `1`   | The worker died first; the JSON says `"status": "exit"`                                |

The reply carries the agent's own message and its native session id, so a
wake-up is directly actionable.

### The states

Four of them are the agent's own [status](/agents/status). The other three are
about the pane, because not everything worth waiting on is an agent:

| State                             | Means                                                                                    |
| --------------------------------- | ---------------------------------------------------------------------------------------- |
| `idle` `working` `waiting` `done` | What the agent's hooks last reported                                                     |
| `no-agent`                        | Nothing reports status in this pane — a plain shell, or an agent whose hooks are missing |
| `free`                            | The foreground command has exited; the pane is back to its bare shell                    |
| `exit`                            | The pane is gone. Ends every wait, whether you asked for it or not                       |

### Waiting on a command instead of an agent

An agent says when it is done. A `cargo test` does not — so for a plain pane the
question is whether anything is still running in front of the shell, which is
what `free` answers:

```bash theme={null}
tty7 send "$PANE" 'cargo test > /tmp/t.log 2>&1; echo $? > /tmp/t.rc' --enter
tty7 wait "$PANE" --until free --changed --timeout 900
cat /tmp/t.rc /tmp/t.log
```

`free` costs one extra request per poll, so it is only checked when you name it —
and only once the agent ladder has not already answered, so pairing it with
`waiting,done` never costs you a state you asked for.

<Warning>
  `free` is read off the process tree, which has two blind spots. A pane whose
  own root process *is* the command — what `tty7 run` spawns — looks free the
  whole time it runs; wait on `tty7 run` itself instead, it already blocks. And
  a backgrounded job (`… &`) keeps the pane busy after the foreground command
  has finished.
</Warning>

<Note>
  A pane with no agent reports `no-agent`, **not** `idle`. That distinction is
  why `--until idle` cannot be used to mean "the command finished" — `idle` is a
  thing an agent says about itself, and a busy shell never says it.
</Note>

### Why `--changed` matters

The status the server keeps is a **level, not an event**. `done` stands until
the next turn begins; `waiting` stands until the agent moves again.

So a `wait` issued immediately after a `send` can answer with the *previous*
turn's state, before the worker has even read the input. `--changed` refuses the
state the pane was already in, which is what every round after the first needs.
Without it, the JSON's `stale` flag tells you whether that happened.

`free` has the same problem and a different fix: a shell that goes free → busy →
free ends up where it started, so there is no new state to compare against.
There `--changed` means "something ran while I was watching", which is exactly
what you want in the line after a `send`.

That does mean a command which starts *and* finishes between two polls is never
seen running, and the wait sits there until it times out. If the thing you are
waiting on can be that quick, poll faster (`--interval 100`) or drop `--changed`
and let a sentinel file carry the answer. The timeout says as much when it
happens.

## Answering a prompt

A worker that stops at `waiting` is usually showing something that keystrokes,
not text, are the answer to — a permission prompt driven by the arrow keys, a
menu, a TUI to be dismissed. `send --key` presses keys:

```bash theme={null}
tty7 wait "$PANE" --until waiting --changed   # it needs something
tty7 capture "$PANE" --plain | tail -20       # see what it is asking
tty7 send "$PANE" --key down --key enter      # answer it
tty7 send "$PANE" --key C-c                   # or stop it altogether
```

Keys are delivered as separate events 200 ms apart, so a raw-mode TUI reads a
sequence as a sequence rather than as a paste. The
[full vocabulary](/cli/reference#tty7-send-pane-text-enter-key-key) is in the
reference.

## When an agent never moves

If `tty7 wait` times out and `tty7 agents` shows a status that never changes,
the likely cause is that the agent's status hooks are not installed or are out
of date — the agent is working fine, it just has no way to say so. `tty7 doctor`
reports where every agent's hooks stand, and `tty7 agents` names the specific
one when it can see the gap.

## Watching everything at once

```bash theme={null}
tty7 agents          # every agent on the machine: pane, agent, status, message
tty7 agents --json   # the same, parseable
```

If you are an agent yourself, you are in that list too.

## Teaching an agent to do this

tty7 installs nothing into `~/.claude` for it — no switch in **Settings →
Agents** writes a skill, and none ever will. What the agent needs to know ships
in the repository instead, as a skill you install yourself:

```bash theme={null}
npx skills add l0ng-ai/tty7
```

That covers the pane-driving half — where it is, how to open a pane, send into
one, read one back, and the rules below — see [the agent
skill](/cli/agent-skill). The `wait` step is documented on this page.

A skill rather than a global instruction, on purpose: only its one-line
description rides in context until something reaches for it, so an agent that
never touches another pane pays nothing for it.

## Rules of the road

<Warning>
  The panes on a machine are somebody's real work, and some of them are other
  agents mid-task. Treat anything you did not create as read-only.
</Warning>

* **Never `send` into a pane you did not open.** Check `tty7 agents` first.
* **Never close a pane, tab, or workspace you did not create.**
* **Never `server stop` or `server restart`.** Every pane on the machine dies
  with it.
* **Clean up what you did create** — `tty7 pane close %83` when you are done.
  An interrupted `run` leaves its pane behind; `tty7 pane ls --all` shows those,
  and `tty7 pane close --orphans` clears them. That last one is a human's
  broom, not an agent's: it closes every orphan on the machine, including ones
  somebody else abandoned mid-command.

The full agent-facing contract is in [the skill](/cli/agent-skill).
