Stage 8 Chat SDK support for Mira→Red Queen INVESTIGATE routing metadata. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|---|---|---|
| proto | ||
| .gitmodules | ||
| ack_tracker.go | ||
| auth.go | ||
| client.go | ||
| doc.go | ||
| errors.go | ||
| example_test.go | ||
| go.mod | ||
| go.sum | ||
| history.go | ||
| options.go | ||
| participant_cache.go | ||
| participants.go | ||
| README.md | ||
| subscription_test.go | ||
| subscription.go | ||
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:
git clone --recurse-submodules <repo-url> 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:
authorization: Bearer <token>
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:
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:
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:
ListTopicsGetTopicGetMessageHistoryPostMessageusing server-sideAgentPostMessageSubscribeSubscription.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.