<!-- okf
type: Reference
title: "@namzu/sdk"
description: >-
  An agent kernel for TypeScript. Runs an agent as supervised turns of a
  session, with an identity, a budget, a permission boundary and a durable
  session log. Renders no UI, hosts no service, and has no preferred model vendor.
tags: [readme, package, sdk, agent-kernel]
status: stable
generated: { by: human:bahadirarda, at: 2026-08-21T00:00:00Z }
-->

<div align="center">

<h1>@namzu/sdk</h1>

**An agent kernel for TypeScript.**

[![npm](https://img.shields.io/npm/v/@namzu/sdk.svg)](https://www.npmjs.com/package/@namzu/sdk)
[![build](https://github.com/cogitave/namzu/actions/workflows/ci.yml/badge.svg)](https://github.com/cogitave/namzu/actions/workflows/ci.yml)
[![license](https://img.shields.io/badge/license-FSL--1.1--MIT-blue.svg)](https://github.com/cogitave/namzu/blob/main/LICENSE.md)

[Install](#install) · [Quick start](#quick-start) · [What you get](#what-you-get) · [Documentation](#documentation)

</div>

---

An agent that works in a demo is a loop around a model call. An agent that
works in production is that loop plus everything around it — a budget that
stops it, an identity that attributes it, a boundary it cannot talk its way
past, a record that can survive the process when durable stores are configured,
and a way to shrink a conversation that is about to overflow without corrupting
it.

This is the kernel for those other things. It runs an agent the way an operating
system runs a process: given an identity and a budget, scheduled, checkpointed,
and optionally confined by the sandbox a host supplies. It renders no UI,
requires no database, hosts no service, and has no preferred model vendor.

## Install

```bash
pnpm add @namzu/sdk zod@^3
```

Requires Node.js 20+, ESM, and TypeScript strict mode. Pin Zod to v3, the
supported peer range.

The kernel ships alone. Add a driver for whichever backend you use —
[`@namzu/anthropic`](https://www.npmjs.com/package/@namzu/anthropic),
[`@namzu/openai`](https://www.npmjs.com/package/@namzu/openai),
[`@namzu/bedrock`](https://www.npmjs.com/package/@namzu/bedrock),
[`@namzu/deepseek`](https://www.npmjs.com/package/@namzu/deepseek),
[`@namzu/openrouter`](https://www.npmjs.com/package/@namzu/openrouter),
[`@namzu/ollama`](https://www.npmjs.com/package/@namzu/ollama),
[`@namzu/lmstudio`](https://www.npmjs.com/package/@namzu/lmstudio),
or the zero-dependency [`@namzu/http`](https://www.npmjs.com/package/@namzu/http).
With none of them the kernel still runs against `MockLLMProvider`, which is
pre-registered and scriptable.

## Quick start

This first turn needs no API key or network. The mock supplies a scripted model
reply; the kernel executes the same turn loop used by service-backed drivers.

```ts
import { ProviderRegistry, runAgent } from '@namzu/sdk'

const { provider } = ProviderRegistry.create({ type: 'mock', responseText: 'Paris.' })

const { output, turn, identity } = await runAgent({
  provider,
  model: 'mock-model',
  prompt: 'What is the capital of France?',
})

console.log(output)          // Paris.
console.log(turn.stopReason) // end_turn
console.log(identity)        // { sessionId, topicId, projectId, tenantId }
```

Save it as `agent.ts` in an ESM project and run it with `pnpm exec tsx agent.ts`
after adding `tsx` as a development dependency. `runAgent` creates the four
identity values when absent and returns them. Pass both `identity` and
`turn.messages` into the next call to continue a conversation; identity alone
does not load its history. For store-backed delegation, supply the identity of
records created in the session store.

### Run a tool

This complete example scripts two model turns and executes a real local tool.
The mock requests `add`, then supplies the final answer; it does no inference.

```ts
import { defineTool, MockLLMProvider, runAgent, ToolRegistry } from '@namzu/sdk'
import { z } from 'zod'

const tools = new ToolRegistry()
tools.register(defineTool({
  name: 'add',
  description: 'Add two numbers.',
  inputSchema: z.object({ a: z.number().finite(), b: z.number().finite() }),
  category: 'custom',
  permissions: [],
  readOnly: true,
  destructive: false,
  concurrencySafe: true,
  execute: async ({ a, b }) => ({ success: true, output: String(a + b) }),
}))

const provider = new MockLLMProvider({
  turns: [
    { toolCalls: [{ name: 'add', args: { a: 20, b: 22 } }] },
    { text: '42' },
  ],
})

const { output, turn } = await runAgent({
  provider,
  model: 'mock-model',
  tools,
  prompt: 'Add 20 and 22.',
  maxIterations: 4,
  tokenBudget: 8192,
  timeoutMs: 30_000,
})

console.log(output) // 42
console.log(turn.messages.filter((message) => message.role === 'tool'))
```

To use inference, install a [provider driver](#install) and replace the mock
provider and model with that driver's configuration. The tools and the
`runAgent` call stay the same.

Each call is one turn of a session, with the kernel's budgets, tool loop and
checkpoints. The session is recorded in one append-only log under
`NAMZU_HOME` (default `~/.namzu`), in `projects/<slug>/<session-id>.jsonl`,
and nothing is written under the working directory; see the
[session log](https://github.com/cogitave/namzu/blob/main/docs/sdk/session-log.md).
A session has at most one active turn: starting another while one is running
or paused throws `TurnInProgressError`. `ReactiveAgent` exposes additional
configuration such as compaction and where the session is stored; the config
passed to its `run` method requires `sessionId`, `topicId`, `projectId` and
`tenantId`. OS isolation is explicit rather than ambient: supply a
`sandboxProvider` when the host requires it. Direct SDK turns use a disposable
sandbox workspace unless `sandbox: { workspace: 'working-directory' }` is
selected and the provider advertises support for rooting itself at the turn's
declared working directory. Pass an `InMemorySessionLog` as `sessionLog` to
keep a session entirely in memory, and configure telemetry exporters when the
process must export what it did.

## What you get

| | |
|---|---|
| **Boundary** | a permission gate decides before dispatch; configured sandbox providers add OS confinement |
| **Budget** | tokens, money, wall clock and iterations, enforced rather than hoped for |
| **Identity** | tenant → project → topic → session → turn → message, on every record and span |
| **Durability** | one hash-chained log per session, with checkpoints beside it, survives the process; SQLite is only a rebuildable index |
| **Compaction** | a conversation about to overflow is shrunk without being corrupted |
| **Observability** | telemetry and log seams whose providers and sinks the host owns |

Before a provider receives carried history, the kernel validates tool-call
chronology. Orphaned and displaced results are removed, abandoned calls receive
an explicit unknown-outcome error result, and duplicate call ids fail closed.
Durable approval or crash-resume authority is resolved first so an owned call
is completed exactly once. Hosts receive `message_history_repaired` with source
and counts before the model call; conversation and tool content stay out of the
event.

Stored image and document references are materialized under the turn's caller
signal before provider work starts. A pre-cancelled turn performs no attachment
store I/O; cancellation also settles the turn when a custom or remote store
ignores the signal, while retaining the unresolved references in its durable
message record. `AttachmentStore.get` receives an optional
`AttachmentOperationOptions` so implementations can stop their own I/O. The
caller keeps ownership of its controller, and a late store result is never
published into a cancelled turn. `resumeSession` carries its already-selected
checkpoint snapshot into the same boundary, so cancellation neither rereads a
non-cooperative checkpoint backend nor replaces prior history, usage, or a new
queued reference with an incomplete snapshot. The selected checkpoint also
carries its durable trace parent into the cancelled turn, preserving one
cross-process timeline without a second checkpoint read.

Hosts that discover scoped repository policy can supply a
`ProjectInstructionContext` to `query`, `runAgent`, `ReactiveAgent`, or
`SupervisorAgent`. Its first-request snapshot is structurally tagged and
retained; completed registry calls, including nested dispatch, can publish a
replacement immediately after the complete tool-result batch. Each callback
receives the turn signal and the exact accepted message prefix; each returned
snapshot is committed before the next observation starts, so cancellation can
discard an unfinished suffix without losing accepted policy state. This
channel does not create a human continuation, so a terminal tool or stop
predicate cannot strand the update. Canonical project-relative `AGENTS.md`
provenance survives compaction and lets a reconstructed host re-read disk
authority rather than trusting persisted policy text.

High-level `ReactiveAgent` and `SupervisorAgent` configurations also accept
`paths`, a `SessionPaths`. Supplying one puts the session log, its child
sessions, checkpoints, token ledger and task state under that root instead of
`resolveNamzuHome()`. A project id is minted once per working directory into
`projects/<slug>/project.json`; two processes that open the same directory at
the same moment adopt the same id.

`TopicManager` is the lifecycle authority for the durable subject above a
session. Supply it to agent and handoff dependencies as `topicManager`; spawn
and handoff then share the same archived-topic gate. Hosts can distinguish
`TopicArchivedError`, `TopicNotEmptyError`, and `StaleTopicError` directly from
the package root, and each carries `details.topicId`.

## Documentation

- [All docs](https://github.com/cogitave/namzu/tree/main/docs)

## License

FSL-1.1-MIT, converting to MIT two years after each release.
