Some checks failed
tamq-ci / test (push) Failing after 5s
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
417 lines
15 KiB
Markdown
417 lines
15 KiB
Markdown
# tmux-amq
|
|
|
|
Tmux Agentic Message Queueing (`tamq`) provides repository-aware tmux sessions
|
|
and a local, durable message queue. It does not assume who or what uses a pane.
|
|
|
|
The local alpha provides tmux endpoint lifecycle, local SQLite history and
|
|
leases, readable `To:` routing, JSONL export/replay, bounded delivery state, and
|
|
a versioned Unix-socket coordination-engine adapter.
|
|
|
|
Agents and operators should start with the concise
|
|
[TAMQ messaging introduction](TamqMessagingIntroduction.md).
|
|
|
|
## Install and start a local session
|
|
|
|
Prerequisites must be available on `PATH`: Python 3.11+, [uv](https://docs.astral.sh/uv/),
|
|
tmux, and gita. Each repository must already have an exact gita slug and path.
|
|
|
|
Install or upgrade `tamq` as an isolated user tool:
|
|
|
|
```bash
|
|
make install
|
|
command -v tamq
|
|
tamq --version
|
|
```
|
|
|
|
Start an attached two-repository shell session:
|
|
|
|
```bash
|
|
tamq flex-auth audit-core
|
|
```
|
|
|
|
This creates or reuses the managed `tamq` session, opens same-named windows in
|
|
the exact gita paths, starts an ordinary shell behind tamq's transparent PTY
|
|
observer, and attaches to the first window. The explicit form is equivalent:
|
|
|
|
```bash
|
|
tamq start flex-auth audit-core
|
|
```
|
|
|
|
Set up the session without attaching, then attach later:
|
|
|
|
```bash
|
|
tamq start --detach flex-auth audit-core
|
|
tamq status
|
|
tamq attach
|
|
```
|
|
|
|
No initial command runs unless requested. To run one explicitly in each newly
|
|
created window, use `--command`; tamq runs exactly that command and makes no
|
|
assumption about its purpose:
|
|
|
|
```bash
|
|
tamq --command codex flex-auth audit-core
|
|
tamq start --command 'htop --tree' flex-auth audit-core
|
|
```
|
|
|
|
`--cmd` remains an alias for `--command`. Repeated starts reuse existing
|
|
windows and never run another initial command in them.
|
|
|
|
Select an endpoint delivery mode at startup. `output` is the safe default;
|
|
`inbox` keeps messages durable without displaying them:
|
|
|
|
```bash
|
|
tamq --mode output flex-auth audit-core
|
|
tamq --mode inbox flex-auth audit-core
|
|
```
|
|
|
|
`--no-display` remains a compatibility alias for `--mode inbox`.
|
|
|
|
Experimental `pushy` mode places a routed message in the target input buffer
|
|
without Enter. `trigger` places the same input and submits it exactly once:
|
|
|
|
```bash
|
|
tamq --mode pushy --command codex flex-auth audit-core
|
|
tamq --mode trigger --command codex flex-auth audit-core
|
|
```
|
|
|
|
All messaging-enabled windows use the same terminal-neutral PTY observer,
|
|
whether they contain a shell, a tool, or an explicitly selected coding agent.
|
|
An operator submits one addressed line:
|
|
|
|
```text
|
|
To:audit-core: Please review the authentication change.
|
|
```
|
|
|
|
Protocol keywords are case-insensitive, so `to:`, `TO:`, `cmd:`, and their
|
|
mixed-case forms work as well. Repository slugs remain exact. For full-screen
|
|
terminal programs, tamq also recognizes `To:` immediately after a conservative
|
|
worker-output gutter such as the `•` used to frame assistant output; operator
|
|
prompt gutters are not worker output.
|
|
|
|
A worker may extend its addressed first line with non-empty follow-up output.
|
|
The first empty line terminates and sends the block:
|
|
|
|
```text
|
|
To:audit-core: Please review the authentication change.
|
|
Context: AUTH-WP-0004-T02.
|
|
Expected reply: accepted or blocked.
|
|
|
|
```
|
|
|
|
Operator input is forwarded unchanged and is delivered with `/o`; a line
|
|
originating in worker output has no suffix:
|
|
|
|
```text
|
|
From:flex-auth/o: Please review the authentication change.
|
|
From:flex-auth: Worker-generated message.
|
|
```
|
|
|
|
Every delivered physical line carries the same `From:` prefix and is never
|
|
routable. The observer suppresses recent exact operator lines
|
|
when a TUI echoes or redraws them, so the echo cannot become a second worker
|
|
message. Ambiguous exact echoes fail closed. Pushy and trigger still cannot
|
|
infer the foreground program or protect input already being edited, so both
|
|
remain explicit experiments. Startup requires the readable-duplex, trigger,
|
|
worker-block, and line-limit capabilities and replaces incompatible old broker
|
|
or tap processes.
|
|
|
|
## Exchange messages manually
|
|
|
|
Each managed window has its own repository identity. From operator input in the
|
|
`flex-auth` window, queue a message with:
|
|
|
|
```bash
|
|
To:audit-core: please review the auth boundary
|
|
```
|
|
|
|
The same line at the start of worker output begins a worker-originated message
|
|
block. Non-empty follow-up rows are included until an empty row, a new `To:`
|
|
line, or worker exit. The keyword may use any letter case, and a recognized
|
|
full-screen output gutter may precede it.
|
|
The `@`, `#`, reply shorthand, and interactive recipient composer from earlier
|
|
alphas have been removed.
|
|
|
|
Operator input can change allowlisted runtime state:
|
|
|
|
```text
|
|
Cmd: mode=trigger
|
|
Cmd: maxmsg=16
|
|
Cmd: maxin=2048
|
|
Cmd: maxout=65536
|
|
Cmd: reset-limits
|
|
```
|
|
|
|
`Cmd:` is recognized only on the operator-input path; identical worker output
|
|
is inert. The transparent input contract means the line also remains visible
|
|
to the foreground program. Small tamq-owned shell absorbers prevent ordinary
|
|
shells from reporting `To:repo:` and `Cmd:` as missing commands; tamq refuses
|
|
to overwrite unrelated files. The explicit CLI send form is:
|
|
|
|
```bash
|
|
tamq send 'To:audit-core: please review the auth boundary'
|
|
```
|
|
|
|
With normal `output` delivery, the target pane visibly receives:
|
|
|
|
```text
|
|
From:flex-auth/o: please review the auth boundary
|
|
```
|
|
|
|
This uses the pane's tmux-reported `/dev/pts/<number>` device—the same Unix
|
|
terminal-output mechanism underlying tools such as `write(1)`. For an ordinary
|
|
shell with screen rows above its cursor, tamq confines scrolling to those rows,
|
|
writes the delivery immediately above the input row, and restores the cursor.
|
|
Thus a partially typed command remains in place. If no safe row exists, tamq
|
|
uses ordinary line output; alternate-screen programs receive the conservative
|
|
fallback and may redraw over it. Neither path uses `send-keys`, sends Enter, or
|
|
places bytes on the foreground process's stdin, so the durable inbox remains
|
|
authoritative.
|
|
|
|
In the `audit-core` window, inspect and acknowledge it:
|
|
|
|
```bash
|
|
tamq inbox
|
|
tamq ack <message-id>
|
|
```
|
|
|
|
Human inbox output uses the same framing. Use `--json` or `tamq history` to get
|
|
the durable id needed by `ack`:
|
|
|
|
```text
|
|
From:flex-auth/o: please review the auth boundary
|
|
```
|
|
|
|
### Reviewing the communication protocol
|
|
|
|
TAMQ automatically appends structured protocol events to the local database.
|
|
The ledger includes addressed-message acceptance, operator/worker provenance,
|
|
worker block boundaries and termination reasons, allowlisted command outcomes,
|
|
line-limit blocks, endpoint lifecycle, and delivery attempts or failures. It
|
|
does not capture unrelated pane output or ordinary shell input.
|
|
|
|
Create a review artifact with:
|
|
|
|
```bash
|
|
tamq capture --output tamq-protocol.md
|
|
tamq capture --repo audit-core --output audit-core-protocol.md
|
|
tamq capture --message-id m-... --format jsonl
|
|
tamq capture --event delivery.failed
|
|
```
|
|
|
|
Markdown is the human-oriented default and starts with aggregate counts useful
|
|
for spotting protocol friction. JSONL is intended for scripts. The default is
|
|
the newest 500 matching events; `--limit` changes that bound. Repository filters
|
|
match either side of an exchange. A message body is included only on its
|
|
acceptance event, not repeated for every delivery event. Captures are durable
|
|
communication evidence, so never put credentials or secrets in TAMQ messages.
|
|
When upgrading an already-running alpha, recreate the managed session once so
|
|
its long-lived pane observers use the capture-aware build; existing durable
|
|
message lifecycle is backfilled, but old observers cannot reconstruct block or
|
|
command events retroactively.
|
|
|
|
To explicitly consume pending messages through a command, use an inbox filter:
|
|
|
|
```bash
|
|
tamq inbox --filter 'cat >> msg.log'
|
|
```
|
|
|
|
The command runs once per pending message with the readable `From:` form on standard
|
|
input. `TAMQ_MESSAGE_ID`, `TAMQ_SENDER_REPO`, and `TAMQ_TARGET_REPO` are set in
|
|
its environment. A zero exit acknowledges that message; a non-zero exit leaves
|
|
it and all later messages pending. Filters never run in the background and
|
|
cannot be combined with `--all` or `--json`.
|
|
|
|
Outside a managed window, use `tamq inbox --repo audit-core` and optionally
|
|
`--json`. Neither `output` nor `inbox` mode injects terminal keystrokes. Pushy
|
|
places input without Enter. Trigger waits for terminal paste detection to settle
|
|
and then adds exactly one Enter. A short endpoint-startup grace protects the
|
|
first input delivery while the foreground program initializes.
|
|
|
|
The policy profile controls completion. The default `injected` policy completes
|
|
after a successful terminal write. `acknowledged` keeps the message in
|
|
`awaiting_ack`, visible in the inbox, and redelivers the same ID after the ack
|
|
deadline. Delivery failures and expired leases use bounded backoff; exhaustion
|
|
becomes `failed`. Inspect the attempt count and reason, then explicitly reset a
|
|
terminal message if retry is safe:
|
|
|
|
```bash
|
|
tamq inspect m-...
|
|
tamq retry m-...
|
|
```
|
|
|
|
Acknowledged-mode redelivery can create duplicate terminal presentation. A late
|
|
ack wins even after exhaustion. Neither state proves that an agent completed the
|
|
requested work.
|
|
|
|
Every session window has independent, session-lifetime running counters for
|
|
accepted outbound messages, operator input lines, and normalized worker output
|
|
lines. Defaults are 8, 1024, and 32768 respectively:
|
|
|
|
```bash
|
|
tamq --maxmsg 16 --maxin 2048 --maxout 65536 flex-auth audit-core
|
|
```
|
|
|
|
The same defaults can be set as `maxmsg`, `maxin`, and `maxout` under `[tamq]`
|
|
in the configuration file. A message is rejected once any counter is equal to
|
|
its limit. Tamq displays the applicable count and `Cmd: reset-limits` resets all
|
|
three counters for the current window. Counters survive tap and broker restarts
|
|
within the same managed-session generation and are visible in `tamq status`.
|
|
|
|
After upgrading from an earlier alpha, recreate the managed session once so
|
|
existing panes inherit the neutral shell contract, repository command `PATH`,
|
|
and terminal-output broker registration:
|
|
|
|
```bash
|
|
tamq stop
|
|
tmux kill-session -t tamq
|
|
tamq flex-auth audit-core
|
|
```
|
|
|
|
For degraded tmux-only use, `tamq start --no-service ...` skips socket endpoint
|
|
registration and durable messaging. Stop the broker and remove the managed tmux
|
|
session explicitly when finished:
|
|
|
|
```bash
|
|
tamq stop
|
|
tmux kill-session -t tamq
|
|
```
|
|
|
|
For a runaway or stale installation, use the emergency cleanup command from a
|
|
terminal outside the managed session. It is a dry run unless `--yes` is given:
|
|
|
|
```bash
|
|
tamq cleanup
|
|
tamq cleanup --yes
|
|
make cleanup
|
|
```
|
|
|
|
Confirmed cleanup stops only the verified tamq broker, closes only a tmux
|
|
session carrying tamq's management marker, disconnects transient endpoints,
|
|
clears leases and line counters, removes the configured socket/PID/lock files,
|
|
deletes only tamq-generated protocol absorbers (including legacy `@repo`
|
|
shims), and removes owned stale `tamq-*` tmux sockets.
|
|
Durable message history and unrelated tmux sessions or files are preserved.
|
|
`make cleanup` is the convenient confirmed form and preserves that same scope.
|
|
The command is idempotent; an ownership mismatch is reported instead of being
|
|
removed.
|
|
|
|
History remains a separate explicit operation. A reflected delivery incident
|
|
can be selected by durable receipt direction, dry-run, and then removed without
|
|
age-based purging:
|
|
|
|
```bash
|
|
tamq purge --feedback-chain m-...
|
|
tamq purge --feedback-chain m-... --yes
|
|
```
|
|
|
|
Uninstall the user tool from the checkout with `make uninstall`, or from
|
|
anywhere with `uv tool uninstall tmux-amq`.
|
|
|
|
## Development
|
|
|
|
```bash
|
|
uv run pytest
|
|
uv run tamq --help
|
|
uv run tamq --version
|
|
uv run tamq start net-kingdom railiance-platform
|
|
uv run tamq attach
|
|
```
|
|
|
|
The alpha includes the local SQLite queue, neutral tmux endpoint manager, strict
|
|
gita target validation, manual inbox, control-mode client, and an explicit PTY
|
|
tap for integration experiments. Coordination-engine integration remains a
|
|
later phase.
|
|
|
|
## Terminal architecture
|
|
|
|
`tamq` separates terminal coordination into three layers:
|
|
|
|
1. tmux control mode is the topology and output/control stream;
|
|
2. the local broker assigns endpoint/source identity and durably queues intent;
|
|
3. `tamq tap` is a full-duplex PTY proxy around every messaging-enabled shell
|
|
or explicit command. It forwards bytes unchanged in raw terminal mode,
|
|
propagates resize and lifecycle signals, observes operator input, and
|
|
normalizes worker output for case-insensitive start-of-content `To:` routing,
|
|
including conservative full-screen worker-output gutters.
|
|
|
|
This keeps tmux-specific topology concerns separate from reusable terminal I/O
|
|
observation and message identity.
|
|
|
|
Normal endpoints use terminal-output delivery. `--mode inbox` selects
|
|
inbox-only manual mode. Neither becomes pane input. Experimental `--mode pushy`
|
|
places a sanitized `From:` block
|
|
without Enter; `--mode trigger` performs the same placement, lets paste
|
|
detection settle, and submits once.
|
|
The PTY observer records explicit operator/worker provenance and suppresses
|
|
echoed operator lines before they can route again.
|
|
The older pane-delivery experiment remains available only with the explicit
|
|
`tamq start --tap --command ...` opt-in. Both input paths use the same bounded
|
|
retry and acknowledgement state machine.
|
|
The visible endpoint label is `tmux-amq-<PID>`; each boot also receives an
|
|
instance nonce so PID reuse cannot collide with prior leases or receipts.
|
|
|
|
## Configuration
|
|
|
|
Configuration is read from `${XDG_CONFIG_HOME:-~/.config}/tamq/config.toml` (or
|
|
`$TAMQ_CONFIG`). Environment variables such as `TAMQ_SOCKET` and
|
|
`TAMQ_STATE_DIR` take precedence. The purge and startup advisory defaults can
|
|
be tuned without changing command lines:
|
|
|
|
```toml
|
|
[tamq]
|
|
state_dir = "/run/user/1000/tamq"
|
|
purge_before = "365d"
|
|
purge_max_size = "100MB"
|
|
history_max_size = "100MB"
|
|
delivery_poll_interval = "0.5"
|
|
delivery_lease_seconds = "30"
|
|
delivery_retry_backoff_seconds = "5,15,60,300"
|
|
delivery_ack_timeout_seconds = "30"
|
|
maxmsg = 8
|
|
maxin = 1024
|
|
maxout = 32768
|
|
|
|
[policy.profiles.diagnostics]
|
|
safety_gated_max_attempts = 2
|
|
delivery_ack_mode = "acknowledged"
|
|
delivery_max_attempts = 4
|
|
```
|
|
|
|
Policy profiles accept one to nine delivery attempts. Lease expiry and write
|
|
failure consume an attempt; retry delay uses the configured sequence and then
|
|
repeats its last value. `delivery_ack_mode` is either `injected` or
|
|
`acknowledged`. Use `--policy-profile` to select a profile; `--orwell` enables
|
|
explicitly unsafe local diagnostics.
|
|
|
|
The Unix socket service supports structured `ping`, `register`, `send`,
|
|
`message`, `history`, `ack`, `retry`, `endpoints`, and `disconnect` operations. Endpoint
|
|
registrations and messages are persisted in the same local SQLite database;
|
|
delivery remains delegated to the control-mode adapter.
|
|
|
|
## Coordination-engine adapter
|
|
|
|
`tamq.client.CoordinationEngineAdapter` is a transport-only async adapter. It
|
|
does not import tmux/control-mode code and opens a fresh authenticated
|
|
Unix-socket connection per operation. A wake resolves exactly one live endpoint,
|
|
validates the gita target, and uses the coordination lease ID as its idempotency
|
|
key:
|
|
|
|
```python
|
|
from tamq.client import CoordinationEngineAdapter, WakeRequest
|
|
|
|
receipt = await CoordinationEngineAdapter().wake(
|
|
WakeRequest(
|
|
lease_id="lease-123",
|
|
trigger_id="task-456:r7",
|
|
target_repo="audit-core",
|
|
prompt="Resume the actionable task and publish a checkpoint.",
|
|
)
|
|
)
|
|
```
|
|
|
|
Repeating that wake returns the same message ID. Reusing its lease ID for a
|
|
different payload is rejected. Receipt recovery, explicit acknowledgement, and
|
|
terminal retry use `receipt`, `acknowledge`, and `retry_failed`. The normative
|
|
state and ownership contract is
|
|
[`spec/coordination-engine-adapter-v0.1.md`](spec/coordination-engine-adapter-v0.1.md).
|