Skip to content

Latest commit

 

History

History
204 lines (173 loc) · 10.8 KB

File metadata and controls

204 lines (173 loc) · 10.8 KB

Architecture And Design Decisions

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 Short Version

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.

Design Goals

  • 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.

Vercel Chat SDK Alignment

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.

Non-Goals

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.md defines the runtime boundary).
  • A generative-UI card DSL — ADR 0004 rejected a cross-platform card model as lossy; platform-native payloads ship opaquely via NativeContentPoster instead.
  • 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.State stays 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.

Intentional Gaps

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 OnDirectMessage hook
  • 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_actions buttons and menus are routed as Interaction Events)
  • no synchronous modal view_submission response (modal open via views.open ships; the synchronous response_action is incompatible with ack-then-work and is deferred)

State and history:

  • no history persistence APIs: HistoryReader is 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 Observer seam is provided; OpenTelemetry stays out of the core import graph)
  • no live Slack end-to-end test in CI
  • no adapter marketplace/package conventions

Where The Design Is Recorded

  • CONTEXT.md is the project's vocabulary and architecture document. Read it top to bottom to understand the system. Its Language section defines every domain term (with the synonyms to avoid), grouped by area. Its Relationships section is the closest thing to a formal specification of the runtime's invariants. Its Flagged ambiguities section 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