How it works
amx has no daemon. Each command reads the records on disk and asks tmux about the panes, answers, and exits. The view does the same once a second. Agents keep running when no amx process is alive, and killing amx kills nothing.
#One tmux session per agent
Each agent is a detached tmux session named amx-<id> with one pane, on the
tmux server you are in (or your default server when you are not in tmux). amx
brings no tmux config: the server reads your ~/.tmux.conf. Starting an agent
never changes what your own tmux client is showing.
amx is never between you and the agent. amx attach and enter in the view
are tmux switching or attaching your client. amx addresses panes by tmux pane
id, which is why it needs tmux 3.2 or newer.
#Starting an agent
amx new does this:
- Picks an id from the task (
port-the-importer-k3f), or uses--name. - Cuts a worktree if there is one to cut, and runs the
copy,linkandsetupconfig steps in it. - Writes the record:
meta.json(how it was started),handoff.json(the task and the vendor's argv), and a one-time copy of your environment. - Opens a detached tmux session that runs
amx _boot <id>.
_boot restores the environment, deletes that copy, and runs the vendor
through sh -c, followed by amx _exit <id> <status> so the record learns
how the process ended. The pane's environment carries AMX_ID, AMX_DIR and
AMX_AGENT_DIR so everything running inside it can find the record.
The underscore verbs are amx calling itself and are hidden from --help:
| Verb | Called by | Does |
|---|---|---|
_boot |
the new pane | Restores the environment and runs the agent. |
_exit |
the pane's shell, after the agent exits | Records the exit status. |
_hook |
the vendor's hooks, plugin or extension | Records one event from stdin. |
_park |
a tmux timer set when an agent goes idle | Closes the pane of an agent idle for park_after seconds, if nobody is attached. |
#Hooks versus the screen
amx learns what an agent is doing in two ways.
Hooks. The file amx setup installed makes the vendor call amx _hook
at each moment: session started, prompt submitted, tool called, question
asked, turn ended. Each call appends a line to events.jsonl and updates
state.json. The turn-end event carries the agent's answer, which is what
amx result prints. The hook finds its agent through AMX_ID in the pane's
environment, or through the session id for an adopted agent.
The screen. When hooks are missing or have gone quiet, amx captures the
pane and matches it against rules for that vendor: which rows mean a spinner,
a prompt, a permission box, a menu. The rules are TOML files compiled into the
binary (assets/screen-rules*.toml), measured against real screens of a
specific vendor version. docs/*-screens.md in the repository holds the
captures they were measured on.
Hooks are better: they carry the answer and the question's choices, and they cannot misread a screen. The screen covers the gaps, such as an agent whose wiring is missing, a vendor screen that fires no event, or a vendor like codex that sends no event for an interrupted turn.
#How a state is decided
Every reading works the state out at that moment, in this order:
- The record says it ended. An exit status or a stop was written. That is final.
- The pane is gone. No pane means
stopped, unless amx parked it on purpose, in which case the agent keeps its idle state. - The hooks are fresh. An event from the last 8 seconds is trusted as is. pi's extension and opencode's plugin also touch a heartbeat file during long tool calls, which counts as a fresh event.
- The screen matches a rule. The captured pane is matched against the vendor's rules.
- Nothing matches. The state is
unknown, with how long since the agent was last heard from. On a vendor with hooks, a record the hooks left atidleorwaitingkeeps that state instead.
amx status <id> prints which of these it used, and evidence in the JSON
says the same.
The row's text comes from the newest source available: pi and opencode's live
stream while a turn runs, else the last line of the vendor's transcript, else
the last hook (Running Bash).
#Screens in front of the work
Some vendor screens appear before the session starts, so no hook can report them: the folder-trust question, a first-run setup, codex's hooks-review screen, an update prompt. An agent stopped on one would wait forever for a caller that never attaches.
amx handles them three ways:
- The screen rules recognise them, so the row reads
waitingwith the question, andamx answeror the view can answer it. trust = trueanswers the folder-trust screen for worktrees amx cuts, before the agent starts. See Worktrees.amx doctorfails itsgatecheck while any agent is stuck on such a screen, andamx --dir <path> doctortells you in advance whether an agent started there would meet the folder-trust screen.
#What is on disk
Everything amx keeps is under ~/.local/state/amx:
~/.local/state/amx/
agents/<id>/
meta.json how it was started: task, command, directory, worktree, branch, base
state.json what it is doing, as the last event left it
events.jsonl every event, one JSON object per line, in arrival order
handoff.json the task and argv the pane was started with
output what a command row printed (or the first 64 KB of an agent's pane)
pr.json the last pull request lookup for its branch
summary.asked the last summary_command request, when that key is set
scratch/ $AMX_AGENT_DIR, the agent's own directory
view.json the view's arrangement and recent tasks and replies
visited.json the last 20 agents you went into, for attach --last
allowed/ copies of project config files you allowed
models/ cached model lists from pi and codex
Records are written under a lock, one writer at a time; readers never lock and
never see a half-written file. Each agent's directory is created 0700, so
only your user can read its task and answers.
amx ls deletes records of agents that finished (done or failed) more than a
week ago. Stopped agents are kept, since the record names the branch they left,
and so is any record whose worktree is still on disk. amx stop --delete,
clear and sweep remove records on request.
Deleting view.json or visited.json loses only the view's arrangement and
the --last trail. Deleting a file in allowed/ un-allows that project file.
claude, pi and codex keep their transcripts where they always do (for example
~/.claude/projects/ and ~/.pi/agent/sessions/), and amx reads them in
place. opencode's plugin writes its message list into the record, as
opencode-messages.jsonl.
#Privacy
amx has no network code and sends nothing anywhere. Everything it records
stays in ~/.local/state/amx on your machine.
It does run other programs that may use the network:
| Program | When |
|---|---|
| Your agent CLIs | Always; they talk to their model providers as usual. |
gh or glab |
To read pull requests for the PR column, sweep and new --pr. |
git fetch |
For new --pr, new --branch on a branch only origin has, sweep, and every five minutes per repository while the view is open. |
summary_command, on_* commands |
Whatever you configured them to run. |
Nothing else is contacted, and there is no telemetry.