Skip to content

AI chat architecture

newsence chat is a Cloudflare ChatAgent Durable Object. The Web app owns authentication, product data, and billing storage; the chat Worker owns model orchestration and AI cost policy; the Core Worker owns corpus reads and search.

flowchart LR
    UI["Web chat UI\nuseAgentChat"]
    Agent["ChatAgent Durable Object"]
    Engine["engine.ts\nprompt + tool gating"]
    Stream["stream.ts\nAI SDK streamText"]
    Core["Core service binding\ncorpus search/read"]
    Domain["App DOMAIN binding\ndocuments/workspaces/billing"]
    Model["AI Gateway providers"]

    UI <--> Agent
    Agent --> Engine
    Engine --> Stream
    Engine <--> Domain
    Stream <--> Core
    Stream <--> Domain
    Stream <--> Model
  1. web-tanstack/src/components/chat/useChatRuntime.ts connects with useAgent and useAgentChat. The request carries the selected model, language, optional workspace, and attached-context keys. Skill shortcuts send ordinary user messages through the same path as typed prompts.
  2. workers/chat-worker/src/chat/agent.ts authenticates the app-injected user and coordinates Durable Object state, history, reconnects, and title sync.
  3. workers/chat-worker/src/chat/engine.ts gets the authoritative billing snapshot through DOMAIN and builds the prompt and allowed tool set. workers/chat-worker/src/chat/stream.ts passes them to AI SDK streamText.
  4. search-news and resource reads go through Core service-binding RPC. Document, workspace, resource-link, and billing writes go through the App DOMAIN binding. The chat Worker does not own those tables.
  5. Tool parts and transient progress data stream to the UI. Final ordered message parts are persisted for history reconstruction; transient parts are excluded by AI SDK.

workers/chat-worker/src/chat/tools/registry.ts is the only tool registry. It constructs request-scoped AI SDK tools and applies product feature gates.

Tool Responsibility
search-news Filter-aware bilingual retrieval from the canonical corpus
search-web Exa fallback when corpus search is insufficient
read-context Batch-read resources, documents, collections, or saved URLs
create-workspace Create an app-owned workspace when the plan allows it
create-document Generate Markdown, convert it to Tiptap JSON, and persist a document in a workspace
edit-document Edit an existing app-owned document
add-resource Pin cited resource IDs or newly ingested URLs to a workspace
generate-image Generate, meter, and persist an image resource
create-podcast Start the workspace podcast workflow

Daily report, summary, rewrite, social, and podcast shortcuts are regular user messages. AI SDK’s standard multi-step streamText loop selects and executes the same request-scoped tools used by manually typed prompts.

Tools return raw JSON-serializable results. They throw on failure so AI SDK emits an error tool result. Progress previews use typed data-* parts through the stream writer; live-only parts must be transient.

Search results expose canonical resources.id values. read-context can load their full text through Core. After a document is created, add-resource sends the cited resource IDs and external URLs to the App DOMAIN binding and pins the resolved resources to the returned workspace ID.

These pins are workspace context, not document-private citation rows. Markdown citations remain ordinary links in generated content and are rendered by the Streamdown-based chat renderer. Relational ownership uses resource_links; display citations do not create another resource identity or read model.

The current UI is intentionally small:

File Responsibility
components/chat/ChatPanel.tsx Composes session state, conversation, prompt composer, and page/rail layouts
components/chat/useChatRuntime.ts Agent connection, session switching, send/retry, cache invalidation
components/chat/ChatConversation.tsx Empty-state presets, message list, errors, retry, and streaming state
components/chat/ChatMessageContent.tsx Renders ordered text, reasoning, tools, files, and typed data parts
components/chat/AgentSteps.tsx Tool progress/results, including corpus and web search
components/chat/MarkdownStreamContent.tsx Lazy boundary for streaming/static Markdown rendering
components/chat/MarkdownStreamContentImpl.tsx Streamdown rendering, CJK plugin, links, and prose component overrides
components/chat/PodcastCard.tsx Podcast workflow status and audio
components/chat/PromptComposer.tsx Input, model selection, context picker, audio shortcut, submit/stop

There is no separate citation component or document-progress component. Tool results render through AgentSteps, and citations remain links in Markdown.

  • web-tanstack/src/lib/billing/plans.ts owns plan and product policy.
  • web-tanstack/src/server/domain/billing.ts owns subscription, credit account, ledger, and atomic deductions.
  • workers/chat-worker/src/chat/billing/ owns model cost estimates, preflight, and settlement calls through DOMAIN.

See Prompt and billing for the detailed policy flow.