Documentation
¶
Overview ¶
Package courier delivers agents-board commands into the recipient agent's own session — beside its keyboard, never through it (design 050, polling-stream).
A command (msg.BoardKindCmd) is a board post addressed to one pane. The courier hears it on the board, works out how that pane's agent takes messages from outside, and hands it over:
- claude-inbox: one JSON line on the claude session's own inbox socket (Claude Code cross-session messaging). An idle claude starts a turn; a busy one reads it between tool calls.
- codex-queue: `codex queue --thread <id> --message <text>`. The running codex TUI runs it at its next idle point.
- rysh-native: a prompt on the pane's inbox subject, for a pane whose agent IS rysh (no foreground program).
THE ONE RULE: there is no fallback to typing into the pane. The pane's PTY belongs to the human, and the input-stream path that types into it erases whatever the human was writing (design 050 §1). A command the courier cannot deliver is reported on the board as undelivered, where the human sees it.
Index ¶
- Constants
- Variables
- func CatchUp(r *board.QueryReply, me, boardID string, delivered map[string]bool) []*msg.MsgBoardPost
- func CodexBin() (string, error)
- func Compose(boardID string, p *msg.MsgBoardPost, bin, session string) string
- func DeliveredKey(boardID string, p *msg.MsgBoardPost) string
- func Dispatch(ctx context.Context, pub *msg.NATSPublisher, r Route, t Target, text string) error
- func IsSocket(path string) bool
- func ListenerFor(ev board.Event, me, boardID string) bool
- func LockPane(path string) (f *os.File, ok bool, err error)
- func SendClaudeInbox(ctx context.Context, socket, text string) error
- func SendCodexQueue(ctx context.Context, bin, thread, text string) error
- func SocketDirs() []string
- type Courier
- type DeliveredLog
- type Deps
- type Route
- type Target
Constants ¶
const ( // MetaClaudeInbox is a claude's inbox socket path, stamped by a SessionStart // hook fleetctl installs (`$CLAUDE_CODE_MESSAGING_SOCKET`). Preferred over // guessing from the pid because claude can bind elsewhere on a collision. MetaClaudeInbox = "claude.inbox" // MetaCodexSession is the codex thread id the daemon discovers (design 029). MetaCodexSession = "codex.session_id" // MetaFleetComm is the fleet's comm mode (design 050): "input-stream" panes // are typed into by ANSA and never receive courier deliveries. MetaFleetComm = "fleet.comm" // MetaFleetAgent is what fleetctl launched in the pane: claude|codex|rysh. MetaFleetAgent = "fleet.agent" )
Pane meta keys the courier reads.
const ( RouteClaudeInbox = "claude-inbox" RouteCodexQueue = "codex-queue" RouteRyshNative = "rysh-native" )
Route names.
const ( CommQueue = "queue" CommGrid = "grid" CommPolling = "polling-stream" // = grid CommInputStream = "input-stream" )
Fleet comm modes (design 050/051), as stamped in pane meta fleet.comm.
- queue: the default since design 051. No central push: every agent runs its OWN listener (`rysh board listen`) that watches the board and takes the prompts addressed to it. The courier stays out of these panes.
- grid: point-to-point. The daemon's courier writes each prompt straight into the recipient's inbox. "polling-stream" is its pre-051 name.
- input-stream: ANSA types into the pane. Neither courier nor listener.
const Identity = "courier"
Persona/PaneID the courier posts under. Not a pane uuid on purpose: the board store files a post whose PaneID does not own the thread as a reply, so the notice lands under the command, and no agent's inbox counts it as an answer.
const UndeliveredKind = "undelivered"
UndeliveredKind is the kind of the board post the courier files on a command's thread when it gives up.
Variables ¶
var DefaultBackoff = []time.Duration{time.Second, 2 * time.Second, 5 * time.Second, 15 * time.Second, 60 * time.Second}
DefaultBackoff is design 050 §5.2's schedule.
var ErrPermanent = errors.New("permanent")
ErrPermanent wraps a planning failure that waiting will not fix.
var ListenerBackoff = []time.Duration{ time.Second, 2 * time.Second, 5 * time.Second, 15 * time.Second, 30 * time.Second, time.Minute, 2 * time.Minute, 5 * time.Minute, }
ListenerBackoff is longer than the courier's: a queue listener's agent may simply be restarting, and the prompt is safe on the board meanwhile. After it runs out the listener files `undelivered`; a restarted listener still catches the prompt up, because it remains open on the board.
Functions ¶
func CatchUp ¶
func CatchUp(r *board.QueryReply, me, boardID string, delivered map[string]bool) []*msg.MsgBoardPost
CatchUp returns the prompts still OPEN for me on a board — never acked or answered by me, and not already delivered by an earlier listener — oldest first, so a listener that starts late hands over the backlog in order.
func Compose ¶
func Compose(boardID string, p *msg.MsgBoardPost, bin, session string) string
Compose is the text the agent receives: the command, who sent it on which board, and how to answer. The answer instruction is what turns a delivered message into a closed loop — the sender learns it was done from the ack.
The ack instruction is COMPLETE — binary, session, board and identity are all spelled out — because the agent's own shell cannot be trusted to know them. Found live (design 050 P2): codex runs commands in an environment that does not carry $RYSH_PANE/$RYSH_SESSION, and spent a minute rediscovering who and where it was before it could ack. bin is the daemon's own absolute executable: a bare "rysh" resolves through the agent's PATH, which may hold an older build with no `board` verb.
func DeliveredKey ¶
func DeliveredKey(boardID string, p *msg.MsgBoardPost) string
DeliveredKey identifies one prompt on one board: (board, sender, cmd id).
func Dispatch ¶
Dispatch performs one delivery over a planned route. rysh-native is the only route that touches a pane subject, and it is the pane's prompt INBOX — the same non-PTY door fleetctl's native kickoff uses — never rawinput.
func ListenerFor ¶
ListenerFor reports whether a board event is a prompt for this listener's agent on this listener's board. Everything else — status posts, prompts for other agents, other boards — is somebody else's, and passes.
func LockPane ¶
LockPane takes the per-pane listener lock, so one agent has exactly one listener. ok=false means another listener holds it. The returned file must stay open for as long as the lock is wanted; the OS drops it on exit.
func SendClaudeInbox ¶
SendClaudeInbox writes one user message to a claude session's inbox socket.
The wire format is Claude Code's own (cross-session messaging): one JSON line, {"type":"user","message":{"role":"user","content":"..."}}. The optional {"type":"auth"} line is omitted — it is optional on macOS and Linux, and its token is only exported to the session's own children, which the daemon is not. Fleet panes run with crossSessionInbound=accept, so the message is delivered rather than held behind an approval dialog in the composer.
Claude closes a connection that sends no complete line within 30s, so the message is fully built before dialling, as its docs ask.
func SendCodexQueue ¶
SendCodexQueue queues a user turn on a running codex thread: `codex queue --thread <id> --message <text>`. The interactive TUI attached to that thread runs it at its next idle point. Success is codex's own receipt ("Queued message … for thread …"), not merely exit 0.
func SocketDirs ¶
func SocketDirs() []string
SocketDirs are where Claude Code binds inbox sockets, in the order it tries them: the shared dir, then the per-user fallback it uses when it cannot accept the shared one.
Types ¶
type Courier ¶
type Courier struct {
// contains filtered or unexported fields
}
Courier holds one FIFO queue per recipient: commands to one agent are delivered in the order they were posted, one at a time, so a burst cannot trip the recipient's inbox limits and a slow delivery to one agent never delays another.
func (*Courier) Close ¶
func (c *Courier) Close()
Close stops every queue and waits for in-flight deliveries to return.
type DeliveredLog ¶
type DeliveredLog struct {
// contains filtered or unexported fields
}
DeliveredLog is the listener's record of what it has handed over, one key per line, append-only. It is per agent pane, in the listener's state dir.
func NewDeliveredLog ¶
func NewDeliveredLog(path string) *DeliveredLog
func (*DeliveredLog) Load ¶
func (d *DeliveredLog) Load() map[string]bool
Load reads every recorded key. A missing file is an empty record.
func (*DeliveredLog) Record ¶
func (d *DeliveredLog) Record(key string)
Record appends one key. Best effort: a failure costs a possible duplicate after a restart, never a lost prompt.
type Deps ¶
type Deps struct {
// Snapshot reads the recipient pane now.
Snapshot func(paneID string) (Target, error)
// Deliver performs one delivery over a planned route.
Deliver func(ctx context.Context, r Route, t Target, text string) error
// Notify publishes a courier post (undelivered) on a board.
Notify func(boardID string, p *msg.MsgBoardPost)
// IsSocket defaults to IsSocket.
IsSocket func(string) bool
// Bin and Session are named in the answer instruction (Compose).
Bin string
Session string
// AsListener marks a per-agent queue listener (design 051) rather than the
// daemon's central courier. The courier skips queue panes, because their
// own listener takes their prompts; a listener delivers to them.
AsListener bool
// Identity is who files undelivered notices; defaults to Identity.
Identity string
// Delivered, when set, is told about every successful delivery — the
// listener records it so a restart does not deliver the same order twice.
Delivered func(boardID string, p *msg.MsgBoardPost, r Route)
// Backoff is the wait before each retry; len(Backoff)+1 attempts in all.
Backoff []time.Duration
// Log defaults to slog.Default().
Log *slog.Logger
}
Deps is everything the courier needs from the daemon. Injected so the queue, retry and refusal logic is testable without a daemon, a socket or codex.
type Route ¶
Route is a planned delivery: which adapter, and its address.
type Target ¶
type Target struct {
PaneID string
Program string // foreground executable, "" at the shell prompt
PGID int // foreground process group, 0 when Program is ""
Meta map[string]string
}
Target is what the courier knows about a recipient pane at delivery time.
func SnapshotVia ¶
func SnapshotVia(pub *msg.NATSPublisher, paneID string) (Target, error)
SnapshotVia reads a pane's foreground program, pgid and meta over the bus — the one look the courier and a listener both take before every delivery.