SNA

Introduction

Understand SNA, boot the server, open a session, and send a first prompt.

The shortest path from zero to live events. By the end of this page you'll have an SNA server running in your app, a session attached to Claude Code (or Codex / OpenCode), and tokens streaming back over WebSocket.

Build agentic applications with SNA

SNA is a runtime layer for agentic applications: products where a long-running agent is part of the application, carries session history, uses tools, asks for permission, and streams state back to the product.

That means you can build:

  • Sessionful product agents that keep project context and continue across turns instead of answering one prompt at a time.
  • Cache-friendly long conversations where the app keeps sending to the same SNA session instead of reassembling history and cache settings for every turn.
  • Tool-using workflows where the agent can call the same capabilities its CLI runtime already knows how to use.
  • Human-in-the-loop applications with permission requests surfaced through your own UI.
  • Runtime-harness-backed agents that use the orchestration layer tuned by Claude Code, Codex, or OpenCode instead of rebuilding that loop from raw model APIs.
  • Multi-runtime products where the product API stays stable while the underlying agent runtime can change.

What you're about to build

SNA is a small server you embed in your app. You ask it to start a session, the server spawns an agent CLI as a managed subprocess and exposes it through one HTTP + WebSocket API. From the consumer side it looks like talking to a backend; under the hood it's a real Claude Code / Codex / OpenCode process doing the work.

your app

   │  HTTP for control (send, interrupt, kill, …)
   │  WS   for events (assistant_delta, tool_use, complete, …)

SNA server  ──spawn──►  claude | codex | opencode

Five steps below. For the why behind the design choices (canonical history, runtime swaps, attribution), read What is SNA? next.

Prerequisites

  • Node.js 20+.
  • At least one runtime CLI: claude, codex, or opencode on PATH, or an explicit runtimePaths entry when starting SNA.
  • A package manager. Examples below use pnpm, but npm / yarn / bun work too.

1. Install

pnpm add @sna-sdk/core@0.17.2 @sna-sdk/client@0.17.2

SNA is still in the 0.x.x line, so pin exact package versions in apps that depend on it. See Install for the stability note; bugs and feature requests belong in GitHub issues.

See Install for the full prerequisite checklist plus CLI overrides.

2. Boot the server

In your app:

import { resolveClaudeCli, startSnaServer } from "@sna-sdk/core/node";

const claude = resolveClaudeCli();

const sna = await startSnaServer({
  appId: "my-app",
  port: 3099,
  dbPath: "./data/sna.db",
  runtimePaths: {
    claudeCode: claude.path,
  },
});

This forks the SNA server as a managed child process. sna.port holds the bound port (auto-allocated if 0), sna.connection is passed to SDK clients, sna.appId is tagged onto sessions created by this server, and sna.stop() sends SIGTERM.

3. Connect a client

import { SnaClient } from "@sna-sdk/client";

const client = new SnaClient(sna.connection);
client.connect();

4. Open a session and start the agent

const { sessionId } = await client.sessions.create({ label: "scratch" });

await client.agent.start(sessionId, {
  provider: "claude-code",
  model: "claude-sonnet-4-6",
});

5. Subscribe to events, then send

client.agent.onEvent(({ event }) => {
  if (event.type === "assistant_delta") process.stdout.write(event.delta as string);
  if (event.type === "tool_use") console.log("\n[tool]", event.message);
  if (event.type === "complete") console.log("\n[done]", event.data);
});
await client.agent.subscribe(sessionId);

await client.agent.send(sessionId, "List the files in this directory.");

That's the whole loop. Tokens stream into assistant_delta, tool invocations fire tool_use, the turn finishes on complete with usage and cost.

One-shot instead?

If you just want a single answer (no session, no events), use completion():

import { completion } from "@sna-sdk/core";

const result = await completion({
  prompt: "Summarize: ...",
  provider: "claude-code",
  model: "claude-haiku-4-5",
  onDelta: (chunk) => process.stdout.write(chunk),
});
// result.text, result.usage, result.costUsd, result.durationMs

See Cookbook → Streaming for the three streaming flavors (in-process callback, SSE, full event stream).

Next

On this page