A local tmux based agentic message queue for repository worker agent coordination.
Find a file
repo-manager 04de219061
Some checks failed
tamq-ci / test (push) Failing after 6s
chore(registrar): assign State Hub identifiers
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
2026-08-26 08:11:58 +02:00
.claude/rules feat: make sessions terminal neutral 2026-08-24 20:10:08 +02:00
.forgejo/workflows feat: complete reliable coordination adapter 2026-08-26 08:11:09 +02:00
spec feat: complete reliable coordination adapter 2026-08-26 08:11:09 +02:00
src/tamq feat: complete reliable coordination adapter 2026-08-26 08:11:09 +02:00
tests feat: complete reliable coordination adapter 2026-08-26 08:11:09 +02:00
workplans chore(registrar): assign State Hub identifiers 2026-08-26 08:11:58 +02:00
.custodian-brief.md chore(consistency): sync task status from DB [auto] 2026-08-26 08:08:37 +02:00
.gitignore chore: register repository governance baseline 2026-08-24 14:16:11 +02:00
.repo-classification.yaml chore: register repository governance baseline 2026-08-24 14:16:11 +02:00
AGENTS.md docs(agents): repoint remote State Hub URL to the in-cluster address 2026-08-25 00:22:17 +02:00
CLAUDE.md chore: register repository governance baseline 2026-08-24 14:16:11 +02:00
INTENT.md feat: make sessions terminal neutral 2026-08-24 20:10:08 +02:00
Makefile feat: add make cleanup target 2026-08-25 22:25:54 +02:00
pyproject.toml Bootstrap tamq local tmux agent message queue 2026-08-24 01:18:40 +02:00
README.md feat: complete reliable coordination adapter 2026-08-26 08:11:09 +02:00
SCOPE.md feat: complete reliable coordination adapter 2026-08-26 08:11:09 +02:00
TamqMessagingIntroduction.md feat: complete reliable coordination adapter 2026-08-26 08:11:09 +02:00
uv.lock Bootstrap tamq local tmux agent message queue 2026-08-24 01:18:40 +02:00
WORK-RECORDS.md feat: complete reliable coordination adapter 2026-08-26 08:11:09 +02:00

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.

Install and start a local session

Prerequisites must be available on PATH: Python 3.11+, uv, tmux, and gita. Each repository must already have an exact gita slug and path.

Install or upgrade tamq as an isolated user tool:

make install
command -v tamq
tamq --version

Start an attached two-repository shell session:

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:

tamq start flex-auth audit-core

Set up the session without attaching, then attach later:

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:

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:

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:

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:

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:

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:

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:

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:

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:

tamq send 'To:audit-core: please review the auth boundary'

With normal output delivery, the target pane visibly receives:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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

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:

[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:

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.