# l-ai-chat-client-go Reusable Go SDK for l-ai-chat agent/service clients. It uses the authoritative `l-ai-chat-proto` generated Go API through the `proto/l-ai-chat-proto` submodule and does not copy protobuf files manually. ## Install / Build Clone with submodules: ```bash git clone --recurse-submodules l-ai-chat-client-go cd l-ai-chat-client-go go test ./... ``` On this workstation the server currently consumes proto commit `f1f44e4ed276316ef17f8deccac54da457654312`. The local sibling `../l-ai-chat-proto` checkout is older, so the SDK submodule is pinned to the same proto worktree commit as `../l-ai-chat`. ## Transport and Auth `New` uses TLS by default and automatically adds service auth metadata to unary RPCs, `Subscribe`, reconnects, and `Acknowledge`: ```text authorization: Bearer ``` Use `WithInsecureTransport()` only for trusted/internal networks. The service token travels over this gRPC transport; plaintext mode is suitable only for trusted/internal network until service-to-service TLS/mTLS is deployed. Tokens come from a `TokenProvider`; `StaticToken` is the simple provider. Empty tokens and provider errors fail closed. Tokens are not included in SDK errors. ## Delivery Guarantee Agent delivery is durable at-least-once: - receiving a message does not mean processing succeeded; - the server durable cursor changes only after successful `Acknowledge`; - the SDK acknowledges only after `MarkProcessed`; - unacked messages replay after reconnect; - handlers with external side effects must be idempotent. The server derives the durable consumer identity from the authenticated agent/service token. The SDK does not accept an arbitrary consumer id. ## Cumulative ACK Warning `Acknowledge(last_ack_sequence=N)` is cumulative. If deliveries are: ```text 100, 104, 111 ``` and `104` finishes before `100`, do not acknowledge `104` yet: that would mark `100` processed too. Use `Subscription.MarkProcessed`; the SDK tracks actual delivery order, not `seq+1`, so global sequence gaps inside a topic are safe. Expected ACK behavior: ```text 104 done -> no ACK 100 done -> ACK 104 111 done -> ACK 111 ``` ## Subscription Lifecycle `Subscribe` opens one server stream for one topic and waits for the first `ready=true` marker before returning. READY is not exposed as a chat message. Each successful stream registration is a new SDK generation. Deliveries carry internal generation/subscription fencing, so an old `Delivery` cannot ACK a new stream after reconnect. Transient stream failures (`Unavailable`, `ResourceExhausted`, `DeadlineExceeded`, EOF/transport reset) reconnect with bounded exponential backoff and jitter. Reconnect does not send a local receive cursor; the server starts from the durable ACK cursor and replays unacked messages. `Aborted` means the server intentionally replaced this active subscription because another stream for the same `(consumer, topic)` connected. The SDK treats that as terminal by default to avoid reconnect ping-pong between two replicas of one logical consumer. HA/leader election belongs above this SDK. First-time `(consumer, topic)` subscriptions start at the current topic head. Use `History` for historical context. Existing consumers resume from the durable ACK cursor. `Delivery.Replay` means redelivery/backlog context, not an error. ## gRPC Surface The SDK wraps implemented read/agent RPCs: - `ListTopics` - `GetTopic` - `GetMessage` - `History` - `PostMessage` using server-side `AgentPostMessage` - `Subscribe` - `Subscription.MarkProcessed` Generic `PostMessage`, `CreateTopic`, and `UpdateTopic` are intentionally not exposed as ergonomic helpers because the current server returns `Unimplemented` for those fail-closed gRPC paths. ## Lifecycle Notes `Client` owns and closes the gRPC connection it creates in `New`. If callers provide an existing connection with `WithClientConn`, the SDK uses it but does not close it; callers keep ownership of that connection. `Subscription.Run` is sequential: it calls the handler for one delivery at a time and calls `MarkProcessed` only after the handler returns nil. Handler errors stop `Run` and do not ACK that delivery. Automatic reconnect cancels the subscription stream generation, but it cannot undo external handler side effects; handlers should honor their context and remain idempotent. Calling `MarkProcessed` more than once for the same delivery is harmless. The first call may advance the safe ACK boundary; later duplicate calls do not add tracker entries, move the cursor backward, or ACK a caller-mutated sequence.