Embedding Atomic in a web server
This guide puts an Atomic agent behind an HTTP endpoint, using a Next.js App Router project as the example. The same pattern works in any long-running Node.js server: keep one session per conversation, start a prompt per request, and stream the session’s events back as newline-delimited JSON (NDJSON). Read the SDK page first forcreateAgentSession() and session events.
The examples use createTranscript(), createAgentSessionAdapter(), and RunOpts.onStageSessionEvent, which require an Atomic release newer than 0.9.28-alpha.1. Check the changelog for your installed version.
Install
@embedded-postgres/<platform>, protobufjs, and @google/genai, and reports them after bun add @bastani/atomic. You don’t need to add them to trustedDependencies in package.json: Atomic prepares the embedded Postgres runtime itself the first time it starts it, and it doesn’t depend on the other scripts.
Keep Atomic out of the server bundle
Atomic finds its bundled resources and native modules relative to its installed package, so it must load fromnode_modules rather than from Next.js’s server bundle. List it in serverExternalPackages (Next.js 15 and later):
node_modules once @bastani/atomic is external. The other entries cover packages your app may import directly, such as typebox for workflow schemas, so your code and Atomic share one copy. Add any other Atomic dependency you import yourself.
Every route that imports Atomic must use the Node.js runtime:
import type from @bastani/atomic, which adds nothing to the browser bundle.
Keep one session per conversation
AnAgentSession holds the conversation’s history, so create one per conversation and reuse it across requests. Keep sessions on globalThis: in next dev, module-level variables reset on every hot reload, but globalThis survives, so open conversations don’t vanish and old sessions aren’t leaked. Restart the dev server after changing session options; existing sessions keep the options they were created with.
SessionManager.inMemory() keeps history in process memory. To keep conversations across restarts, pass a file-backed SessionManager instead; see Sessions. Call closeConversation() when a conversation ends or has been idle for a while, because each open session holds resources until it is disposed.
Sessions on globalThis live in one server process. Run this on a long-lived Node.js server, and route each conversation to the same instance if you run several. Short-lived serverless functions lose the session between requests.
Stream a prompt
The route below starts one prompt and streams the assistant’s output as NDJSON.createTranscript() turns session events into ordered, JSON-serializable parts (text, thinking, and tool calls joined to their results), so each line carries the complete output so far and the client simply replaces what it shows.
- One prompt at a time.
session.prompt()rejects while the session is already streaming. The route answers409instead.busyis set synchronously, so two requests that arrive together can’t both start a prompt. To queue a message instead of rejecting it, callsession.prompt(message, { streamingBehavior: "followUp" }). - Stop on disconnect. When the browser disconnects or aborts its
fetch,request.signalaborts and the stream is cancelled. Both callsession.abort(), which stops the current turn and its tools but keeps the session usable. - Payload size. Each line repeats the whole output so far. For long answers, send lines on a timer instead of on every event, or send only the parts that changed.
createTranscript() reports a tool result’s text by default. To send a tool’s structured details instead, pass createTranscript({ toolResult: (toolName) => (toolName === "todo" ? "details" : "content") }).
Read the stream in the browser
type: text and thinking are strings, and a toolCall part has name, arguments, and, once the tool reports progress or finishes, a result with content, isError, and isPartial. Pass an AbortController’s signal to stop the agent from the page.
Run a workflow from a route
run() from @bastani/atomic/workflows runs a workflow definition in the server process. Each stage runs as an in-process Atomic session, so run() needs no adapters. Use createAgentSessionAdapter() when every stage should share session options, such as the same tool restrictions as your chat sessions:
onStageSessionEvent receives every stage’s session events tagged with its run and stage ids, including stages that switch to a fallback model and stages of nested workflows. Aborting signal, here when the client disconnects, cancels the run.
Choose workflow durability
By default,run() makes workflow state durable by starting a managed local Postgres, so a run can resume after the process exits. Request-scoped servers rarely want that:
durability: { mode: "memory" }keeps state in memory for this call only. No Postgres starts, and the run can’t resume after the process exits. Use it when the request owns the run.durability: { mode: "durable", systemDatabaseUrl }stores state in a Postgres database you run, such as a hosted one, and Atomic doesn’t start its own. Use it in deployments that need runs to survive restarts.DBOS_SYSTEM_DATABASE_URL, when set, overrides the URL.
Secure the endpoint
A route that drives an agent session lets whoever calls it run tools on your server with the server process’s permissions. Atomic has no built-in sandbox.- Require authentication on every route that creates sessions, sends prompts, or starts workflows, and scope conversation ids to the authenticated user, as
getConversation()does above. - Allow only the tools you need.
toolsis an allowlist; the examples allow only the read-onlyread,find, andsearch. Leave outbash,edit, andwriteunless the agent must change files or run commands.excludedToolsremoves tools, andnoTools: "all"exposes none.ask_user_questionneeds a person to answer, so leave it out of headless sessions. See Tools. - Disable builtins you don’t use.
builtinsturns off the shipped workflows, subagents, MCP, web access, and Intercom packages, along with their tools. - Choose the working directory deliberately.
cwdsets the starting directory for tool paths and controls which project settings, context files, and extensions Atomic discovers. It does not restrict file access: tools can use absolute paths and paths outsidecwd. Point it at a directory you control, never at an upload or a user-supplied path. - Isolate the process in a container, VM, or restricted OS account when callers must not access other server files, even with read-only tools, or when the agent can write files or run commands. See Containerization.
- Keep credentials on the server. Provider keys come from the server’s
ModelRuntime. Never send them to the browser or accept them from requests.
Next steps
- SDK for sessions, prompting, and events.
- SDK API reference for every
createAgentSession()option. - Workflow API reference for
run()andRunOpts.