# Agent sessions

Shells show up in the [Command Center](https://sox.send.it/docs/command-center) on their own. Agent state — blocked, error, done — does not: it comes from a plugin running inside the agent that reports to the local Sox broker. This page is the install and configuration procedure, per harness.

> Reading this as an agent asked to set yourself up? Everything below is executable. Work the Requirements section, then your harness’s section, then verify with the doctor at the end. Do not guess at a harness you cannot identify — ask.

## Requirements

* `jq` on `PATH`.
* `SOX_ATTENTION_BIN` set to an absolute path to the broker binary that ships in the app bundle: `/Applications/Sox.app/Contents/Resources/bin/soxd`. Both bundled names implement the same commands; the broker keeps the internal `bin/soxd` filename.
* The plugin installed **wherever the agent process runs**: on the Mac for local sessions, and on each remote host for remote ones.

```
export SOX_ATTENTION_BIN=/Applications/Sox.app/Contents/Resources/bin/soxd
```

On a remote sox session this export is unnecessary: the attach path sets `SOX_ATTENTION_BIN` and `SOX_ATTENTION_HOME` in the shell’s environment already, pointing at the deployed remote broker. The plugin install is still yours to run on that host.

## Claude Code

The plugin is published through the repository’s marketplace manifest. Add the marketplace, then install:

```
/plugin marketplace add 0xsend/sox
/plugin install sox@sox
```

`sox@sox` is `<plugin-name>@<marketplace-name>` — both happen to be `sox`. After the release-channel switch, this Git marketplace installs the plugin tree from the newest fully published Sox release. A local checkout is for development only: give it a separate marketplace name or use `--plugin-dir` so it does not replace the production `sox` source.

Claude Code reports the full state set, including `blocked` the moment a permission prompt would render, whether or not the window has focus and whether the agent is local or remote.

## Codex

After the release-channel switch, Codex reads the repository marketplace from Git:

```
codex plugin marketplace add 0xsend/sox
codex plugin add sox@sox
```

The hook map is shared with Claude Code, so Codex gets the same state set. A local checkout is an explicit development source, separate from the production `sox` marketplace.

### Existing installs after the release-channel switch

For a Claude Code Git marketplace, update and restart the session:

```
claude plugin marketplace update sox
claude plugin update sox@sox
```

Claude Code does not auto-update third-party marketplaces by default. A marketplace added from a **local directory** needs a one-time replacement at its original scope. First run `claude plugin marketplace list --json` and find `sox` with `"source":"directory"`. Read the settings file that declares it to find marketplace scope `M`: user at `~/.claude/settings.json`, project at `<project>/.claude/settings.json`, or local at `<project>/.claude/settings.local.json`. Run project/local commands from that project. Run `claude plugin list --json` and record every `sox@sox` install’s scope `S`, `projectPath`, and enabled state. Then:

```
claude plugin uninstall sox@sox --scope S --keep-data  # each recorded install
claude plugin marketplace remove sox --scope M
claude plugin marketplace add 0xsend/sox --scope M
claude plugin install sox@sox --scope S             # each recorded install
```

Re-disable any install that was disabled, restart, and verify `marketplace list --json` shows `"source":"github"`, `"repo":"0xsend/sox"` without a local `path` and `plugin list --json` shows the release version in `cache/sox/sox/`. Keep the original scope and project directory for every command.

For a Codex Git marketplace, run `codex plugin marketplace upgrade sox`, `codex plugin remove sox@sox`, `codex plugin add sox@sox`, then restart. For a Codex marketplace added from a checkout, check its root with `codex plugin marketplace list`, remove `sox@sox` and the `sox` marketplace, add `0xsend/sox`, add `sox@sox`, and restart.

## Grok

Grok loads the same Claude-compatible plugin, and the lifecycle events it does emit map straight through: `working` while it runs, `done` when it stops, cleared at session end.

**Grok has no focus-independent blocked signal.** Its event table does not include the permission event the other harnesses use, so a Grok session sits at `working` for the whole approval wait rather than rising to the top of the roster. Until xAI ships an equivalent event, bridge it through Grok’s notification hooks. Add to `~/.grok/config.toml`, using an absolute path to the installed plugin’s hook:

```
[[ui.notifications.hooks]]
command = "bash \"/absolute/path/to/plugin/claude-code/bin/sox-attention-hook\" approval_required"
events = ["approval_required"]
only_unfocused = false
timeout_secs = 5
```

`only_unfocused = false` matters: a focused approval dialog is still something the roster should show. `SOX_ATTENTION_BIN` must be set in the Grok process’s environment, which Sox does for surfaces it owns. Approving the tool returns the row to `working` on its own.

When a Grok session has working sox attention but no bridge configured, the plugin says so once at session start rather than reporting a state it cannot observe.

## pi

Sox ships as a pi package — a hooks extension over the same reporting path:

```
pi install git:git@github.com:0xsend/sox.git
```

pi’s lifecycle events map onto `working`, `done`, and clear. pi has neither a permission event nor a notification bridge, so attention rests in those three; there is no `blocked` to report. The package also installs a `/sox-doctor` prompt carrying the same diagnostic guidance as the Claude Code command.

## Starting an agent in a shell

There is no `--agent` flag and no agent verb. `sox new` and `sox up` take `--exec` followed by `--` and an argv, and that argv becomes the shell’s child process:

```
sox new ae-dev --exec -- claude --resume
```

Everything after `--` is argv, passed through unmodified — no shell quoting, no wrapping, no prompt parsing. Sox does not know or care that the argv is an agent; the Command Center shows an ordinary shell row, and agent state arrives on it through the plugin above exactly as it does for an agent you launched by hand.

A shell whose child is the agent ends when the agent ends. Sox clears attention on `SessionEnd`, but the PTY exits with the process, so the row goes away with it. To keep the shell after the agent exits, pass a wrapper as the argv — that is the recipe, and it stays yours rather than becoming a sox flag:

```
sox new ae-dev --exec -- bash -lc 'claude --resume; exec "${SHELL:-/bin/bash}" -l'
```

The shell drops to an interactive login prompt in the same directory, keeping the agent’s scrollback, and the Command Center row reverts to `manual`. Substitute any wrapper: `; exec $SHELL -l` to inspect and continue, `|| exec $SHELL -l` to keep it only on failure, or a script of your own for anything else.

To run the agent unattended and get the terminal back, use `new --no-attach`:

```
sox new ae-dev --no-attach --exec -- bash -lc './scripts/nightly.sh; exec "$SHELL" -l'
```

That executes the argv on the remote host with no confirmation and no sandbox, and returns immediately. It prints the new shell as `ae-dev/<backing-id>`, the spelling `sox wait` and `sox send` take for a shell on that host. Attach later with the host-qualified handle from `sox ls`.

## Steering an agent without attaching

`sox send` queues one bounded payload into a shell that is already running, which is how you hand a running agent an instruction without taking its terminal.

Check the target’s latest turn first. Input goes to whatever its terminal is showing. If the agent has a question or permission picker open, `--enter` can select the focused option while the text is lost. Leave an open question for the human to answer.

`send` refuses, delivering nothing, when it can tell. Exit 45 means the agent reported an open question: relay it to the human. Exit 46 means the target is running `claude`, `codex` or `grok` and its attention state is missing or unreadable: run `/sox:doctor` in that shell. To pass on an answer the human gave, add `--answer`; the receipt then says what the guard saw. A question the agent never reported is not caught, so the check above still applies.

For a target with no open prompt:

```
sox ls | cat                       # s3  s3.g1477  live  claude  /work/lane
sox send s3.g1477 --enter -- 'run the migration, then report'
```

**The target names its generation.** A handle is lineage-blind: `s3` means whichever shell holds that slot right now, and shells re-materialize under new generations whenever the daemon restarts. A send is a blind write, so `sox send s3` is refused, and the refusal tells you what `s3` backs at that moment. Piped `sox ls` prints the generation beside the handle for exactly this reason; on a terminal, ask the shell’s host for it with `sox ls --backing-ids <host>`.

**A bare target is a shell on this machine.** A shell on another host is written `ae-dev/s3.g1477`, which is how `sox ls` prints it, and the send runs on that host over SSH. No verb asks any other host, so an unrelated host that is asleep or offline never delays or refuses it.

A delivered send reports what it hit on stderr — `sent 34 B to s3.g1477 (cwd /work/lane)` — but that receipt means only that the shell accepted the input. It does not show that the agent read the text as a message. Confirm the text appears in the target’s transcript before saying the message was delivered.

## Token and cost figures

Row sparklines, token counts, and running cost come from the statusline, not from hooks — no hook event carries them. The statusline is a user-owned setting, so no plugin can write it for you.

Claude Code, in `settings.json`:

```
{
  "statusLine": {
    "type": "command",
    "command": "/home/you/.claude/plugins/data/sox-sox/bin/sox-statusline"
  }
}
```

Codex, in `config.toml`:

```
[tui.custom_status_line]
type = "command"
command = "/home/you/.codex/plugins/data/sox-sox/bin/sox-statusline"
padding = 0

[tui.custom_status_line.env]
SOX_ATTENTION_BIN = "/Applications/Sox.app/Contents/Resources/bin/soxd"
```

Each harness allows exactly one statusline command. To keep one you already have, make `sox-statusline` the configured command and name yours as the delegate — sox submits its progress figures and then emits your script’s output verbatim:

```
export SOX_STATUSLINE_DELEGATE=/absolute/path/to/your-statusline
```

The delegate must be an absolute executable. Without one, sox renders its own `model | context% | $cost` line.

`SessionStart` creates the launcher under the plugin data directory. Start and exit one session with the migrated plugin, then run `sox-doctor` from that plugin to see the exact launcher path for this host. Replace **only** the Sox script path in the setting that already owns the statusline (Claude user/project/local `statusLine.command`, or Codex `[tui.custom_status_line] command`). Keep `type`, `padding`, `refreshInterval`, all env values and shell exports unchanged. If a personal wrapper probes checkout, marketplace clone, or versioned cache paths, replace its candidate list with the launcher alone while keeping its delegate logic. Restart and run `sox-doctor`: `ok` means the launcher is in use; `stale-soon` names a versioned cache path, `main` a marketplace clone, and `checkout` a development path. The launcher follows the plugin version loaded at the next session start, including an accepted rollback.

## Verifying

Hook failures are swallowed on purpose — a broken submit must never stall an agent — so a half-configured install reports success and shows nothing. The doctor is how you find out. It resolves and prints every value that has to agree: the broker binary, which broker home the submit dials and why, whether that socket answers, which app owns it, the surface key, whether that key has a cached envelope, and whether `jq` is present. It reads only; it submits nothing.

```
"$SOX_ATTENTION_BIN" attention --doctor
```

Claude Code also exposes it as `/sox:doctor`, which runs the check and interprets the result for you; pi ships the same guidance as the `/sox-doctor` prompt.

For a failure the doctor calls healthy, trace the submits themselves. Point `SOX_ATTENTION_DEBUG` at a writable file and every hook invocation appends the resolved binary, its arguments, and the broker’s exit code. Diagnostics go only to that file, never to stdout, so statusline and hook output stay clean. Leave it unset in normal use.

```
export SOX_ATTENTION_DEBUG="$HOME/.cache/sox/attention-hook.log"
```

## What the plugin sends

| Event                                                                    | Submits                                                                   |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| permission prompt                                                        | `blocked`, with the tool name as the reason                               |
| `AskUserQuestion` begins                                                 | `blocked`, with generic `Input required` (question text is not submitted) |
| prompt submitted, ordinary tool entry, and every tool completion/failure | `working`                                                                 |
| agent stops                                                              | `done`                                                                    |
| session ends                                                             | clear                                                                     |
| statusline render                                                        | tokens and cost, without touching the state                               |

Submissions go over a local socket, never to the terminal, so nothing the plugin does can corrupt what you see on screen. If the binary, `jq`, or the socket is unavailable, the hook exits successfully and writes nothing.

Notification and approval reason text is passed as a process argument to the local broker and truncated to 256 characters. That is visible to same-user process inspection while the helper runs. `AskUserQuestion` is the exception: question, option, and answer text never become a reason; only the literal `Input required` is submitted. Sox accepts the remaining reason path on the assumption that one operator owns the machine; the environment variables above follow the same assumption, and none of them should point anywhere you do not control.
