Documentation
¶
Overview ¶
Package biam — Bidirectional Inter-Agent Messaging substrate (ADR-015 Phase 1). identity.go owns the per-instance Ed25519 keypair: every clawtool listener generates one on first launch at ~/.config/clawtool/identity.ed25519 and exchanges public keys with peers via the trust file (peers.toml). Signed envelopes use the private key; receivers verify against the trust map.
The identity file is mode 0600 + 32-byte raw seed; the public key is derived deterministically. We don't ship a CA or PKI — peer trust is operator-managed (one-line `clawtool peer add`).
Package biam — process-internal completion notifier (ADR-024 preview / TaskNotify support). The SQLite-backed task store is the durable record; this is the *edge-triggered* fast path so a TaskNotify caller doesn't have to poll. Lifetime = clawtool serve process. Subscriptions evaporate on restart — completed tasks remain queryable via TaskGet.
Index ¶
- Variables
- func DefaultIdentityPath() string
- func DefaultStorePath() string
- func ParsePublicKey(s string) (ed25519.PublicKey, error)
- func Verify(pub ed25519.PublicKey, message, signature []byte) bool
- type Address
- type Body
- type Envelope
- type EnvelopeKind
- type Identity
- type Runner
- type SendStream
- type Store
- func (s *Store) Close() error
- func (s *Store) CreateTask(ctx context.Context, taskID, initiatedBy, agent string) error
- func (s *Store) GetTask(ctx context.Context, taskID string) (*Task, error)
- func (s *Store) ListTasks(ctx context.Context, limit int) ([]Task, error)
- func (s *Store) MessagesFor(ctx context.Context, taskID string) ([]Envelope, error)
- func (s *Store) PutEnvelope(ctx context.Context, env *Envelope, inbound bool) error
- func (s *Store) SetTaskStatus(ctx context.Context, taskID string, status TaskStatus, lastMessage string) error
- func (s *Store) WaitForTerminal(ctx context.Context, taskID string, poll time.Duration) (*Task, error)
- type Sub
- type Task
- type TaskStatus
Constants ¶
This section is empty.
Variables ¶
var Notifier = ¬ifier{subs: map[string][]chan Task{}}
Notifier is the process-wide singleton. Tests use ResetForTest.
Functions ¶
func DefaultIdentityPath ¶
func DefaultIdentityPath() string
DefaultIdentityPath honours XDG_CONFIG_HOME, falls back to HOME.
func DefaultStorePath ¶
func DefaultStorePath() string
DefaultStorePath honours XDG_DATA_HOME, falls back to HOME.
func ParsePublicKey ¶
ParsePublicKey decodes the `ed25519:<hex>` form back into a key.
Types ¶
type Body ¶
type Body struct {
Text string `json:"text,omitempty"`
Extras map[string]any `json:"extras,omitempty"`
}
Body is the per-message payload. `Text` is the agent-readable content; `Extras` carries opt-in structured data without forcing a schema bump.
type Envelope ¶
type Envelope struct {
Version string `json:"v"`
TaskID string `json:"task_id"`
MessageID string `json:"message_id"`
ParentID string `json:"parent_id,omitempty"`
CorrelationID string `json:"correlation_id,omitempty"`
From Address `json:"from"`
To Address `json:"to"`
ReplyTo Address `json:"reply_to"`
Kind EnvelopeKind `json:"kind"`
Body Body `json:"body"`
HopCount int `json:"hop_count"`
MaxHops int `json:"max_hops"`
Trace []string `json:"trace"`
CreatedAt time.Time `json:"created_at"`
TTLSeconds int64 `json:"ttl_seconds"`
IdempotencyKey string `json:"idempotency_key"`
Signature string `json:"signature,omitempty"`
}
Envelope is the wire shape every BIAM message takes. Locked at `v: biam-v1` per ADR-015. Field rules in the ADR's "Wire envelope" section.
func NewEnvelope ¶
func NewEnvelope(from, to Address, taskID string, kind EnvelopeKind, body Body) *Envelope
NewEnvelope stamps the routine fields a fresh envelope needs and leaves the caller to set Body / ParentID / Kind. Trace seeds with the sender's address so cycle detection works on hop 1.
func (*Envelope) HasCycle ¶
HasCycle reports whether `peer` already appears in the envelope's trace — a clean way to detect "this came back to me, drop it."
func (*Envelope) Hop ¶
Hop bumps the hop count + appends `me` to the trace. Returns the fresh max-hops error when the cap is exceeded.
type EnvelopeKind ¶
type EnvelopeKind string
EnvelopeKind enumerates what a message represents in a BIAM thread.
const ( KindPrompt EnvelopeKind = "prompt" KindReply EnvelopeKind = "reply" KindClarification EnvelopeKind = "clarification" KindResult EnvelopeKind = "result" KindError EnvelopeKind = "error" KindCancel EnvelopeKind = "cancel" )
type Identity ¶
type Identity struct {
HostID string
InstanceID string
Public ed25519.PublicKey
// contains filtered or unexported fields
}
Identity carries the Ed25519 keypair plus the human-friendly host / instance label every signed envelope's `from` field uses.
func LoadOrCreateIdentity ¶
LoadOrCreateIdentity reads the seed file at path; creates a new keypair on first launch. The host_id and instance_id default to the host's hostname + "default" when not set in the seed metadata.
First-launch creation is guarded by a sibling .lock file (flock): two clawtool processes starting in parallel must not race two keypairs into the same path, with the last-write winner stranding every envelope the loser had already signed. The lock is held only over the create-and-publish window — readers on a healthy file never touch it.
func (*Identity) PublicKeyB64 ¶
PublicKeyB64 returns the public key encoded as `ed25519:<hex>` — the format the peers.toml file stores.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner glues the BIAM store to the supervisor's dispatch surface: async submissions land in the store as `prompt` envelopes; a goroutine drains the upstream stream and persists `result` (or `error`) envelopes; tasks transition through pending → active → done|failed.
func NewRunner ¶
func NewRunner(store *Store, id *Identity, send SendStream) *Runner
NewRunner wires the runner. Identity + store are mandatory; send is the supervisor's dispatch func.
func (*Runner) Submit ¶
func (r *Runner) Submit(ctx context.Context, instance, prompt string, opts map[string]any) (string, error)
Submit enqueues an async dispatch. Returns the new task_id immediately; the goroutine streams the response into the store and transitions the task on completion. Cancel via `Cancel(taskID)`.
type SendStream ¶
type SendStream func(ctx context.Context, instance, prompt string, opts map[string]any) (io.ReadCloser, error)
SendStream is the function shape the runner expects from Supervisor: invoke `instance` with `prompt` + `opts`, return a streaming io.ReadCloser. Matches Supervisor.Send so we can swap in tests.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store wraps the per-instance SQLite file. Methods are safe for concurrent calls — the underlying connection pool serialises writes; readers fan out via WAL.
func OpenStore ¶
OpenStore opens (creating if absent) the SQLite database at path. WAL mode + busy-timeout makes concurrent writers tolerant.
func (*Store) CreateTask ¶
CreateTask inserts a new task row and returns the row's task_id. Idempotent: an existing task_id returns nil error.
func (*Store) GetTask ¶
GetTask returns the row for the given task_id, plus the message count via a sub-query so the caller doesn't need a second round trip.
func (*Store) MessagesFor ¶
MessagesFor returns every envelope persisted under task_id, oldest first. Snapshot — does not subscribe.
func (*Store) PutEnvelope ¶
PutEnvelope inserts a message into the messages table. Inbound vs outbound is the caller's call. Dedupe via idempotency_key prevents double-inserts on retry.
func (*Store) SetTaskStatus ¶
func (s *Store) SetTaskStatus(ctx context.Context, taskID string, status TaskStatus, lastMessage string) error
SetTaskStatus updates the task row + (when terminal) closed_at + last_message. Pass empty `lastMessage` to leave it untouched.
type Sub ¶
type Sub struct {
Ch <-chan Task
// contains filtered or unexported fields
}
Sub is a handle to one subscription. Cancel removes the channel from the subscriber list so a goroutine that bails out doesn't leak its slot until the next Publish.
type Task ¶
type Task struct {
TaskID string `json:"task_id"`
Status TaskStatus `json:"status"`
InitiatedBy string `json:"initiated_by"` // who started it; empty for inbound
Agent string `json:"agent"` // agent instance the dispatch hit
CreatedAt time.Time `json:"created_at"`
ClosedAt *time.Time `json:"closed_at,omitempty"`
LastMessage string `json:"last_message,omitempty"` // tail of the latest result
MessageCount int `json:"message_count"`
}
Task is the BIAM-level row for a multi-message thread.
type TaskStatus ¶
type TaskStatus string
TaskStatus enumerates the per-task lifecycle ADR-015 §"State machine" locks at v1.
const ( TaskPending TaskStatus = "pending" TaskActive TaskStatus = "active" TaskDone TaskStatus = "done" TaskFailed TaskStatus = "failed" TaskCancelled TaskStatus = "cancelled" TaskExpired TaskStatus = "expired" )
func (TaskStatus) IsTerminal ¶
func (s TaskStatus) IsTerminal() bool
IsTerminal reports whether a status closes the task.