Skip to content

Commit edf7b1b

Browse files
authored
docs: rewrite guides, reference, and explanation in the README's style (#84)
Rewrite the how-to guides, explanation, reference intro, docs index, Linear capabilities page, tutorial tunnel step, and Linear example README in the README's plain, task-first style; move the testing contract to CONTRIBUTING.md. Codex review fixes tighten accuracy on ack ordering, dedupe marks, DetachTimeout budgets for waiting strategies, and strategy-dependent serialization. The slash-command guide now states RespondURL responses are always ephemeral. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_
1 parent b09fec4 commit edf7b1b

13 files changed

Lines changed: 565 additions & 547 deletions

‎CONTRIBUTING.md‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,30 @@ Narrower tasks help while you iterate:
5151
When you bump a state module's dependencies, also run `go mod tidy` in the
5252
matching example module; otherwise its `go.sum` goes stale.
5353

54+
### What Tests Must Cover
55+
56+
Tests check external behavior and public contracts, not private
57+
implementation details. Keep these families covered:
58+
59+
- runtime construction and shutdown
60+
- handler registration and replacement
61+
- routing order and no-op missing handlers
62+
- explicit subscription and unsubscribe
63+
- direct-message implicit mention routing
64+
- self-message filtering
65+
- accepted, ignored, rejected, duplicate, and lock-conflict events
66+
- state conformance across memory, Redis, Postgres, and NATS
67+
- token-owned lock lease acquire, release, extend, expiry, and stale release
68+
- Slack signature verification and URL verification
69+
- Slack golden payload normalization
70+
- thread ID construction and validation
71+
- thread handle reconstruction
72+
- text, Markdown, sent message, ephemeral, and ephemeral fallback posting
73+
- typed adapter access
74+
- documentation coverage of intentional Vercel Chat SDK differences in the
75+
README, reference, explanation, and GoDoc (see
76+
[Documentation](#documentation))
77+
5478
## Documentation
5579

5680
Docs follow [Diátaxis](https://diataxis.fr/); the

‎docs/README.md‎

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Chat SDK Go Documentation
22

3-
User-facing documentation is organized along [Diátaxis](https://diataxis.fr/):
4-
learning-oriented tutorials, task-oriented how-to guides, information-oriented
5-
reference, and understanding-oriented explanation.
3+
The docs follow [Diátaxis](https://diataxis.fr/): a tutorial to learn,
4+
how-to guides to get a task done, reference to look things up, and
5+
explanation to understand the design.
66

77
## Tutorials
88

@@ -13,7 +13,7 @@ Start here if you are new to the SDK.
1313

1414
## How-To Guides
1515

16-
Task-oriented guides for people already running a bot.
16+
For people who already have a bot running.
1717

1818
- [Choose a state backend](how-to/choose-a-state-backend.md) — memory, Redis,
1919
Postgres, or NATS JetStream.
@@ -30,22 +30,21 @@ Task-oriented guides for people already running a bot.
3030

3131
## Reference
3232

33-
- [Reference](reference.md) — module layout and pkg.go.dev pointers, the
34-
runtime's semantics by concept (construction, webhooks, routing, dispatch,
35-
state, concurrency, messages, history, actors, adapter access),
36-
per-adapter capability status, the examples, and the testing contract.
37-
- [Linear agent capability gaps](linear-agent-capabilities.md) — tracked list
38-
of Linear agent APIs the adapter does not yet wrap.
33+
- [Reference](reference.md) — modules and packages, how the runtime behaves
34+
(routing, dispatch, state, concurrency, messages, history), what each
35+
adapter supports, and the runnable examples. The API itself is in the
36+
[GoDoc](https://pkg.go.dev/github.com/coder/chat).
37+
- [Linear agent capabilities](linear-agent-capabilities.md) — what the Linear
38+
adapter supports today and what it does not wrap yet.
3939

4040
## Explanation
4141

42-
- [Architecture and design decisions](explanation.md) — an index over
43-
[`CONTEXT.md`](../CONTEXT.md) (the ubiquitous language and architecture
44-
document) and the [ADRs](adr/) that record every significant decision,
45-
plus the design goals, the Vercel Chat SDK concept map, the non-goals, and
46-
the intentional gaps.
42+
- [Architecture and design decisions](explanation.md) — the model in brief,
43+
design goals, the Vercel Chat SDK concept map, non-goals, and intentional
44+
gaps, with an index of the [ADRs](adr/) and
45+
[`CONTEXT.md`](../CONTEXT.md), the project's vocabulary.
4746

48-
## Non-User Documentation
47+
## For Contributors
4948

5049
- [`CONTRIBUTING.md`](../CONTRIBUTING.md) — how to build, test, and propose
5150
changes to this repository.

‎docs/explanation.md‎

Lines changed: 71 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,14 @@
11
# Architecture And Design Decisions
22

3-
Chat SDK Go's design is documented in two places, and this page is the index
4-
over both. It also states the [design goals](#design-goals), maps the
5-
project against [Vercel Chat SDK](#vercel-chat-sdk-alignment), and lists the
6-
[non-goals](#non-goals) and [intentional gaps](#intentional-gaps).
7-
8-
- [`CONTEXT.md`](../CONTEXT.md) — the ubiquitous language and architecture
9-
document. It defines every domain term precisely (with the synonyms to
10-
avoid), states the architectural invariants as explicit relationships, and
11-
records resolved ambiguities against upstream Vercel Chat SDK behavior.
12-
- [`docs/adr/`](adr/) — Architecture Decision Records. Every significant
13-
decision has one, including the decisions *not* to build something.
14-
15-
## Reading CONTEXT.md
16-
17-
If you want to understand the system, read `CONTEXT.md` top to bottom; it is
18-
the single most information-dense document in the repository. Its `Language`
19-
section groups the vocabulary by area — runtime lifecycle, the platform
20-
adapter boundary, Linear session lifecycle, threads and routing, the
21-
event/message model, dispatch and concurrency, observability, state and
22-
history, content and formatting, and tenancy and identity. The
23-
`Relationships` section is the closest thing to a formal specification of the
24-
runtime's invariants, and `Flagged ambiguities` explains where and why the
25-
design deliberately diverges from Vercel Chat SDK.
26-
27-
## Decision Records
28-
29-
| ADR | Decision | Status |
30-
| --- | --- | --- |
31-
| [0001](adr/0001-linear-app-actor-slice.md) | Linear app-actor slice before a full Linear adapter | Accepted |
32-
| [0002](adr/0002-async-dispatch.md) | Deferred runtime dispatch (ack-then-work) | Accepted |
33-
| [0003](adr/0003-slash-commands.md) | Command Events and slash command routing | Accepted |
34-
| [0004](adr/0004-interactive-components.md) | Interaction Events and native content instead of a card DSL | Accepted |
35-
| [0005](adr/0005-rate-limit-handling.md) | Rate-limit retry lives in adapters, with typed `RateLimited` errors | Accepted |
36-
| [0006](adr/0006-multi-tenant-install.md) | Multi-tenant installs via app-implemented `InstallStore`; OAuth flows stay app-owned | Accepted |
37-
| [0007](adr/0007-teams-adapter.md) | Microsoft Teams adapter approach (Bot Framework, direct HTTP) | Proposed — gated on a spike |
38-
| [0008](adr/0008-linear-full-adapter.md) | Full Linear agent activity surface (thought/response/action/elicitation/error) plus session updates (plans, external URLs) | Accepted |
39-
| [0009](adr/0009-message-history.md) | Message history stays application-owned; optional storage-free `HistoryReader` | Accepted |
40-
| [0010](adr/0010-observability.md) | Optional `Observer` seam; no OpenTelemetry in core | Accepted |
41-
| [0011](adr/0011-resumable-streaming.md) | Resumable streaming deferred from core, not foreclosed | Proposed |
42-
| [0012](adr/0012-concurrency-strategy.md) | Concurrency strategy expansion (`drop`/`queue`/`debounce`/`concurrent`/`burst` + lock scope implemented; force/steerability names reserved) | Accepted |
43-
| [0013](adr/0013-linear-generic-comments.md) | Linear generic issue/comment participation | Accepted |
44-
| [0014](adr/0014-nats-state-adapter.md) | NATS JetStream state adapter | Accepted |
45-
| [0015](adr/0015-runtime-coordination.md) | Deferred-dispatch admission bound; cross-instance coalescing rejected for now | Accepted |
3+
This page explains why Chat SDK Go is shaped the way it is: the model in
4+
brief, the design goals, how it maps to
5+
[Vercel Chat SDK](#vercel-chat-sdk-alignment), and what it leaves out on
6+
purpose ([non-goals](#non-goals) and [intentional gaps](#intentional-gaps)).
7+
The full record is in [`CONTEXT.md`](../CONTEXT.md) and the
8+
[ADRs](#where-the-design-is-recorded).
469

4710
## The Short Version
4811

49-
For readers who want the model in five paragraphs:
50-
5112
**The runtime coordinates conversations; it does not own your product.**
5213
`chat.Chat` verifies webhooks through adapters, normalizes platform payloads
5314
into events, dedupes them, serializes work per thread with token-owned lock
@@ -92,8 +53,8 @@ diverges and documents the divergence.
9253

9354
Chat SDK Go follows Vercel Chat SDK's conversation semantics where they fit
9455
Go, built outward from a production-shaped Slack slice. It is
95-
not a TypeScript API port and does not promise full feature parity. For
96-
readers who know Vercel Chat SDK, this is the concept-by-concept status map:
56+
not a TypeScript API port and does not promise full feature parity. If you
57+
know Vercel Chat SDK, this table maps each concept to its status here:
9758

9859
| Vercel Chat SDK concept | Chat SDK Go status |
9960
| --- | --- |
@@ -127,9 +88,9 @@ history — are documented on the affected symbols' GoDoc and in the
12788

12889
## Non-Goals
12990

130-
These are deliberate design boundaries, each recorded in an ADR. Most are
131-
permanent ownership boundaries; streaming is the one explicitly *deferred*
132-
boundary — out of the core runtime today, not foreclosed forever.
91+
These are deliberate boundaries, each recorded in an ADR. Most are
92+
permanent: they mark what belongs to your application. Streaming is the
93+
exception; it is *deferred*, out of the core today but not ruled out.
13394

13495
- **Streaming token transport in the core runtime** —
13596
[ADR 0011](adr/0011-resumable-streaming.md) defers token streaming and
@@ -159,29 +120,25 @@ boundary — out of the core runtime today, not foreclosed forever.
159120

160121
## Intentional Gaps
161122

162-
These are not bugs; they are things the current scope deliberately does not
163-
include:
123+
These are smaller things the current scope leaves out on purpose. They are
124+
not bugs.
125+
126+
API shape:
164127

165128
- no TypeScript API compatibility
166129
- no full Vercel Chat SDK feature parity
167130
- no multiple handlers per routing hook
168131
- no lazy runtime initialization
169-
- no Linear personal API key mode, and no single-install static access token
170-
(pre-exchanged access tokens are supported through the multi-tenant
171-
`InstallStore`)
172-
- no Linear streaming, reactions, or Markdown conversion
173-
- no built-in OAuth web flow: authorize/callback/token-exchange routes and
174-
install storage are application-owned
175-
([ADR 0006](adr/0006-multi-tenant-install.md))
176-
- no live Slack end-to-end test in CI
177132
- no dedicated `OnDirectMessage` hook
178133
- no public proactive `OpenDM`, except adapter behavior needed for explicit
179134
ephemeral fallback
180135
- no pattern handlers
181136
- no middleware
182-
- no history persistence APIs: `HistoryReader` is a storage-free live
183-
read-through, implemented by the Slack and Linear adapters
184-
- no thread application state APIs
137+
138+
Messages and content:
139+
140+
- no edit, delete, reaction, or other outbound mutation APIs beyond what a
141+
native interaction response needs
185142
- no JSX cards, files, or typed Block Kit / Adaptive Card payload builders
186143
(native Block Kit content ships as an opaque payload via
187144
`NativeContentPoster`)
@@ -190,10 +147,58 @@ include:
190147
- no synchronous modal `view_submission` response (modal open via
191148
`views.open` ships; the synchronous `response_action` is incompatible with
192149
ack-then-work and is deferred)
193-
- no edit, delete, reaction, or other outbound mutation APIs beyond what a
194-
native interaction response needs
150+
151+
State and history:
152+
153+
- no history persistence APIs: `HistoryReader` is a storage-free live
154+
read-through, implemented by the Slack and Linear adapters
155+
- no thread application state APIs
156+
157+
Linear:
158+
159+
- no Linear personal API key mode, and no single-install static access token
160+
(pre-exchanged access tokens are supported through the multi-tenant
161+
`InstallStore`)
162+
- no Linear streaming, reactions, or Markdown conversion
163+
164+
Operations:
165+
166+
- no built-in OAuth web flow: authorize/callback/token-exchange routes and
167+
install storage are application-owned
168+
([ADR 0006](adr/0006-multi-tenant-install.md))
169+
- no built-in HTTP server or router integrations
195170
- no bundled metrics framework, exporters, or scrape endpoint (an optional
196171
no-op `Observer` seam is provided; OpenTelemetry stays out of the core
197172
import graph)
198-
- no built-in HTTP server or router integrations
173+
- no live Slack end-to-end test in CI
199174
- no adapter marketplace/package conventions
175+
176+
## Where The Design Is Recorded
177+
178+
- [`CONTEXT.md`](../CONTEXT.md) is the project's vocabulary and architecture
179+
document. Read it top to bottom to understand the system. Its `Language`
180+
section defines every domain term (with the synonyms to avoid), grouped by
181+
area. Its `Relationships` section is the closest thing to a formal
182+
specification of the runtime's invariants. Its `Flagged ambiguities`
183+
section explains where and why the design diverges from Vercel Chat SDK.
184+
- [`docs/adr/`](adr/) holds the Architecture Decision Records. Every
185+
significant decision has one, including decisions *not* to build
186+
something:
187+
188+
| ADR | Decision | Status |
189+
| --- | --- | --- |
190+
| [0001](adr/0001-linear-app-actor-slice.md) | Linear app-actor slice before a full Linear adapter | Accepted |
191+
| [0002](adr/0002-async-dispatch.md) | Deferred runtime dispatch (ack-then-work) | Accepted |
192+
| [0003](adr/0003-slash-commands.md) | Command Events and slash command routing | Accepted |
193+
| [0004](adr/0004-interactive-components.md) | Interaction Events and native content instead of a card DSL | Accepted |
194+
| [0005](adr/0005-rate-limit-handling.md) | Rate-limit retry lives in adapters, with typed `RateLimited` errors | Accepted |
195+
| [0006](adr/0006-multi-tenant-install.md) | Multi-tenant installs via app-implemented `InstallStore`; OAuth flows stay app-owned | Accepted |
196+
| [0007](adr/0007-teams-adapter.md) | Microsoft Teams adapter approach (Bot Framework, direct HTTP) | Proposed — gated on a spike |
197+
| [0008](adr/0008-linear-full-adapter.md) | Full Linear agent activity surface (thought/response/action/elicitation/error) plus session updates (plans, external URLs) | Accepted |
198+
| [0009](adr/0009-message-history.md) | Message history stays application-owned; optional storage-free `HistoryReader` | Accepted |
199+
| [0010](adr/0010-observability.md) | Optional `Observer` seam; no OpenTelemetry in core | Accepted |
200+
| [0011](adr/0011-resumable-streaming.md) | Resumable streaming deferred from core, not foreclosed | Proposed |
201+
| [0012](adr/0012-concurrency-strategy.md) | Concurrency strategy expansion (`drop`/`queue`/`debounce`/`concurrent`/`burst` + lock scope implemented; force/steerability names reserved) | Accepted |
202+
| [0013](adr/0013-linear-generic-comments.md) | Linear generic issue/comment participation | Accepted |
203+
| [0014](adr/0014-nats-state-adapter.md) | NATS JetStream state adapter | Accepted |
204+
| [0015](adr/0015-runtime-coordination.md) | Deferred-dispatch admission bound; cross-instance coalescing rejected for now | Accepted |

‎docs/how-to/choose-a-state-backend.md‎

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
# How To Choose A State Backend
22

3-
Runtime state is required: the runtime stores subscribed-thread membership,
4-
event dedupe marks, and thread lock leases in a `chat.State`. It is
5-
coordination state, not product state — keep your application's own data in
6-
your own database keyed by `ThreadID`.
3+
Every bot needs a `chat.State`. The runtime keeps three things there:
4+
which threads are subscribed, which events it has already accepted (dedupe
5+
marks, written before any handler runs), and who holds each thread's lock. That is coordination state, not
6+
product state: keep your application's data in your own database, keyed by
7+
`ThreadID`.
78

8-
Four implementations ship today:
9+
## Pick One
910

10-
| Backend | Module | Use for |
11+
| Backend | Module | Use it when |
1112
| --- | --- | --- |
12-
| Memory | `github.com/coder/chat/state/memory` (in the core module) | Tests and local demos. Lost on restart. |
13-
| Redis | `github.com/coder/chat/state/redis` | Production, horizontally scaled deployments. |
14-
| Postgres | `github.com/coder/chat/state/postgres` | Production, when Postgres is already your coordination store. |
15-
| NATS JetStream | `github.com/coder/chat/state/nats` | Production, when you already run NATS with JetStream. |
13+
| Memory | `github.com/coder/chat/state/memory` (core module) | You are writing tests or following the tutorial. State is lost on restart. |
14+
| Redis | `github.com/coder/chat/state/redis` | You already run Redis. |
15+
| Postgres | `github.com/coder/chat/state/postgres` | You already run Postgres. |
16+
| NATS JetStream | `github.com/coder/chat/state/nats` | You already run NATS with JetStream. |
1617

17-
Redis, Postgres, and NATS live in separate Go modules so applications that only
18-
need core, Slack, or memory state do not pull their dependencies.
18+
The three durable backends are equivalent for the runtime: they implement
19+
the same token-owned lock lease and dedupe contract and pass the same
20+
conformance suite, so any of them lets you run several bot replicas safely.
21+
Pick the one you already operate. Redis and Postgres are tested against
22+
real servers via Testcontainers; NATS is tested against an embedded
23+
JetStream server.
24+
25+
Redis, Postgres, and NATS are separate Go modules, so an application that
26+
uses only the core module does not pull their dependencies.
27+
28+
Whichever you pick, give each bot application its own namespace; see
29+
[One namespace per bot application](#one-namespace-per-bot-application).
1930

2031
## Memory
2132

@@ -121,25 +132,13 @@ runnable example is
121132

122133
## One Namespace Per Bot Application
123134

124-
The `Prefix`/`Namespace` options default to `chat`. If two *independent* bot
125-
applications share one Redis, Postgres, or NATS service with the default,
126-
their subscription, dedupe, and lock records collide — thread IDs carry
127-
platform tenant/channel identity but no application identity, so app A
128-
subscribing a thread can route that thread's follow-ups into app B's
129-
`OnSubscribedMessage`, and one app's locks can suppress the other's events.
130-
Give every bot application its own stable namespace, shared only by that
131-
app's replicas (replicas must share the namespace — that is what makes
132-
dedupe and locking work across them).
133-
134-
## How To Decide
135-
136-
- Writing tests or following the tutorial: use memory.
137-
- Already running Redis: use Redis. Same for Postgres and NATS — the backends
138-
are contract-equivalent, so pick the one you already operate.
139-
- Running more than one bot replica: any of the durable backends works; all
140-
three implement the same token-owned lock lease and dedupe contract, which is
141-
what makes horizontal scaling safe.
142-
143-
All backends are exercised by the same conformance suite; Redis and Postgres
144-
integration tests run against real backends via Testcontainers, and NATS tests
145-
run against an embedded JetStream server.
135+
The Redis `Prefix`, Postgres `Namespace`, and NATS `Prefix` options default to
136+
`chat`. Set them.
137+
138+
- **Replicas of one bot share a namespace.** That is what lets them dedupe
139+
and lock across each other.
140+
- **Independent bots need different namespaces.** Thread IDs identify the
141+
platform tenant and channel but not your application. If two bots share a
142+
backend and a namespace, bot A subscribing a thread can route that
143+
thread's follow-ups into bot B's `OnSubscribedMessage`, and one bot's locks
144+
can suppress the other's events.

0 commit comments

Comments
 (0)