Help us shape the future of Go! Take the 2026 Go Developer Survey

delivery

package
v0.26.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 20, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package delivery is one batch's deliver-and-confirm transaction (cli#907, C1), shared by the host poller and C2's posting CLI (#908). The server decides everything that needs no screen: recipients, filtering, rendering, batching and timing (S1, hadron-server#2017). This package decides only what the terminal shows: whether to type, and whether the text arrived.

It talks to the server through the Server seam. The GraphQL adapter arrives with S1/S5's schema on server main; nothing here depends on it.

Index

Constants

View Source
const (
	HoldDraft           = "draft"
	HoldIsolated        = "isolated"
	HoldQuestion        = "question"
	HoldScreenUnknown   = "screen-unknown"
	HoldNotSubmitted    = "not-submitted"
	HoldTwoAgents       = "two-agents"
	HoldBindingMismatch = "binding-mismatch"
)

Hold reasons: S5's closed vocabulary (hadron-server#2021).

View Source
const (
	StepRead       = "read"
	StepReadFailed = "read-failed"
	StepOffByScope = "off-by-scope"
	// WakeBudgetExhausted: the wake read answered WAKE_BUDGET_EXHAUSTED.
	WakeBudgetExhausted = "budget-exhausted"
)

How a poller step's batch read went (cli#906): the poller reports one per step, so `team host status` can tell a quiet poller from a stopped one.

View Source
const (
	TickPrefix = "[hadron tick]"
	TaskPrefix = "[hadron task]"
)

Own prefixes of a typed wake line. With Prefix they are what a deliverer recognizes as its own leftover in a composer.

View Source
const (
	WakeTyped = "typed"
	WakeAcked = "acked"
)

Wake record states (cli#906 6257108208 §1). The two are kept apart: a typed record is pending evidence and never expires by age; only its own landed ack, or a newer tick of the same ticker whose ack landed, moves it to acked. Acked records are archive, pruned after a day.

View Source
const AckTimeout = 30 * time.Second

AckTimeout bounds one acknowledgement or hold report, which is sent even while stopping.

View Source
const DefaultBudget = 2*orcaterm.CallTimeout + AckTimeout + 30*time.Second

DefaultBudget is the expiry budget: everything after the screen checks must fit inside the proof's remaining life — a send with --wait-submit and a confirm read (one orca call bound each), the record write, and the ack — plus slack. A proof with less left is re-read, not typed on. This narrows the stale-proof window; it does not close it (network delay, a slow server), and a stale ack after a confirmed send is reconciled from the record, never re-typed.

View Source
const (
	// HoldTimingUnavailable: the server gave no deliverAfter, or the agent
	// is busy or unproven and it gave no busy deadline.
	HoldTimingUnavailable = "timing-unavailable"
)

Hold reasons local to a wake. A wake hold is logged and shown in status, never reported to S5 (S5's hold is a batch event).

View Source
const MaxTextRunes = 65_536 + 4_096

MaxTextRunes bounds a batch's text by S1's own rule (renderBatch.ts at server main ad7f1c2): at most 10 messages within a 6,000-character body budget, except that the first message is always delivered whole and alone, up to the 65,536-character chat body. The header, authors, parent context, overflow lines and reply hint come on top; 4,096 covers them. A longer text is a contract error. The server counts UTF-16 units, which are never fewer than runes. (Colette's LOCAL P2 on #939: the old 8,000 refused a valid whole message.)

View Source
const MaxWakeRunes = 1000

MaxWakeRunes bounds a wake line. A longer one is cut at a reason boundary and says how many reasons it left out.

View Source
const Prefix = "[hadron chat"

Prefix opens every rendered batch (D8). It is how a deliverer recognizes its own leftover in a composer: anything else there is someone's draft.

View Source
const RecordConfirmed = "confirmed"

RecordConfirmed is the only state a record is ever written in. Nothing is persisted before the delivery test succeeds: a batch read, prepared or sent without confirmation has no record, and counts as not delivered.

Variables

View Source
var ErrBusy = errors.New("another deliverer holds this worker's lock")

ErrBusy is a lock another live deliverer holds. A deliverer that finds it skips the worker this time and never waits: a post must not stall behind the poller.

View Source
var ErrHoldUnavailable = errors.New("the server has no delivery hold report")

ErrHoldUnavailable is Hold's answer when the server has no hold report (S5 not on it yet: cli#940). The adapter returns it without a call. The hold stays local: Held and unreported, never Failed, and never cached as reported, so it is reported once the capability is there.

View Source
var ErrMalformed = errors.New("malformed batch")

ErrMalformed is a batch that breaks S1's contract. It is neither typed nor acknowledged nor held (S5 has no reason for it, by design): it is logged by span and left due.

View Source
var ErrMalformedWake = errors.New("malformed wake")

ErrMalformedWake is a wake that breaks dueWakes' contract: neither typed nor acknowledged, logged by span and left due.

View Source
var OwnPrefixes = []string{Prefix, TickPrefix, TaskPrefix}

OwnPrefixes are every prefix C1 types: a leftover opening with any of them is ours (submitted or cleared), never a foreign draft.

Functions

func BoundWorker

func BoundWorker(worktree string) string

BoundWorker is the worker id a worktree's session binding names, or "".

func CheckBatch

func CheckBatch(b Batch) error

CheckBatch is the shape a batch must have before anything is typed: one line (a newline would submit early), our prefix, a bounded length, and ranges whose seqs lie inside them.

func CheckWake

func CheckWake(w Wake) error

CheckWake is the shape a wake must have before anything is typed: a proof, a worker, and reasons whose keys are complete.

func ClearKeys

func ClearKeys(leftover string) string

ClearKeys empties a composer holding leftover: one Backspace per rune. It does nothing to an empty composer, so a human who empties it between our read and our send loses nothing. Never Ctrl+C: ONE on an empty Codex composer quits Codex. Never Ctrl+U (one line only) or Esc Esc (Claude opens its Rewind dialog on an empty composer).

func KnownCode

func KnownCode(code string) bool

KnownCode reports whether code is in the delivery diagnostic vocabulary.

func RenderWake

func RenderWake(reasons []WakeReason) string

RenderWake is the one line typed for reasons (TICK and TASK only). Server-free and count-free, so it holds for a tick with no channels and for every-tick: reasons joined by "; ", cut at a reason boundary to MaxWakeRunes with the count of reasons left out.

func WithWorkerLock

func WithWorkerLock(dir, workerID string, fn func(stillOwner func() bool) error) error

WithWorkerLock runs fn holding workerID's lock under dir. fn receives stillOwner, re-checked before every send and every write, so a deliverer that somehow lost the lock stops instead of acting alongside another.

Types

type AckResult

type AckResult string

AckResult is the server's answer for one batch id.

const (
	Acked     AckResult = "acknowledged"
	Unchanged AckResult = "unchanged" // already acknowledged: a retry's success
)

type Batch

type Batch struct {
	// ID is S1's signed proof. It is sent back to ack or hold and nowhere
	// else: never logged, stored or put in argv.
	ID string
	// BatchKey is S1's stable hash over the ranges: the same messages, the
	// same key, across re-issued proofs. A lookup key, never evidence.
	BatchKey string
	WorkerID string
	Ranges   []Range
	Text     string
	// DeliverAfter has passed for every batch S1 returns; it is checked
	// anyway. BusyDeadline is DeliverAfter plus S9's busy-delay cap: a
	// working agent defers a batch until then and no later.
	DeliverAfter   time.Time
	BusyDeadline   time.Time
	ProofExpiresAt time.Time
	// Interrupt is set only for a halt (S6, phase 4): always false now.
	Interrupt bool
}

Batch is one ready-to-deliver batch as S1 renders it.

func (Batch) Span

func (b Batch) Span() string

Span is the batch's ranges for logs: channels and seqs, never text.

type Capabilities

type Capabilities struct {
	// EnterOnly: an Enter-only send submits what the composer holds.
	EnterOnly map[string]bool
	// Clear: ClearKeys empties the family's composer.
	Clear map[string]bool
}

Capabilities are what has been PROVEN on a scratch terminal, per TUI family. Until proven, the path it would enable holds instead.

func Proven

func Proven() Capabilities

Proven is what the 2026-10-18 scratch terminal proved on Claude Code 2.1.303 and Codex CLI 0.163.0 (cli#907; hadron-dev-team-joshua:work: cli907-scratch-terminal-evidence-2026-10-18): Enter alone submits an existing composer, and ClearKeys empties one, multi-line included.

type Cover

type Cover struct {
	// Full: every message of the batch was confirmed delivered. Ack it whole,
	// with no typing.
	Full bool
	// Through: the channels with a confirmed covered prefix, when not Full.
	// Channels left out are not covered and move nothing.
	Through []Through
	// Matched are the records that proved it.
	Matched []Record
}

Cover is what confirmed records prove about a batch.

func Reconcile

func Reconcile(b Batch, recs []Record) Cover

Reconcile is what confirmed records prove about b. A record applies to a channel only when the channel id AND the incarnation match: a recreated channel restarts its seqs on the same id (hadron-server src/lib/channels.ts at b8384631), so an earlier incarnation's record proves nothing. Within a channel the covered prefix is the batch's own seqs from its first, for as long as each is in one record's confirmed seqs. The first seq not in the record ends it, so a later distinct message is never acked unseen, and a batch whose first seq the record lacks gets no ack at all. (cli#907 delta 2 also asked that the record START at the batch's first seq. That only ever refused a record starting earlier, e.g. [4,5,6] against [5,6,7], which proves 5 and 6 confirmed: refusing it would retype them.) A batchKey match with a confirmed record is a full cover by S1's definition of the key.

type Deliverer

type Deliverer struct {
	Server  Server
	Orca    Terminals
	Records *Records
	// LockDir holds the per-worker locks shared with C2.
	LockDir string
	Caps    Capabilities
	// Bound is the worker a worktree's session binding names, or "".
	Bound  func(worktree string) string
	Now    func() time.Time
	Budget time.Duration
	Emit   func(Event)

	// Wakes acknowledges wake proofs; nil leaves wakes unacknowledged.
	Wakes       WakeServer
	WakeRecords *WakeRecords
	// AckMixed is set only once A′ (hadron-server#2072) is verified
	// deployed: a wake carrying a MENTION, or naming channels, may then be
	// acknowledged once all its TICK and TASK reasons are typed. Off, C1
	// acknowledges only a proof with no MENTION reason and no channels.
	AckMixed bool
	// contains filtered or unexported fields
}

Deliverer delivers batches into Orca terminals.

func New

func New(server Server, orca Terminals, stateDir string) *Deliverer

New builds a Deliverer with the production defaults.

func (*Deliverer) Deliver

func (d *Deliverer) Deliver(ctx context.Context, b Batch, r Recipient) Outcome

Deliver carries one batch to its recipient's terminal, under the worker's lock. The order is the design (cli#907 attach §2 and deltas 2–4): reconcile confirmed records first (never type what was confirmed), check the batch and the proof's remaining life, find exactly one agent terminal in a registered worktree with a matching binding, judge its composer under R2 and its liveness, then send, confirm, record, and ack.

func (*Deliverer) DeliverWake

func (d *Deliverer) DeliverWake(ctx context.Context, w Wake, r Recipient) Outcome

DeliverWake types w's not-yet-recorded TICK and TASK reasons into r's terminal as one line, records them, and acknowledges the proof when the accepted set allows it.

The order mirrors a batch's: reconcile records first (a reason already typed is never typed again), then the shared terminal path, then record every typed key before the ack. A proof is acknowledged only when ackable.

type Event

type Event struct {
	Time     time.Time
	Worker   string
	WorkerID string
	Span     string
	Kind     Kind
	Reason   string
	Terminal string
	// ResolvedBy says where the recipient terminal's agent family came
	// from: "identity" (Orca's agent identity) or "screen" (Orca reported
	// none, and the screen showed one). Empty before a terminal is found. A
	// diagnostic code only (cli#906): it grants nothing.
	ResolvedBy string
	// Wake: the event is a tick or task wake's (cli#906 row 1), not a
	// batch's. Span is then reason counts.
	Wake bool
	// Unreported: a hold the server was not told about (cli#940).
	Unreported bool
}

Event is one line of what delivery did, for the operator.

type Kind

type Kind string

Kind is a delivery's outcome.

const (
	Delivered  Kind = "delivered"  // typed and confirmed
	Reconciled Kind = "reconciled" // acknowledged from a confirmed record, nothing typed
	Held       Kind = "held"       // left due with a reason; reported to S5 unless Unreported (#940)
	Deferred   Kind = "deferred"   // left due: try again next time
	Skipped    Kind = "skipped"    // left due, without a hold (no terminal, lock busy)
	Failed     Kind = "error"      // a contract or transport error; left due
)

type Outcome

type Outcome struct {
	Kind     Kind
	Reason   string
	Terminal string
	// AckPending: delivered and recorded, but the ack did not land; the
	// record reconciles it next time without typing.
	AckPending bool
	// Unreported: Held, but the server was not told (ErrHoldUnavailable).
	// No server hold, episode, alarm or escalation exists for it.
	Unreported bool
}

Outcome is what one Deliver did. Reason is a code; nothing here carries text, a proof or screen content.

type Range

type Range struct {
	ChannelID   string `json:"channelId"`
	Incarnation int    `json:"incarnation"`
	FromSeq     int    `json:"fromSeq"`
	ThroughSeq  int    `json:"throughSeq"`
	Seqs        []int  `json:"seqs"`
}

Range is one channel's part of a batch (S1 #2017 issuecomment-6246620593). Seqs are the exact message seqs rendered, at most ten across the batch.

type Recipient

type Recipient struct {
	WorkerID string
	Name     string
	// TUI is the agent interface its terminal must show ("claude" or
	// "codex"), when the host knows it. Empty means the screen's own family
	// and Orca's agent identity must agree instead.
	TUI string
	// Worktrees are its registered worktrees, absolute.
	Worktrees []string
	// contains filtered or unexported fields
}

Recipient is a worker registered on this host, as the poller's latest registration resolved it.

type Record

type Record struct {
	State       string    `json:"state"`
	WorkerID    string    `json:"workerId"`
	BatchKey    string    `json:"batchKey,omitempty"`
	Ranges      []Range   `json:"ranges"`
	ConfirmedAt time.Time `json:"confirmedAt"`
}

Record is submitted evidence for one batch: written after a confirmed send and before its ack, deleted once the ack lands. It holds no text, no proof and no batch id.

type Records

type Records struct {
	Dir string
}

Records is the confirmed-delivery store: one 0600 file per batch under a 0700 directory per worker. Files are written by atomic rename and named by a hash of the ranges, so a writer that lost its lock never clobbers another deliverer's record.

func (*Records) Delete

func (s *Records) Delete(workerID string, ranges []Range) error

Delete removes a record once its ack has landed.

func (*Records) List

func (s *Records) List(workerID string) ([]Record, error)

List is the worker's confirmed records. A file that does not parse as a confirmed record for this worker is ignored: only typed, confirmed evidence may suppress a send.

func (*Records) Prune

func (s *Records) Prune(workerID string, cutoff time.Time) error

Prune drops records confirmed before cutoff: past the proof's lifetime a record can only match a re-hand, and S1 re-hands within minutes.

func (*Records) Put

func (s *Records) Put(b Batch, at time.Time) error

Put records a confirmed delivery.

func (*Records) Retire

func (s *Records) Retire(workerID string, rec Record, acked []Range) error

Retire removes from rec the messages an ack just covered (acked, the acknowledged batch's ranges) and keeps the rest: a record can confirm seqs or channels the acknowledged batch did not carry, and those are still owed their own ack (Jane's P2 on #939). The remainder keeps rec's confirmation time and drops its batchKey, which named the whole. It is written before rec is removed, so a crash between leaves both, which only proves the same messages twice. A record with nothing left is deleted.

type Server

type Server interface {
	Ack(ctx context.Context, batchID string, through []Through) (AckResult, error)
	Hold(ctx context.Context, batchID, reason, terminalID string) error
}

Server is S1's acknowledgement and S5's hold, as delivery consumes them. An Ack error of any kind (a refusal included: BAD_USER_INPUT, WAKE_PROOF_STALE) means nothing moved, so a confirmed record is kept.

type Status

type Status struct {
	Version    int                     `json:"version"`
	LastStepAt time.Time               `json:"lastStepAt"`
	LastRead   string                  `json:"lastRead"`
	Workers    map[string]WorkerStatus `json:"workers"`
	// The newest wake read (cli#906 row 1): its code, and dueWakes' counts.
	WakeRead          string     `json:"wakeRead,omitempty"`
	BudgetExhaustedAt *time.Time `json:"budgetExhaustedAt,omitempty"`
	Dormant           int        `json:"dormant"`
	Withheld          int        `json:"withheld"`
}

Status is the poller's local delivery snapshot. It is observational: what this host's poller last saw, never authoritative delivery health (that is S5's). It holds no text, proof, draft or batch id.

func ReadStatus

func ReadStatus(stateDir string) (Status, error)

ReadStatus reads the snapshot under stateDir. A missing file is an fs.ErrNotExist error; a corrupt one is any other error.

type StatusBook

type StatusBook struct {
	// contains filtered or unexported fields
}

StatusBook keeps the snapshot as events arrive and writes it after each poller step. Safe for concurrent use: deliveries for different workers run at once.

func NewStatusBook

func NewStatusBook(stateDir string) *StatusBook

NewStatusBook keeps the snapshot under stateDir, beside the records and locks.

func (*StatusBook) Observe

func (b *StatusBook) Observe(e Event)

Observe records e as its worker's newest event, unless an event already recorded is newer. It writes nothing: Step does.

func (*StatusBook) ObserveWakes

func (b *StatusBook) ObserveWakes(r WakeRead)

ObserveWakes records a step's wake read. It writes nothing: Step does. The budget time is kept only while the newest read says it is spent.

func (*StatusBook) Step

func (b *StatusBook) Step(at time.Time, read string) error

Step records a finished poller step and writes the snapshot: 0600, by atomic rename.

type TaskReason

type TaskReason struct {
	EventID    string
	ChainIndex int
	// Event is the line's wording: "assigned", "child finished", "alarm" or
	// "end session".
	Event string
	// ID and Title are the task's. A task-less alarm (S5 #2021
	// comment-6259060238: taskId null) has neither, and none is invented.
	ID    string
	Title string
	// AlarmType is a task-less alarm's closed type code from the server
	// (e.g. delivery.stuck), shown as given.
	AlarmType string
}

TaskReason is the task a TASK reason is for (DueTaskReason).

type Terminals

type Terminals interface {
	List(ctx context.Context) ([]orcaterm.Terminal, error)
	ReadScreen(ctx context.Context, handle string) *orcaterm.Screen
	Send(ctx context.Context, handle, text string, o orcaterm.SendOptions) orcaterm.Receipt
}

Terminals is the orca surface delivery uses; *orcaterm.Client is one.

type Through

type Through struct {
	ChannelID string `json:"channelId"`
	Seq       int    `json:"seq"`
}

Through limits an ack to a channel's covered prefix: Seq must be one of that channel's rendered seqs (S1), and the mark moves from the range's start to Seq and no further. Channels left out move nothing.

type Wake

type Wake struct {
	// Proof is the signed proof handed back to acknowledgeWakes. Never
	// logged, stored or put in argv.
	Proof    string
	WorkerID string
	Reasons  []WakeReason
	// NamesChannels: the proof carries channel ranges (DueWake.channels is
	// non-empty). Before A′ is deployed, acknowledging such a proof could
	// mark unseen text delivered, so C1 never does.
	NamesChannels bool
	// DeliverAfter and BusyDeadline are S1 slice (c)'s timing (R63): the
	// busy deadline counts from the oldest waiting reason's due time. A zero
	// one means the server did not say, and C1 invents none: no
	// DeliverAfter holds the wake as timing-unavailable even for an idle
	// agent, and no BusyDeadline holds it so for a busy or unproven one.
	DeliverAfter time.Time
	BusyDeadline time.Time
}

Wake is one due wake as dueWakes returns it.

func (Wake) Span

func (w Wake) Span() string

Span is the wake for logs: reason counts only, never keys, names or titles.

type WakeKind

type WakeKind string

WakeKind is a wake reason's kind, as dueWakes names it.

const (
	WakeMention WakeKind = "MENTION"
	WakeTick    WakeKind = "TICK"
	WakeTask    WakeKind = "TASK"
)

type WakeRead

type WakeRead struct {
	At       time.Time
	Read     string
	Dormant  int
	Withheld int
}

WakeRead is how a step's wake read went (cli#906 row 1): its code and the counts dueWakes reports for status. Counts only.

type WakeReason

type WakeReason struct {
	Kind       WakeKind
	TickerID   string
	TickerName string
	// LastTickAt names the tick (k) a TICK reason is for: it changes per
	// tick, so with the ticker it is the reason's stable identity.
	LastTickAt time.Time
	Task       *TaskReason
}

WakeReason is one reason a wake is due.

func (WakeReason) Key

func (r WakeReason) Key() string

Key is the reason's stable submitted identity: tick:<tickerId>:<lastTickAt> or task:<eventId>:<chainIndex>. "" for a MENTION, which C1 never records.

type WakeRecord

type WakeRecord struct {
	State    string    `json:"state"`
	WorkerID string    `json:"workerId"`
	Key      string    `json:"key"`
	TickerID string    `json:"tickerId,omitempty"`
	TickAt   time.Time `json:"tickAt,omitzero"`
	At       time.Time `json:"at"`
}

WakeRecord is submitted evidence for one TICK or TASK reason. It holds the reason's key and nothing else from the wake: no text, no proof.

type WakeRecords

type WakeRecords struct {
	Dir string
}

WakeRecords is the wake-reason store: one 0600 file per key under a 0700 directory per worker, written by atomic rename.

func (*WakeRecords) Acked

func (s *WakeRecords) Acked(workerID string, keys []string, at time.Time) error

Acked moves records to acked. A key with no record is skipped: only typed evidence is ever archived.

func (*WakeRecords) List

func (s *WakeRecords) List(workerID string) (map[string]WakeRecord, error)

List is the worker's wake records, keyed by reason key. A file that does not parse as this worker's record under its own name is ignored.

func (*WakeRecords) Prune

func (s *WakeRecords) Prune(workerID string, cutoff time.Time) error

Prune drops acked records older than cutoff. Typed records are never pruned by age.

func (*WakeRecords) Typed

func (s *WakeRecords) Typed(workerID string, reasons []WakeReason, at time.Time) error

Typed records reasons whose line was confirmed submitted. Written after the confirmation and before any ack.

type WakeServer

type WakeServer interface {
	AckWake(ctx context.Context, proof string) (AckResult, error)
}

WakeServer is acknowledgeWakes, for one proof C1 itself read. An error of any kind means nothing moved.

type WorkerStatus

type WorkerStatus struct {
	Name       string    `json:"name"`
	Kind       Kind      `json:"kind"`
	Reason     string    `json:"reason,omitempty"`
	At         time.Time `json:"at"`
	ResolvedBy string    `json:"resolvedBy,omitempty"`
	// Wake: the event was a tick or task wake's, not a batch's.
	Wake bool `json:"wake,omitempty"`
	// Unreported: the newest event was a hold the server was not told about.
	Unreported bool `json:"unreported,omitempty"`
}

WorkerStatus is a worker's newest delivery event: codes only.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL