mailbox

package
v0.8.67 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Overview

Package mailbox is the intra-island agent message store — Lane 5, Phase 1 of docs/inter-island-exchange-spec.md. Agents in the SAME island exchange small typed messages through the daemon (a shared blackboard / mailbox). It is the low-risk layer: same-island agents are one trust domain (they already share /workspace + home), so intra-island messaging is allowed by default.

Cross-island exchange is deliberately NOT here — that is the brokered, operator-granted, audited "link" layer (Phase 2+), with a separate deny-all posture and an action-delegation gate. Keeping the two apart is the whole point.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Message

type Message struct {
	Seq    int64  `json:"seq"`
	Island string `json:"island"`
	From   string `json:"from"` // sender agent id (literal)
	// FromLabel / ToLabel are the human names for From / To, daemon-resolved from
	// the island roster when a message is returned (read time, so a rename
	// reflects). Empty for a broadcast (no To), an unknown/cross-island handle, or
	// an unlabeled agent. Storage keeps only the ids; these are filled on the way
	// out so consumers render names without a second roster fetch.
	FromLabel string    `json:"from_label,omitempty"`
	To        string    `json:"to,omitempty"` // recipient agent id; empty = broadcast to the island
	ToLabel   string    `json:"to_label,omitempty"`
	Topic     string    `json:"topic,omitempty"` // optional channel within the island
	Payload   string    `json:"payload"`
	Time      time.Time `json:"time"`
	// Origin is daemon-stamped provenance, set ONLY for messages delivered from
	// another island over a brokered link (Lane 5). nil for ordinary intra-island
	// messages. Agents cannot set it — see DeliverExternal vs Send.
	Origin *Origin `json:"origin,omitempty"`
	// Action, when non-nil, marks this as a cross-island ACTION delegation (Lane 5
	// Phase 3) rather than free-form info: a NAMED, typed operation the recipient
	// island exposed, authorized by the daemon's action gate. Set only by
	// DeliverAction. The recipient runs its handler for Action.Type — it must not
	// interpret Payload as a free-form prompt.
	Action *MessageAction `json:"action,omitempty"`
}

Message is one intra-island message.

type MessageAction

type MessageAction struct {
	Type   string `json:"type"`
	Params string `json:"params,omitempty"`
}

MessageAction is a named, typed action invocation carried by a cross-island action delegation. Type is one of the recipient island's exposed action types.

type Origin

type Origin struct {
	SourceIsland string `json:"source_island"`
	CrossIsland  bool   `json:"cross_island"`
	// FromLabel is the sender agent's display label, stamped by the daemon at
	// send time from the SOURCE island's roster. A receiving island can't resolve
	// another island's roster (containment), so this is the only way it can show
	// a sender name instead of a bare id. Display-only + omitempty: absent when
	// the sender has no label, and consumers fall back to From (the id). Like the
	// rest of Origin it's unforgeable — only the cross-island delivery path sets it.
	FromLabel string `json:"from_label,omitempty"`
}

Origin marks a message that entered this island's mailbox from ANOTHER island over a brokered link. It's machine-read provenance (SDK / activity feed / audit tooling branch on CrossIsland) — a structured field rather than a parseable sender-string prefix, and unforgeable because only the daemon's cross-island delivery path sets it.

type Store

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

Store is a per-island ring of recent messages. The ring bounds memory so a chatty island can't grow it unbounded. When opened with a path (see Open) the store also persists every append to disk so undelivered messages AND the seq cursor survive a daemon restart — without that, a restart wiped the ring and reset seq to 1, silently dropping unread cross-session coordination (the no-lost-work bar). NewStore keeps the pure in-memory form for tests.

func NewStore

func NewStore(maxPerIsland int) *Store

NewStore returns an in-memory store retaining up to maxPerIsland messages per island. Nothing is persisted — use Open for a restart-durable store.

func Open added in v0.6.9

func Open(path string, maxPerIsland int, log *slog.Logger) *Store

Open returns a store backed by path: it loads any previously persisted messages + seq on startup, and persists every append back to path so they survive a daemon restart. A missing file starts empty; a corrupt/unreadable one is logged and started empty (the daemon must still come up). A nil logger is tolerated. maxPerIsland bounds each island's ring as in NewStore.

func (*Store) DeliverAction

func (s *Store) DeliverAction(island, sourceIsland, from, fromLabel, to, topic, actionType, params string) Message

DeliverAction appends a cross-island ACTION delegation into `island`'s mailbox (Lane 5 Phase 3): a named, typed operation (actionType/params) the daemon's action gate authorized. Like DeliverExternal it stamps Origin; it additionally sets the structured Action field. Distinct from Send/DeliverExternal so only the gated action path can mark a message as an action.

func (*Store) DeliverExternal

func (s *Store) DeliverExternal(island, sourceIsland, from, fromLabel, to, topic, payload string) Message

DeliverExternal appends a message delivered into `island`'s mailbox from another island (sourceIsland) over a brokered link, stamping daemon-controlled Origin (CrossIsland=true). `from` is the source agent's literal id, `fromLabel` its display label resolved by the caller from the source roster (may be ""), and `to` the local recipient agent. It is distinct from Send precisely so the intra-island path can never set Origin — provenance is the daemon's to assert.

func (*Store) Latest

func (s *Store) Latest(island string) int64

Latest returns the highest seq retained for an island (0 if none) — a cheap cursor for "everything after now".

func (*Store) Poll

func (s *Store) Poll(island, agent string, since int64) []Message

Poll returns the retained messages in island visible to agent `agent` with Seq > since, ordered by Seq. Visible = broadcasts (To == "") plus messages addressed To == agent. since == 0 returns all retained. An empty agent sees only broadcasts (an operator/observer view).

func (*Store) Send

func (s *Store) Send(island, from, to, topic, payload string) Message

Send appends a message to an island's ring and returns it with Seq/Time set. from is the sender agent id; to is a recipient agent id, or "" to broadcast to every agent in the island.

func (*Store) SetArrivalHook

func (s *Store) SetArrivalHook(fn func(m Message))

SetArrivalHook registers a callback fired (in its own goroutine, so it never holds the store lock or blocks the sender) after every message is appended — the wake-on-message seam (Lane 5 Phase 3.5). nil disables it.

func (*Store) WaitArrivalHooks added in v0.8.66

func (s *Store) WaitArrivalHooks(d time.Duration) bool

WaitArrivalHooks blocks until every arrival hook started so far has returned, giving up after d. It reports whether they all finished.

For tests. The hook is deliberately detached — that is the point of it, and the daemon outlives any individual delivery — but a test process does not. A hook that runs on past the end of its test does filesystem work against whatever $HOME has become by then: the next test's t.TempDir (where creating a directory during RemoveAll produces "unlinkat: directory not empty") or, if no test is running, the developer's real home. Both were happening.

The deadline is not optional, so that a hook which blocks forever surfaces as a named failure in the test that started it, rather than as the whole binary panicking at the ten-minute mark with the wrong test's name on it. That misattribution is the exact shape of bug this method exists to close.

Jump to

Keyboard shortcuts

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