tmux push notifications¶
CAFleet uses a pull-based delivery model by default: recipients discover
messages via cafleet message poll. To reduce latency, the broker keystrokes
a 2-line inline preview ([cafleet msg …] header + truncated body) into the
recipient's tmux pane immediately after persisting a message, so the
recipient's coding-agent process consumes the preview as a fresh user-turn
input without invoking cafleet message poll. The poll-trigger keystroke
(which DOES inject a literal cafleet message poll command) is reserved for
the Director-issued cafleet member ping manual nudge — not the broker's
auto-fire path.
Send + push notification¶
%%{init: {'theme': 'default', 'sequence': {'actorFontSize': 18, 'messageFontSize': 16, 'noteFontSize': 16, 'wrap': true, 'width': 180}}}%%
sequenceDiagram
autonumber
participant Sender
participant Broker
participant DB as SQLite
participant Pane
participant Recipient
Sender->>Broker: cafleet message send --to <recipient-id> --text <body>
Broker->>DB: INSERT tasks (status=input_required)
Broker->>DB: SELECT placement.tmux_pane_id
DB-->>Broker: pane_id
Broker->>Pane: keystroke inline preview
Pane-->>Recipient: text appears as user-turn input
Recipient->>DB: message ack → status=completed
After the broker saves a delivery task, it looks up the recipient's
agent_placements row. Every agent spawned by cafleet member create has a
placement row, and every fleet's root Director also gets one at
cafleet fleet create time (its placement carries director_agent_id=NULL
to indicate "no parent"). Because the recipient pane is resolved by
agent_id alone, Member → Director notifications work automatically once
the root Director has a placement row. If the recipient has a non-null
tmux_pane_id and is not the sender, the broker keystrokes an inline
preview of the message itself into the recipient's pane:
The recipient's coding agent processes the keystroked text as a fresh
user-turn input — no cafleet message poll invocation is in the auto-fire
path. The recipient acks via cafleet message ack --task-id <task_id> once
it has consumed the message.
If the recipient's TUI is in a non-input state, the keystroked preview lands
wherever the cursor is (the same failure mode any pane keystroke has). The
fallback chain is cafleet member list --activity (the Director observes the
recipient's last_recv column went stale), then
cafleet member ping --member-id <recipient-id> (a manual re-poke that injects the
cafleet message poll command + Enter so the recipient catches up via a
normal message poll round-trip).
Design principles¶
- Best-effort: The message queue remains the sole source of truth. Push notification is an optimization — if it fails, the message is still available for normal polling.
- Self-send skip: When sender == recipient, the notification is suppressed.
- Silent failure: Missing placements, null
tmux_pane_id, dead panes, and absenttmuxbinary all result in no notification — no exceptions propagate to the caller. - No
TMUXenv var required:tmux send-keys -t <pane>works from any process on the same host as long as the tmux server socket is accessible.
Response annotations: Unicast responses include a top-level
notification_sent boolean. Broadcast responses expose
notifications_sent_count as a top-level wrapper field (returned alongside
the broadcast_summary task), reflecting how many recipient panes were
successfully triggered. The count is NOT persisted — it lives only in the
broker return value.
Manual entry-point: cafleet member ping is the manual poll-nudge path:
it injects the poll command + Enter into a member's pane and converts a
failure to exit 1, for an operator or monitoring loop that needs the
recipient to drain whatever has accumulated after a missed inline preview.
Body truncation in the preview (the … suffix at CAFLEET_MAX_TEXT_LEN
codepoints) is documented in
CLI options.