okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
2ebeabd
main
Client API ownership
The Go daemon package owns protocol 3. Application commands and TUI screens
call named operations and receive typed results. Generated code constructs
routes and queries. The adapter checks responses, bounds decoding, and chooses
recovery policy.
OpenAPI defines the wire format;
HTTP API design defines its behavior.
Request flow
Opening a context page follows this flow:
- The screen calls
GetContextSnapshotto obtain the latest prepared snapshot. - It calls
GetContextPagewith that snapshot ID, section, and page. - The adapter validates the response and returns a typed page or an error.
- A typed UI message carries the result to the screen, which owns selection, rendering, and rejection of outdated replies.
Reading context does not prepare a model request. If the snapshot changes, the client obtains the new summary before requesting its pages.
Code organization
Each domain keeps its operations and application-facing types together.
protocol/api.gen.go contains wire types and HTTP request builders generated
from OpenAPI with pinned oapi-codegen. Conversion stays inside the adapter.
Run go -C cli generate ./internal/daemon after editing the contract.
test.sh checks that generation produces the checked-in file.
The generator runs in cli/tools/apigen, outside the CLI dependency graph.
It projects OpenAPI 3.2 to 3.1 in memory for the generator, selects raw JSON for
unions, and preserves nullable field tags for adapter validation. The canonical
spec remains authoritative. The adapter owns bounded response reads, semantic
validation, authentication recovery, and SSE framing and replay.
Session stream parsing is separate from chat operations. Transport, decoding,
and receipt recovery are private machinery. Extension-specific result bodies
remain explicit json.RawMessage values.
Local discovery and launching have separate responsibilities. API recovery cannot launch or replace a daemon. The interactive CLI still offers to keep or restart a running daemon; replacement requires the user's choice. Daemon lifecycle describes startup and operator overrides.
Changes and recovery
Typed patches preserve omitted fields, explicit removals, and false values. Configuration writes use the observed resource validator. A stale screen cannot silently overwrite a newer edit.
Reads can recover through rediscovery within the caller's deadline. Identified creation and input recovery retain the original ID and encoded intent. Other mutations do not automatically replay after a lost acknowledgement. Canceling a request does not cancel accepted input, interrupt a turn, or stop a shared daemon.
Session SSE uses an actor generation and sequence. The client saves both only after delivery succeeds. A reset replaces visible history and live progress; older durable history remains available through paging. Collection SSE carries invalidations. Initial reset and overflow require authoritative refresh.
History entries retain their durable id and position. Clients merge repeated
entries by ID, including overlap between live delivery and older pages; identical
text from different entries remains distinct. Empty assistant provider records
do not settle a streamed reply or consume its duplicate match.
turn_type distinguishes agent mail from user input. Optional mail metadata
supplies the mail ID, sender session ID, sender label, and kind. Clients use the
structured label, falling back to “Agent”, rather than parsing display text.
The daemon owns bounded tool progress and argument parsing. Clients validate and render the normalized updates. Preview offsets count decoded Unicode scalar values. Full arguments, results, and recorded traces remain in durable history.
Storage reports
albedo storage reads the authenticated /storage resource through
GetStorageReport. The adapter traverses report pages; the daemon inspects its
own files and database. Online clients need neither Python nor filesystem access.
Proven absence or a stale discovery record selects read-only local diagnosis.
Authentication, protocol, health, and transport failures remain errors.
--offline deliberately selects local diagnosis, which inspects a temporary
database and WAL copy without changing the installation. Unsupported required
schema columns fail rather than producing incomplete totals. Cleanup requires
the local ownership lock and rechecks approved candidates before deleting files.
Adding an operation
Add the named operation and typed result to the adapter. Keep route encoding, accepted statuses, capability requirements, bounds, and recovery rules there. Translate its result into application or UI state through a typed message.
Use real-daemon tests for observable workflows. Controlled transport tests cover malformed responses and uncertain outcomes a healthy daemon cannot reliably produce. Do not add public generic request helpers or forwarding wrappers.
1# Client API ownership 2 3The Go `daemon` package owns protocol 3. Application commands and TUI screens 4call named operations and receive typed results. Generated code constructs 5routes and queries. The adapter checks responses, bounds decoding, and chooses 6recovery policy. 7[OpenAPI](openapi.yaml) defines the wire format; 8[HTTP API design](http-api-design.md) defines its behavior. 9 10## Request flow 11 12Opening a context page follows this flow: 13 141. The screen calls `GetContextSnapshot` to obtain the latest prepared snapshot. 152. It calls `GetContextPage` with that snapshot ID, section, and page. 163. The adapter validates the response and returns a typed page or an error. 174. A typed UI message carries the result to the screen, which owns selection, 18rendering, and rejection of outdated replies. 19 20 Reading context does not prepare a model request. If the snapshot changes, 21the client obtains the new summary before requesting its pages. 22 23## Code organization 24 25Each domain keeps its operations and application-facing types together. 26`protocol/api.gen.go` contains wire types and HTTP request builders generated 27from OpenAPI with pinned `oapi-codegen`. Conversion stays inside the adapter. 28Run `go -C cli generate ./internal/daemon` after editing the contract. 29`test.sh` checks that generation produces the checked-in file. 30 31The generator runs in `cli/tools/apigen`, outside the CLI dependency graph. 32It projects OpenAPI 3.2 to 3.1 in memory for the generator, selects raw JSON for 33unions, and preserves nullable field tags for adapter validation. The canonical 34spec remains authoritative. The adapter owns bounded response reads, semantic 35validation, authentication recovery, and SSE framing and replay. 36Session stream parsing is separate from chat operations. Transport, decoding, 37and receipt recovery are private machinery. Extension-specific result bodies 38remain explicit `json.RawMessage` values. 39 40Local discovery and launching have separate responsibilities. API recovery 41cannot launch or replace a daemon. The interactive CLI still offers to keep or 42restart a running daemon; replacement requires the user's choice. 43[Daemon lifecycle](daemon-lifecycle.md) describes startup and operator overrides. 44 45## Changes and recovery 46 47Typed patches preserve omitted fields, explicit removals, and false values. 48Configuration writes use the observed resource validator. A stale screen cannot 49silently overwrite a newer edit. 50 51Reads can recover through rediscovery within the caller's deadline. Identified 52creation and input recovery retain the original ID and encoded intent. Other 53mutations do not automatically replay after a lost acknowledgement. Canceling a 54request does not cancel accepted input, interrupt a turn, or stop a shared daemon. 55 56Session SSE uses an actor generation and sequence. The client saves both only 57after delivery succeeds. A reset replaces visible history and live progress; 58older durable history remains available through paging. Collection SSE carries 59invalidations. Initial reset and overflow require authoritative refresh. 60 61History entries retain their durable `id` and `position`. Clients merge repeated 62entries by ID, including overlap between live delivery and older pages; identical 63text from different entries remains distinct. Empty assistant provider records 64do not settle a streamed reply or consume its duplicate match. 65 66`turn_type` distinguishes agent mail from user input. Optional `mail` metadata 67supplies the mail ID, sender session ID, sender label, and kind. Clients use the 68structured label, falling back to “Agent”, rather than parsing display text. 69 70The daemon owns bounded tool progress and argument parsing. Clients validate 71and render the normalized updates. Preview offsets count decoded Unicode scalar 72values. Full arguments, results, and recorded traces remain in durable history. 73 74## Storage reports 75 76`albedo storage` reads the authenticated `/storage` resource through 77`GetStorageReport`. The adapter traverses report pages; the daemon inspects its 78own files and database. Online clients need neither Python nor filesystem access. 79 80Proven absence or a stale discovery record selects read-only local diagnosis. 81Authentication, protocol, health, and transport failures remain errors. 82`--offline` deliberately selects local diagnosis, which inspects a temporary 83database and WAL copy without changing the installation. Unsupported required 84schema columns fail rather than producing incomplete totals. Cleanup requires 85the local ownership lock and rechecks approved candidates before deleting files. 86 87## Adding an operation 88 89Add the named operation and typed result to the adapter. Keep route encoding, 90accepted statuses, capability requirements, bounds, and recovery rules there. 91Translate its result into application or UI state through a typed message. 92 93Use real-daemon tests for observable workflows. Controlled transport tests cover 94malformed responses and uncertain outcomes a healthy daemon cannot reliably 95produce. Do not add public generic request helpers or forwarding wrappers.