state

package
v0.32.0 Latest Latest
Warning

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

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package state is the gateway's durable bookkeeping — the state that MUST survive a restart for the gateway to behave correctly: a durable INBOX of accepted-but-not-yet-processed messages (so a message is never lost between being acked to the provider and being run), and each polling channel's ack cursor (so a restart resumes where it left off). Both Hermes and OpenClaw's worst, money-losing bugs trace to keeping this state in memory; we keep it in a dedicated SQLite file, separate from the core event store.

The inbox row's (channel, message_id) primary key also serves as the dedup key: a redelivery inserts nothing (fresh=false) and is dropped.

Index

Constants

View Source
const (
	PairingTTL = time.Hour
	PairingCap = 25
)

PairingTTL is how long a pairing code stays valid; PairingCap bounds how many requests may be pending at once, so a flood of strangers can't grow the table (or the operator's approval list) without bound.

Variables

This section is empty.

Functions

func DaemonRunning added in v0.18.0

func DaemonRunning(dir string) bool

DaemonRunning reports whether a gateway daemon currently holds the exclusive lock for dir. It probes with a non-blocking lock attempt and releases immediately, so it never disturbs a running daemon. On platforms without file locking it reports false.

Types

type Item

type Item struct {
	Channel      string
	MessageID    string
	Conversation string
	Principal    string
	Text         string
	Trusted      bool
	Reply        string
	Agent        string   // agent snapshot at receipt
	Project      string   // project id snapshot at receipt
	Attachments  []string // media spool IDs (bare filenames; resolved only inside the spool)
	Voice        string   // spool ID of the synthesized voice reply ("" = text only)
}

Item is one inbound message durably recorded for processing. Reply is set only for items returned by PendingReplies (the job finished; the reply awaits delivery).

type Pairing added in v0.14.0

type Pairing struct {
	Code         string
	Channel      string
	Principal    string
	Conversation string
	CreatedAt    time.Time
}

Pairing is one pending request from an unknown sender.

type Store

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

Store is the gateway's durable state.

func Open

func Open(ctx context.Context, dir string) (*Store, error)

Open opens (creating if needed) the gateway state DB at dir/gateway.db. It also takes an exclusive lock on the project so a second `memcode gateway` for the same repo cannot start and double-process the shared inbox — the in-memory dedup guard only protects a single process. The lock releases when the Store is closed or the process exits.

func OpenShared added in v0.14.0

func OpenShared(ctx context.Context, dir string) (*Store, error)

OpenShared opens the gateway state DB WITHOUT the singleton lock — for CLI commands (pair list/approve/deny) that touch side tables while the daemon is running. WAL + busy_timeout make the cross-process access safe. Never use this to drive the inbox worker; the lock in Open exists to keep that single.

func (*Store) Accept

func (s *Store) Accept(ctx context.Context, it Item, now time.Time) (bool, error)

Accept durably records an inbound message as pending and reports whether this call is the one that recorded it. fresh=true means "you own this message, ack the provider and it will be processed"; fresh=false means it was already seen (a duplicate delivery or a concurrent racer) and must be dropped. The insert is atomic, so it also guards two concurrent deliveries of the same id. Callers ack the provider only after Accept returns without error, so a crash before the durable write re-delivers rather than loses the message.

func (*Store) Close

func (s *Store) Close() error

Close closes the database and releases the project lock.

func (*Store) Conversation added in v0.13.0

func (s *Store) Conversation(ctx context.Context, channel, conversation string) (agent, project string, err error)

Conversation returns the agent and project this conversation currently points at (empty when unset — the caller applies channel/gateway defaults).

func (*Store) CreatePairing added in v0.14.0

func (s *Store) CreatePairing(ctx context.Context, channel, principal, conversation, code string, now time.Time) (liveCode string, created bool, err error)

CreatePairing records a pending pairing request for an unknown sender and returns the live code. created reports whether THIS call minted it — the caller sends the code to the sender only then, so repeat messages from the same stranger don't re-trigger a reply. An expired request is replaced; a distinct-principal request past PairingCap is refused.

func (*Store) Cursor added in v0.15.0

func (s *Store) Cursor(ctx context.Context, channel string) (string, error)

Cursor returns the persisted string ack cursor for a channel ("" if none).

func (*Store) MarkDone

func (s *Store) MarkDone(ctx context.Context, channel, messageID string) error

MarkDone marks an item fully processed (reply delivered) so it is not run or re-sent again.

func (*Store) Offset

func (s *Store) Offset(ctx context.Context, channel string) (int64, error)

Offset returns the persisted ack cursor for a polling channel, or 0 if none.

func (*Store) Pending

func (s *Store) Pending(ctx context.Context) ([]Item, error)

func (*Store) PendingPairings added in v0.14.0

func (s *Store) PendingPairings(ctx context.Context, now time.Time) ([]Pairing, error)

PendingPairings lists live (non-expired) pairing requests, oldest first.

func (*Store) PendingReplies

func (s *Store) PendingReplies(ctx context.Context) ([]Item, error)

PendingReplies returns items whose job finished but whose reply has not yet been delivered, oldest first — the outbound retry queue, drained on every tick and replayed after a restart.

func (*Store) PruneDone

func (s *Store) PruneDone(ctx context.Context, before time.Time) error

PruneDone deletes processed items older than the cutoff, so the inbox can't grow without bound. Only 'done' rows are pruned; pending work is never dropped.

func (*Store) SetConversationAgent added in v0.13.0

func (s *Store) SetConversationAgent(ctx context.Context, channel, conversation, agent string) error

SetConversationAgent points a conversation at an agent for its SUBSEQUENT tasks (upsert, preserving the current project).

func (*Store) SetConversationProject added in v0.13.0

func (s *Store) SetConversationProject(ctx context.Context, channel, conversation, project string) error

SetConversationProject points a conversation at a project for its SUBSEQUENT tasks (upsert, preserving the current agent).

func (*Store) SetCursor added in v0.15.0

func (s *Store) SetCursor(ctx context.Context, channel, cursor string) error

SetCursor durably records a channel's string ack cursor.

func (*Store) SetInboxStatus added in v0.28.0

func (s *Store) SetInboxStatus(ctx context.Context, channel, messageID, from, to string) (bool, error)

Pending returns the still-to-process items, oldest first. Used to feed the worker and, on startup, to replay anything a prior crash left unprocessed.

func (*Store) SetOffset

func (s *Store) SetOffset(ctx context.Context, channel string, offset int64) error

SetOffset durably records a polling channel's ack cursor.

func (*Store) SetReplied

func (s *Store) SetReplied(ctx context.Context, channel, messageID, reply, voice string) error

SetReplied durably records a finished job's reply — and, when one was synthesized, the spool ID of its voice rendition — and moves the item to 'replied'. From here the job is never re-run and the voice is never re-synthesized; only the reply's delivery is retried, so a send failure, a crash, or a down channel cannot lose the result, repeat the work, or bill TTS twice.

func (*Store) TakePairing added in v0.14.0

func (s *Store) TakePairing(ctx context.Context, code string, now time.Time) (Pairing, error)

TakePairing removes a pairing request by code (case-insensitive) and returns it — the approve/deny consume step. A missing or expired code is an error.

Jump to

Keyboard shortcuts

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