This page explains why Chat SDK Go is shaped the way it is: the model in
brief, the design goals, how it maps to
Vercel Chat SDK, and what it leaves out on
purpose (non-goals and intentional gaps).
The full record is in CONTEXT.md and the
ADRs.
The runtime coordinates conversations; it does not own your product.
chat.Chat verifies webhooks through adapters, normalizes platform payloads
into events, dedupes them, serializes work per thread with token-owned lock
leases, and routes to your single-slot handlers. Everything your product
stores — transcripts, user records, workflow state — lives in your database,
keyed by the opaque ThreadID.
Adapters own the platform boundary. Signature verification, payload
normalization, outbound rendering, rate-limit retries, and platform quirks
live inside the adapter. Platform-specific power is reached deliberately via
typed adapter access (chat.AdapterAs), never by making raw platform structs
the normal API.
State is required and small. Subscriptions, dedupe marks, and locks — that is all. Memory for development; Redis, Postgres, or NATS for production.
Events are broader than messages. A slash command and a button click are normalized events with their own hooks, not messages. All events ride the same dispatch spine.
Semantic compatibility, not feature parity. Vercel Chat SDK's conversation model is the precedent; its TypeScript API shapes are not. Where Go idioms or operational safety argue otherwise, this SDK deliberately diverges and documents the divergence.
- Go-native API built around
context.Context,net/http, small interfaces, and explicit errors. - Slack-first vertical slice before claiming multi-platform portability.
- Required runtime state for subscriptions, dedupe, and locks: memory for tests and local development; Redis, Postgres, or NATS JetStream for horizontally scaled production deployments.
- Thread-oriented application code: handle a message, subscribe the thread, reply to the thread.
- Platform escape hatches without making raw platform structs the normal API.
- Vercel Chat SDK behavior as the default precedent unless it is non-idiomatic in Go or outside the documented scope.
Chat SDK Go follows Vercel Chat SDK's conversation semantics where they fit Go, built outward from a production-shaped Slack slice. It is not a TypeScript API port and does not promise full feature parity. If you know Vercel Chat SDK, this table maps each concept to its status here:
| Vercel Chat SDK concept | Chat SDK Go status |
|---|---|
Chat runtime |
Implemented as chat.Chat |
| Platform adapters | Slack (supported) and Linear (experimental) implemented; Teams is a spike |
| Normalized events and thread-scoped replies | Implemented |
onNewMention |
Implemented as OnNewMention |
onSubscribedMessage |
Implemented as OnSubscribedMessage |
| Thread subscriptions | Implemented with explicit Thread.Subscribe / Thread.Unsubscribe |
| Runtime state adapters | Memory, Redis, Postgres, and NATS JetStream implemented |
| Direct messages | Routed as implicit new mentions, then subscribed messages |
| Ephemeral messages | Slack native ephemeral plus explicit DM fallback |
| Thread handle reconstruction | Implemented with Chat.Thread |
| AI streaming responses | Deferred from core, not foreclosed (ADR 0011); long generation uses ack-then-work |
| Slash commands | Implemented as OnCommand Command Events (Slack) |
| Interactive components (buttons, menus) | Implemented as OnInteraction block_actions (Slack) |
| Native rich content (Block Kit) | Implemented as the NativeContentPoster Optional Capability (Slack) |
Modal open (views.open) |
Implemented as a Slack adapter Optional Capability |
Modal view_submission synchronous response |
Deferred (incompatible with ack-then-work) |
| Cards, JSX-style cards, native payload builders | Not implemented |
| Pattern handlers | Not implemented |
| Observability metrics/tracing | Optional Observer seam, no-op default, no OTel dependency in core |
| Message history persistence | App-owned (Thread Application State); thin live read-through via the HistoryReader Optional Capability (Slack, Linear) |
| AI-message conversion helpers | Not implemented |
| Middleware | Not implemented |
The behavioral differences that matter when porting handler code — single-slot hooks, fail-fast construction, explicit subscriptions, application-owned history — are documented on the affected symbols' GoDoc and in the reference.
These are deliberate boundaries, each recorded in an ADR. Most are permanent: they mark what belongs to your application. Streaming is the exception; it is deferred, out of the core today but not ruled out.
- Streaming token transport in the core runtime — ADR 0011 defers token streaming and pub/sub transports out of core (without foreclosing a future optional capability); long generation is ack-then-work (ADR 0002) posting one finished message.
- LLM routing and prompt orchestration — the runtime coordinates
conversations; LLM calls, prompt assembly, and generation pipelines are
application concerns inside handlers
(ADR 0011 classifies generation and
stream persistence as app/LLM concerns;
CONTEXT.mddefines the runtime boundary). - A generative-UI card DSL —
ADR 0004 rejected a cross-platform
card model as lossy; platform-native payloads ship opaquely via
NativeContentPosterinstead. - RAG and embeddings — ADR 0009 keeps embeddings, summaries, and RAG corpora as Thread Application State in the application's own database keyed by Thread ID.
- Durable transcript persistence in
chat.State— ADR 0009 rejected baking a message store into runtime state;chat.Statestays subscriptions, dedupe, and locks. - App-user auth orchestration — ADR 0006 scopes the install store to platform-tenant credentials; account linking, login prompts, and OAuth web flows are Application Identity and stay app-owned.
These are smaller things the current scope leaves out on purpose. They are not bugs.
API shape:
- no TypeScript API compatibility
- no full Vercel Chat SDK feature parity
- no multiple handlers per routing hook
- no lazy runtime initialization
- no dedicated
OnDirectMessagehook - no public proactive
OpenDM, except adapter behavior needed for explicit ephemeral fallback - no pattern handlers
- no middleware
Messages and content:
- no edit, delete, reaction, or other outbound mutation APIs beyond what a native interaction response needs
- no JSX cards, files, or typed Block Kit / Adaptive Card payload builders
(native Block Kit content ships as an opaque payload via
NativeContentPoster) - no Slack shortcuts or Block Kit workflow steps (
block_actionsbuttons and menus are routed as Interaction Events) - no synchronous modal
view_submissionresponse (modal open viaviews.openships; the synchronousresponse_actionis incompatible with ack-then-work and is deferred)
State and history:
- no history persistence APIs:
HistoryReaderis a storage-free live read-through, implemented by the Slack and Linear adapters - no thread application state APIs
Linear:
- no Linear personal API key mode, and no single-install static access token
(pre-exchanged access tokens are supported through the multi-tenant
InstallStore) - no Linear streaming, reactions, or Markdown conversion
Operations:
- no built-in OAuth web flow: authorize/callback/token-exchange routes and install storage are application-owned (ADR 0006)
- no built-in HTTP server or router integrations
- no bundled metrics framework, exporters, or scrape endpoint (an optional
no-op
Observerseam is provided; OpenTelemetry stays out of the core import graph) - no live Slack end-to-end test in CI
- no adapter marketplace/package conventions
CONTEXT.mdis the project's vocabulary and architecture document. Read it top to bottom to understand the system. ItsLanguagesection defines every domain term (with the synonyms to avoid), grouped by area. ItsRelationshipssection is the closest thing to a formal specification of the runtime's invariants. ItsFlagged ambiguitiessection explains where and why the design diverges from Vercel Chat SDK.docs/adr/holds the Architecture Decision Records. Every significant decision has one, including decisions not to build something:
| ADR | Decision | Status |
|---|---|---|
| 0001 | Linear app-actor slice before a full Linear adapter | Accepted |
| 0002 | Deferred runtime dispatch (ack-then-work) | Accepted |
| 0003 | Command Events and slash command routing | Accepted |
| 0004 | Interaction Events and native content instead of a card DSL | Accepted |
| 0005 | Rate-limit retry lives in adapters, with typed RateLimited errors |
Accepted |
| 0006 | Multi-tenant installs via app-implemented InstallStore; OAuth flows stay app-owned |
Accepted |
| 0007 | Microsoft Teams adapter approach (Bot Framework, direct HTTP) | Proposed — gated on a spike |
| 0008 | Full Linear agent activity surface (thought/response/action/elicitation/error) plus session updates (plans, external URLs) | Accepted |
| 0009 | Message history stays application-owned; optional storage-free HistoryReader |
Accepted |
| 0010 | Optional Observer seam; no OpenTelemetry in core |
Accepted |
| 0011 | Resumable streaming deferred from core, not foreclosed | Proposed |
| 0012 | Concurrency strategy expansion (drop/queue/debounce/concurrent/burst + lock scope implemented; force/steerability names reserved) |
Accepted |
| 0013 | Linear generic issue/comment participation | Accepted |
| 0014 | NATS JetStream state adapter | Accepted |
| 0015 | Deferred-dispatch admission bound; cross-instance coalescing rejected for now | Accepted |