docs(operations): explain receipts and recovery
Document the queue, storage, and retry contracts. Spell out what an unresolved action means.
2 unmodified lines3456778910118 unmodified lines2021222324252627282923 unmodified lines2 unmodified linesevery session command is one definition with three faces: the CLI menu, user invocation, and the model's typed `commands` bindings. adding a command adds all three; nothing about a command is reimplemented per surface.
## the catalog
`GET /sessions/:id/commands` lists the materialized catalog: name (the slash spelling), description, the minted python `method`, its `usage` line, declared `arguments`, and the `modelCallable` / `userTurn` flags. the CLI menu renders it (merged with its presentation-only entries like `/login` and `/sessions`), `GET`'s list mints the kernel bindings at boot, and the prompt's `<session_commands>` block summarizes it. all three read the same list.`GET /sessions/:id/commands` lists the materialized catalog: name (the slash spelling), description, the minted python `method`, its `usage` line, declared `arguments`, and the `modelCallable`, `userTurn`, and `skill` flags. The `skill` flag marks a selected skill with a preparation function. The CLI submits these through the durable events route. Other command handlers keep their existing uncertainty policy. The CLI menu renders the catalog, merged with its presentation-only entries like `/login` and `/sessions`. The list mints the kernel bindings at boot, and the prompt's `<session_commands>` block summarizes it.
`POST /sessions/:id/commands` runs one command. the body is `{name, arguments}` with raw invocation text (`{"name": "/model", "arguments": "gpt-5 anthropic"}`) or `{name, args}` with declared names (`{"name": "/model", "args": {"model": "gpt-5"}}`); `clientId` labels a submitted turn. the answer is `{"result": ...}` with the command's JSON value or `{"submitted": true}` when a user invocation submitted its turn.
The Go client returns `daemon.APIError` for HTTP failures, retaining the status, daemon error code, and message. Use `errors.As` to inspect it. Workspace failures also unwrap to `daemon.WorkspaceMissingError` so the chat can offer a replacement folder. Capability checks return `daemon.UpgradeRequiredError`; the TUI recognizes that type when displaying catalog upgrade failures. Error wording does not control this routing.8 unmodified lines "Show token and cost usage for this session", [Argument("window", "reporting window", False)], True, False, False, None, fn(context, _caller, args) { ... Ok(command.Data(value)) },)```
23 unmodified lines27 unmodified lines2829303132323334353637383994 unmodified lines13413513613713813914014114214314414514614714814915015115215315415515615715815916016127 unmodified lines`server.prepare_storage` then runs before sessions start:
1. `conversation.initialise` creates the core tables, then calls `conversation_columns.apply`: the existing session columns first, transcript columns second. it then runs its existing domain-local session recovery columns second. The same core initialization creates `operations`, `pending_inputs`, `submission_events`, and `continuation_markers` through `operations.schema`. These are new tables and require no backfill. it then runs its existing domain-local session recovery (missing activity/title from transcript), then creates `sessions_activity`.2. quota, mail, and family initialise their tables, unchanged.3. `migrations.run(ledger, backup)` runs the core `image_store.run`, which first externalizes legacy transcript images, then converts image-table TEXT data94 unmodified lines- credentials migration is filesystem/auth, not SQLite. legacy configuration's provider assignment likewise remains in the existing server startup path.- no zstd, compression, schema-version, offline storage upgrade, new dependency, or unrelated transaction fix is part of this refactor.
## durable operation admissions
`operations` owns receipt retention and pending delivery state on the sharedstore connection. Session creation commits its session row and accepted receiptin one immediate transaction. Submission admission commits its receipt andpending input together. Transcript consumption moves pending metadata into`submission_events`, records its committed position, and removes pending inputin the same transaction as the transcript rows. Failed statements roll back thewhole transaction, including a failed commit.
Receipts retain the original admission result. They do not reference the sessionrow, so deletion cancels pending inputs and leaves receipts available. Terminalreceipts expire seven days after acceptance for creation or rejection, or aftercommit or cancellation for submissions. Pending receipts do not expire. Cleanupremoves at most 128 expired receipts per call. Durable display metadata in`submission_events` remains until session deletion, including operation IDs,display text, and echo identity. Continuations commit a `continuation_markers`row in the same transaction without adding a user-text transcript row. Thesemarkers also remain until session deletion and survive receipt cleanup.
Acceptance guarantees stored input or its committed or cancelled outcome.Commit means the input reached the transcript. Neither guarantee promises thatassistant generation, tools, or external effects occur exactly once.12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849# Retry-safe operation admission
Session creation and user turn submissions use a client-generated UUIDv7 `operationId`. Covered submissions are text, images, continuation, and skill activation. Each intended action gets one ID, even when two actions have identical content. The client retains the ID and encoded request until it resolves the outcome. Failure to obtain random bytes prevents submission.
An accepted response means the daemon stored the action safely. It does not mean the assistant finished. Assistant generation, tool calls, and arbitrary command effects do not have an exactly-once guarantee.
## Ownership and atomicity
`daemon/operations.gleam` owns receipts and pending inputs in the core SQLite store. `daemon/conversation.gleam` commits session creation with its receipt and consumes pending input with its transcript changes. The store actor runs each transaction on its own connection.
The `operations` table survives session deletion. `pending_inputs` records acceptance order and the full resolved submission, including display text, model input, image, source, client echo metadata, and operation ID. Consumption writes transcript inputs and images, compact display metadata, receipt delivery state, and pending deletion in one transaction. A failure rolls back all of those changes.
Committed display metadata retains display text, source, client echo ID, operation ID, and optional original image MIME type, dimensions, and byte count. It contains no image content, image references, or resolved model text. The transcript and image store own those payloads. History displays the original upload dimensions even when the model receives a fitted image. An operations-owned commit descriptor carries this metadata and the transcript offset from submission preparation into consumption.
`continuation_markers` retains consumed continuations at their transcript boundary without adding a user-text row. These markers survive receipt expiry and go with the session on deletion. A creation receipt's target is the original session ID, including after deletion.
A failed admission transaction leaves neither an accepted receipt nor its mutation. A failed consumption transaction leaves the input pending and appends no transcript input. The session retains blocked work and retries preparation at most once every 15 seconds while it remains usable. Startup opens sessions with pending work, including sessions whose previous turn was idle.
## Requests and duplicate detection
`POST /sessions` requires `operationId` alongside the creation fields. `POST /sessions/:id/events` requires `operationId` alongside the submission fields. Text and images use `type: "user"`, which is also the omitted default. Continuation uses `type: "continue"`. Skill activation uses `type: "skill"`, `name`, and raw `arguments`, and resolves through the selected skill's preparation function. It does not execute an arbitrary command handler. The commands route refuses user skill activation; model skill calls still return instructions as data.
The fingerprint includes a version, operation kind, target session, and normalized request fields. JSON key ordering and omitted defaults do not distinguish actions. Image content and skill arguments do. Authentication and echo-only `clientId` do not. An explicit `submissionId` participates because it identifies ownership for targeted cancellation; otherwise ownership defaults to the operation ID. Provider defaults and skill content resolve on first execution.
An identical retry returns the original HTTP status and admission body. A changed request under the same ID returns `409 operation_conflict` and leaves the original decision intact. Receipt lookup precedes mutable workspace, provider, skill, and queue validation. Rejected decisions also replay their original status and error.
Sessions accept at most 32 waiting inputs, including while idle, blocked, interrupted, or opening a kernel. An identical retry uses no additional slot. A distinct request at capacity receives a durable rejection that still replays after the queue drains. Recovery restores every accepted pending input without truncation and refuses new admissions while the restored queue is at or above capacity.
## Query and retention
Authenticated `GET /operations/:operationId` returns the receipt, including its ID, fingerprint, kind, target, timestamp, admission status, original result or error, and HTTP status. A submission also reports `deliveryStatus`:
- `pending`: safely stored and waiting for transcript consumption. `blockingReason` can explain why delivery is waiting.- `committed`: the input is recorded in the transcript. A continuation records its durable marker without adding a user-text row.- `cancelled`: interruption or session deletion cancelled the waiting input.
Creation receipts have no delivery status. Interrupt cancels waiting accepted inputs and stops the active turn. Deletion cancels pending inputs before removing the session. A retry never resurrects cancelled input or recreates a deleted session.
The daemon keeps receipts for seven days after their terminal outcome. Pending inputs and their receipts never expire. Cleanup removes expired terminal receipts in batches of at most 128. Existing receipts are checked first. An unknown ID more than seven days old returns `410 operation_expired` and cannot execute. An ID more than five minutes ahead returns `400 operation_future`.
A missing young ID returns `404 operation_unknown`. This result does not prove that an earlier in-flight request cannot still commit. Recovery retains the same ID.
## Client recovery boundaries
The Go client retains an explicit operation handle with the original encoded request. After transport loss, an invalid acknowledgement, or a server error, it queries the receipt and permits one automatic replay of the same request within the existing deadline. Cancellation stops retries and preserves the handle. A later query resolves that action without another submission POST. The prompt CLI also requests cancellation of its own submission on timeout or cancellation: queued input is removed atomically with its cancelled receipt, while a shared running turn continues for its other participants.
Pending UI rows match durable events by operation ID. Identical text from different actions cannot settle each other's pending rows. The TUI retains pending submission handles when you switch sessions and queries them when you return. Uncertain creation also keeps its handle across navigation; recovery adds the original session to the picker without changing the selected session. General commands, settings, interruption, child creation, and external effects retain their separate uncertainty handling and do not use this automatic replay policy.
`410 operation_expired` ends automatic recovery. The client returns an uncertain outcome with the original handle and sends no further automatic receipt queries or submission requests for that action. Expired user rows keep their IDs and attachments and remain visibly unresolved, but do not count toward the pending-send limit. Expired continuations retain an unresolved note with their operation ID. These states survive navigation. A matching durable user event can still reconcile an unresolved row. Expired creation retains its handle and notice without changing the selected session.1234567891039 unmodified lines# session attachment
Session creation and user turn admission use durable [operation receipts](operations.md).An accepted submission survives a daemon restart while it waits for transcriptconsumption. A retry with the same operation ID returns the original admission.
opening a session attaches the cli to its event stream and independentlyreads `GET /sessions/:id/status`. replayed history does not establish thesession's live phase; status and live events do.
39 unmodified lines53 unmodified lines5455565758585960616210 unmodified lines53 unmodified lines## Slash commands
The official client guide recommends user-explicit activation through a slash command or mention, while leaving the exact syntax to the client. Albedo assigns `/<skill-name>` and includes it in command autocomplete. Built-in commands always win. A colliding skill such as `model` gets the deterministic command `/skill:model` instead of replacing `/model`.
The CLI resolves an invocation against the daemon's [command catalog](commands.md) and asks the daemon to run it. The daemon resolves the same immutable session catalog used by Python, runs the same skill command, and a user invocation submits one normal user turn. The visible intent remains the original slash command while the model input carries a JSON-delimited activation with the exact arguments, source, and instructions. Normal idle, enabled-extension, and workspace checks still apply.The CLI resolves an invocation against the daemon's [command catalog](commands.md), whose `skill` flag identifies skill entries. It submits a dedicated skill activation through the events route with a stable operation ID. The daemon uses the selected entry's `skill_activation` preparation function, shared with the existing command implementation. It stores the resolved instructions with the admission, so a retry or restart does not reread changed skill content. The visible intent remains the original slash command while the model input carries a JSON-delimited activation with the exact arguments, source, and instructions. See [operation receipts](operations.md) for recovery and retention.
## Trust and execution
The selected Albedo workspace is already trusted for model-driven code execution. When enabled, the skills extension reads metadata from that workspace and the listed user roots. It does not create a separate filesystem sandbox.10 unmodified lines