okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffauffix(tui): preserve mail senders and deduplicate transcript events2ebeabd

main
4.9 KiB95 linesraw

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:

  1. The screen calls GetContextSnapshot to obtain the latest prepared snapshot.
  2. It calls GetContextPage with that snapshot ID, section, and page.
  3. The adapter validates the response and returns a typed page or an error.
  4. 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,
18   rendering, and rejection of outdated replies.
19
20Reading 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.