SAM's chat agent is a Cloudflare Durable Object backed by the Think framework—one DO per session—that loads project context, enforces credit budgets in hosted mode, gates tool access through MCP adapters, and manages all WebSocket connections and cleanup for account erasure. The agent wires two context blocks (identity prompt and read-only project context) into Think, scopes tools with Postgres clients to prevent connection leaks, and tracks skill activations via PostHog while adapting MCP tools to prevent cross-project targeting and meter credits consistently.
SamChatAgent in src/server/features/sam/SamChatAgent.ts is a Cloudflare Durable Object backed by the Think framework, with one DO instance per chat session; the DO instance name is the session id.[1] SamChatAgent sets workspaceBash = false to disable Think's bash workspace tool; the underlying dependency is also stubbed out of the bundle to keep ~30 MB of eagerly-evaluated source off the isolate's baseline heap.[1] SamChatAgent.fetch in src/server/features/sam/SamChatAgent.ts persists the public origin to Durable Object storage on every request so the value survives hibernation and is available on WebSocket-only wake-ups where fetch() did not run first.[1] SamChatAgent.destroyForErasure in src/server/features/sam/SamChatAgent.ts closes all WebSockets with code 1000, cancels all chats, waits for stability, then deletes all alarm and storage data — used for account erasure.[1]
SamChatAgent.loadSamContext in src/server/features/sam/SamChatAgent.ts calls SamSessionRepository.getSessionById, then ProjectRepository.getProjectById, then queries the user table for the creator's email — resolving the normalized session row into a full SamContext once per DO lifetime.[1] SamChatAgent.configureSession in src/server/features/sam/SamChatAgent.ts attaches two context blocks to every Think session: "soul" (identity/system prompt) and "project_context" (project shared memory).[1] The "project_context" Think context block in SamChatAgent is read-only from SAM's perspective — no set provider is registered — so Think does not expose a set_context tool; writes to project context go through the update_project_context tool, shared with MCP clients and the settings UI.[1] SamChatAgent.renderProjectContext in src/server/features/sam/SamChatAgent.ts calls ProjectContextService.renderProjectContextMarkdown inside its own withPgClient scope because Think's context-block providers have no ambient Postgres client.[1] SamChatAgent.buildSoulPrompt in src/server/features/sam/SamChatAgent.ts activates intake mode — triggering SAM's onboarding flow — when the project context is missing the "business_overview" section.[1]
SamChatAgent.beforeTurn in src/server/features/sam/SamChatAgent.ts gates every turn on credit availability in hosted mode; if the session row is not found, it returns a refusalTurn with a canned message instead of executing the model.[1] In self-hosted mode, SamChatAgent skips the credit check entirely because self-hosters supply their own provider keys and carry no Autumn balance.[1]
SamChatAgent.getModel in src/server/features/sam/SamChatAgent.ts builds the LLM via buildChatAgentModel using OPENROUTER_API_KEY (required) and the optional OPENROUTER_MODEL env var.[1] SamChatAgent.afterToolCall in src/server/features/sam/SamChatAgent.ts captures a "sam:skill_activated" PostHog event for Think-internal activate_skill tool calls, using ctx.waitUntil to ensure the flush completes before the DO shuts down.[1]
adaptMcpTool in src/server/features/sam/samChatTools.ts strips projectId from the schema exposed to the model and injects the session's projectId at call time, preventing the model from targeting another project or hallucinating a wrong ID.[2] adaptMcpTool also wraps each tool handler with instrumentMcpToolHandler, so project scoping, credit metering, and mcp:tool_call telemetry (source "in_app_agent", null clientId) all match the external MCP path.[2] Each SAM tool call in src/server/features/sam/samChatTools.ts wraps its handler in withPgClient(...) so it scopes its own Postgres client per execution rather than holding an ambient connection across the inference loop.[2] toModelOutput in src/server/features/sam/samChatTools.ts flattens an MCP CallToolResult into { summary, data } (or { summary } when no structured content exists), combining text parts and the structured payload for the model.[2]
scrapeTools in src/server/features/sam/samChatTools.ts exposes two credit-free tools: map_links (discovers homepage + sitemap URLs, up to 60) and read_pages (fetches up to 10 pages as plain text); both can target competitor domains, not just the project's own site.[2] The map_links tool in src/server/features/sam/samChatTools.ts calls discoverSiteUrls and returns { blocked: true, urls: [], note } when the site is unreachable, or { blocked: false, urls } on success; when the project has no domain set it returns { error: "This project has no website set — ask the user for their site first." } rather than throwing.[2] The waitingAuditStatusTool in src/server/features/sam/samChatTools.ts performs server-side polling of get_audit_status: it polls every 2 seconds (up to a 50-second budget) and returns as soon as the progress summary line changes, so one model tool call covers roughly one minute of quiet waiting instead of forcing the model to spin-poll.[2]
Sources