# Agent automation

> Let agents and scripts read and type into specific tmux panes through the tmux-ide CLI, MCP server and SDK, with operation handles for safe recovery.

Agent automation lets scripts and agents read another tmux pane's output and type
into it, like a scoped and auditable `tmux send-keys` / `capture-pane`. It works
through the tmux-ide CLI, an MCP server or the SDK. All three share one
operation API. They need a running daemon of the same version, and they never
fall back to raw tmux when a request fails.

For quick, human-style messaging between agents, `tmux-ide send` and
`tmux-ide wait` are simpler; see [Multi-agent teams](/docs/multi-agent-teams).
Use automation when you need exact pane identity, attribution and retry-safe
handles.

## Read and send in two commands

List the panes you can target, then pass one endpoint in a read or send intent on
stdin. This example uses `jq` to pick the pane titled `Tests`:

```bash
tmux-ide automation panes --json > panes.json
jq '{kind: "read", target: (.panes[] | select(.title == "Tests") | .endpoint)}' panes.json \
  | tmux-ide automation read --json
jq '{kind: "send", target: (.panes[] | select(.title == "Tests") | .endpoint), text: "npm test", enter: true}' panes.json \
  | tmux-ide automation send --json
```

`read` returns up to 16 KiB of the pane's text. `send` types the text and, with
`enter: true`, presses Enter. The sections below explain endpoints, the
two-step reserve and execute flow for safe retries, and sender attribution.

## Find a pane's endpoint

```bash
tmux-ide automation panes --json
```

Each result includes a display title, session name, and an `endpoint`. Copy the
endpoint unchanged into your request. It identifies the machine, tmux server
generation, workspace, and pane lifetime. Pane numbers and titles alone are
not unique across servers. Rediscover after a server or pane is replaced.

A read intent is `{ "kind": "read", "target": endpoint }`. The CLI and MCP
resolve an omitted source from the invoking pane’s credential. Use `source: null`
to deliberately leave the caller unidentified. Discovery also returns `source`
when the daemon can verify that credential.
A send intent also includes `"text"` and `"enter"`, with `"kind": "send"`.
`enter: true` presses Enter after the text and can execute a shell command.
Input is limited to 16 KiB of UTF-8 text and cannot contain NUL.

## Reserve, execute and recover a send safely

Save your intent as `intent.json`, then reserve an operation before executing it:

```bash
tmux-ide automation reserve --json < intent.json > reservation.json
jq '{version:1,handle,intent}' reservation.json > execution.json
tmux-ide automation execute --json < execution.json
```

Reservation does not send input or capture a pane. Keep the returned handle
and the returned, resolved intent until you know the result. On a timeout or lost
response, check the handle:

```bash
tmux-ide automation status GENERATION OPERATION_ID --json
```

You may retry the same `execution.json`. Do not reserve a new operation to
retry an uncertain send. `outcome-unknown` can mean the daemon restarted or
retention expired; it does not prove that nothing happened. A completed send
means the tmux command completed, not that the recipient consumed the input.

`tmux-ide automation send --json` and `tmux-ide automation read --json` accept
an intent on stdin and combine reservation with execution. Use the explicit
two-step flow when the caller must persist the handle before an effect.

Read text is returned only with the first execution response, up to 16 KiB.
Check the byte counts and `truncated` flag. Status and replay responses retain
metadata only; a replay reports `replay-unavailable` and does not capture again.

## Attribute who sent it

Omit `source` in CLI requests and MCP `tmux_prepare` calls to resolve the
invoking pane automatically. Save the resolved `intent` returned with the
reservation and pass it unchanged to execution. Explicit `source: null` disables
this resolution. An invalid credential fails discovery; it never silently
changes an operation’s identity.

Use `source: null` when the caller has no verified pane identity. To claim a
source, supply its discovered endpoint and a valid credential for that exact
pane lifetime. The CLI and MCP process attempt to read the invoking pane's
credential using its actual `TMUX` socket and `TMUX_PANE`; the daemon validates
it separately from the target. A title, native pane number, or matching text
does not establish the sender's identity.

Receipts distinguish `cli`, `sdk`, and `mcp` adapter origins. This is declared
transport metadata, not proof of which agent called it. A reservation binds
that origin along with the intent and source authority; retries must use the
same adapter origin. Older automation clients that omit it retain `sdk`.

Source and target may belong to different registered servers on the same
daemon. Invalid or stale source claims are rejected rather than reassigned.

## Use automation from an MCP client

Configure your harness to launch this stdio server:

```json
{ "command": "tmux-ide", "args": ["mcp"] }
```

The exact surrounding configuration depends on your harness. The server offers:

| Tool                    | Purpose                                        |
| ----------------------- | ---------------------------------------------- |
| `tmux_panes`            | Discover current endpoint identities           |
| `tmux_prepare`          | Reserve a read or send without executing it    |
| `tmux_execute`          | Execute the saved handle and unchanged intent  |
| `tmux_operation_status` | Check an operation without repeating it        |
| `tmux_interactions`     | Read one bounded batch of interaction metadata |

`tmux_interactions` accepts a server-scoped resume cursor and waits up to 30 seconds.
Carry the returned cursor forward. A gap explicitly indicates missing history.
Events contain no terminal contents. The server does not expose an arbitrary
shell runner or HTTP proxy.

## Use the TypeScript SDK

The workspace package `@tmux-ide/sdk` exports the same automation client:

```ts
import { createTmuxIdeAutomationSdk } from "@tmux-ide/sdk";

const client = createTmuxIdeAutomationSdk({
  baseUrl,
  ownerToken,
  // sourceCredential: verified credential, when claiming a source
});
const { panes } = await client.discover();
const target = panes.find((pane) => pane.title === "Tests")?.endpoint;
if (!target) throw new Error("Select a current target pane first");
const intent = { kind: "read" as const, target, source: null };
const { handle } = await client.reserve(intent);
// Persist handle + intent before calling execute when recovery matters.
const response = await client.execute(handle, intent);
```

Use `AutomationInvocationError.handle` for recovery after an uncertain request.
The SDK also provides `status(handle)` and a scoped `subscribe` method. Protect
the daemon owner token as a local automation credential; it grants authority
to send input to managed panes.

## Limits of observation coverage

An authored operation and an observed tmux command provide different evidence.
The automation API records the requested operation and its result. When native
evidence can be explicitly correlated with that operation, it enriches the same
interaction rather than appearing as another send or read.

Stock tmux observation has partial coverage. Missing activity does not prove
that no command ran, and a hook's client is not automatically the original
sender. Raw commands may have an unknown source. Server registrations and pane
lifetimes keep evidence from separate tmux servers apart, even when their pane
numbers match.

With stock observation, typing through the tmux-ide viewer can appear in activity
details as “Send command · sender unknown.” This is a command observation, not evidence
that another agent sent input. Pane headers and Home’s transient indicators show only completed interactions
with a bound source, excluding viewer and same-pane activity. Unknown command
observations remain in history and do not replace a named interaction. Repeated
reads update one short-lived indicator.

Exact viewer suppression requires native
ownership evidence; stock client labels alone cannot establish the issuer.

Experimental native observation uses a bounded metadata journal on a capable
tmux server. It is disabled by default and must be selected explicitly. Replacing
the tmux executable does not upgrade an already running server or enable this
coverage for existing sessions. Development-instance configuration is described
in the [development worktree guide](https://github.com/wavyrai/tmux-ide/blob/main/docs/guides/development-worktrees.md).

Observation describes effects, not intent: captured output does not prove an
agent understood it, and queued input does not prove the recipient acted on it.
Viewer connections used by tmux-ide do not represent another agent reading or
messaging the pane. Native connection evidence is not a security boundary against
another process running as the same user.

History is bounded and can be lost when a server restarts or a reader falls
behind. Keep the server-scoped cursor and handle reported gaps or unavailable
coverage explicitly. A lost observation must not trigger a resend of an
uncertain operation.

An external desktop agent without a tmux pane currently remains unidentified.
A CLI/MCP adapter label does not prove which agent invoked it; external-client
registration is not yet available.

## See also

* [Multi-agent teams](/docs/multi-agent-teams) — coordinate agents with send and wait
* [CLI reference](/docs/commands#agent-automation) — every automation subcommand
* [Agent detection](/docs/agent-detection) — the status agents publish
