TypeScript/Node.js SDK for Agent Assembly, licensed under Apache 2.0.
Pre-1.0 / release candidate. The current release line is
0.0.1-rc.x, published to npm under thercdist-tag and cut as a GitHub Release.
The public surface (
initAssembly,withAssembly) is stabilizing but may change between pre-releases, and per-platform native packaging is still being hardened. Pin an exact version for reproducible installs and review the release notes before upgrading. Production deployments should track the first0.1.0release.
pnpm add @agent-assembly/sdk@rc # latest rc (tracks @rc dist-tag)
pnpm add @agent-assembly/sdk@0.0.1-rc.6 # pin exact — replace the version with the
# desired release from npmjs.com/package/%40agent-assembly%2FsdkBefore installing or contributing, ensure your environment has:
- Node.js ≥ 18.18.0 (LTS). The active LTS lines (18, 20, 22, 24) are exercised in CI.
- pnpm ≥ 10. The repository enforces pnpm via
enginesand ships apnpm-lock.yaml. - Rust toolchain (only required when rebuilding the native
aa-ffi-nodebinding from source — most consumers receive a prebuilt platform binary viaoptionalDependencies).
pnpm add @agent-assembly/sdk
# or
npm install @agent-assembly/sdk
# or
yarn add @agent-assembly/sdk
# or
bun add @agent-assembly/sdkThe SDK ships dual ESM/CJS entries and selects a prebuilt native binding for your platform
during postinstall. No additional build step is required for typical consumers.
Pass your LangChain-style tools ({ name, invoke }) to initAssembly under
langchain.tools, along with the gatewayClient that decides them. Tools are
wrapped in place: the wrapper asks that client for a decision before running the
tool body, so a call the client denies is denied before execution — invoke()
throws PolicyViolationError and the body does not run.
The gatewayClient is what makes that decision real. Wrap tools without one and
initAssembly throws a ConfigurationError under its default fail-closed posture,
rather than route tool checks through the allow-all no-op client it would otherwise
use. That refusal is a startup configuration check — it is not a policy decision about
any tool, and nothing is inspected when it fires.
The snippets below take the LangChain adapter path, which needs @langchain/core.
It is an optional peer dependency, so pnpm add @agent-assembly/sdk does not
install it — add it alongside the SDK for this quickstart:
pnpm add @agent-assembly/sdk @langchain/core
# or
npm install @agent-assembly/sdk @langchain/coreimport { initAssembly, type GatewayClient } from "@agent-assembly/sdk";
// The wrapper calls this before it runs a wrapped tool body. This one is a
// local allow-list so the snippet runs offline; point `check` at a gateway
// you run to source decisions from there instead.
const policyClient: GatewayClient = {
mode: "sdk-only",
start: async () => undefined,
close: async () => undefined,
check: async (request) =>
request.toolName === "search_web"
? { denied: false }
: { denied: true, reason: "not on the allow-list" },
waitForApproval: async () => ({ denied: false }),
record: async () => undefined,
recordResult: async () => undefined,
scanPrompts: async () => undefined
};
const searchWeb = {
name: "search_web",
invoke: async (input: { q: string }) => `results for ${input.q}`
};
const ctx = await initAssembly({
gatewayUrl: "http://localhost:7391",
agentId: "demo",
gatewayClient: policyClient,
langchain: { tools: { searchWeb } }
});
console.log(await searchWeb.invoke({ q: "agent assembly" }));
await ctx.shutdown();const { initAssembly } = require("@agent-assembly/sdk");
// The wrapper calls this before it runs a wrapped tool body. This one is a
// local allow-list so the snippet runs offline; point `check` at a gateway
// you run to source decisions from there instead.
const policyClient = {
mode: "sdk-only",
start: async () => undefined,
close: async () => undefined,
check: async (request) =>
request.toolName === "search_web"
? { denied: false }
: { denied: true, reason: "not on the allow-list" },
waitForApproval: async () => ({ denied: false }),
record: async () => undefined,
recordResult: async () => undefined,
scanPrompts: async () => undefined
};
const searchWeb = {
name: "search_web",
invoke: async (input) => `results for ${input.q}`
};
// CommonJS has no top-level await, so the init/shutdown sequence runs
// inside an async function.
async function main() {
const ctx = await initAssembly({
gatewayUrl: "http://localhost:7391",
agentId: "demo",
gatewayClient: policyClient,
langchain: { tools: { searchWeb } }
});
console.log(await searchWeb.invoke({ q: "agent assembly" }));
await ctx.shutdown();
}
main();Both entrypoints resolve to the same governance pipeline; the package's exports field
selects ESM or CJS automatically based on how the consumer imports it.
initAssembly() registers the LangChain callback handler and auto-wraps the configured
tools, so a wrapped invoke() reaches your gatewayClient for a decision before the
tool body runs. What that decision is worth is whatever the client backs it with: the
allow-list above answers locally, while a client that consults a gateway you run carries
that gateway's verdict. For more frameworks and the lower-level withAssembly()
wrapper, see the Examples guide on the
documentation site.
The SDK is tested against every active Node.js LTS line on every supported operating
system. The matrix is enforced by .github/workflows/test-matrix.yml:
| Node.js | Linux (ubuntu-latest) | macOS (macos-latest) | Windows (windows-latest) |
|---|---|---|---|
| 18 | ✅ | ✅ | ✅ |
| 20 | ✅ | ✅ | ✅ |
| 22 | ✅ | ✅ | ✅ |
| 24 | ✅ | ✅ | ✅ |
Older Node.js lines (≤ 16) are unsupported because the napi-rs ABI used by the native binding requires Node 18.18 or newer.
The Windows column above reflects the JavaScript/TypeScript test suite only. The native governance binding is not built for Windows, so a Windows agent runs unregistered — see Platform support.
initAssembly() auto-detects and installs governance hooks for five optional framework
integrations (LangChain.js, LangGraph.js, Vercel AI SDK, Mastra, OpenAI Agents). The full table —
each framework's optional peer dependency, supported version range, and current status
(including the known Vercel AI SDK caveat) —
is the authoritative reference and lives on the docs site:
Framework compatibility.
For the product-wide, cross-SDK index/hub that links every language SDK's matrix, see the
core documentation:
Framework compatibility index
(the /stable/ link goes live at GA).
The SDK is a thin TypeScript wrapper around the Agent Assembly Rust runtime. It reaches the runtime over one of two transports:
- a gRPC sidecar client that talks to a separate gateway process, or
- a native in-process binding built with napi-rs.
initAssembly() is the primary entrypoint. It resolves the gateway, registers the agent,
and installs governance hooks for whichever supported framework it detects. What a hook
can do to a call depends on the gateway client deciding it — see
How LangChain tools are blocked for which layer
blocks and which only observes.
| Export | Purpose |
|---|---|
initAssembly(config) |
Set up governance and auto-wire detected frameworks. The main entrypoint. |
withAssembly(tools, options) |
Lower-level wrapper to govern a tool map when you manage the gateway client yourself. |
createNoopGatewayClient(mode) |
Build an allow-all GatewayClient for offline demos and tests, or as a base to wrap. |
PolicyViolationError |
Thrown by a governed tool when the gateway client denies the call. |
currentAgentId(), runWithAgentId() |
Read and set the active agent id in the async-context lineage store. |
encodeAuditEvent() / decodeAuditEvent() (and the call-stack codecs) |
Encode and decode audit events to and from their wire shape. |
findAasmBinary(), INSTALL_HINT |
Locate the bundled aasm runtime binary and the install hint shown when it is missing. |
ENFORCEMENT_MODES |
The allowed enforcementMode values. |
Type-only exports (AssemblyConfig, AssemblyContext, AssemblyMode, EnforcementMode,
ToolMap, GatewayClient, the Gateway* governance types, and friends) are documented in
the API reference.
withAssembly(tools, options) needs a GatewayClient. For offline demos, tests, or custom
in-process policies you can build one yourself — no running gateway required:
import { createNoopGatewayClient, withAssembly, type GatewayClient } from "@agent-assembly/sdk";
// Allow-all client — handy for offline smoke tests:
withAssembly(
{ search: { description: "Search", execute: async (q: string) => `result:${q}` } },
{ gatewayClient: createNoopGatewayClient("sdk-only") }
);
// Or enforce your own in-process policy by implementing the GatewayClient interface:
const policyClient: GatewayClient = {
mode: "sdk-only",
start: async () => undefined,
close: async () => undefined,
check: async (request) =>
request.toolName === "delete_file"
? { denied: true, reason: "blocked by local policy" }
: { denied: false },
waitForApproval: async () => ({ denied: false }),
record: async () => undefined,
recordResult: async () => undefined,
scanPrompts: async () => undefined
};
// A governed tool throws `PolicyViolationError` when `check` denies it.LangChain's handleToolStart callback cannot stop a tool from running by its return
value, and @langchain/core discards a callback handler's handleToolEnd return
value too — so a callback can never substitute or redact a tool's output. The SDK
governs LangChain tools with two cooperating layers instead:
- Callback layer (
AssemblyCallbackHandler) — emits denials and results as audit events (including apolicy_post_blockevent if a tool ran despite an earlier deny signal), but cannot block or redact output. Where those events go depends on the gateway client: the native one hands them to the runtime's event channel, and the no-op one the default path resolves holds no channel and drops them. The handoff is unacknowledged, so neither case is an assurance the event was retained; AAASM-5783 is open on the downstream half, and until it lands no SDK can claim ADR 0033 §6 Observed. ReadauditSinkon the assembly context to tell which you have (AAASM-5750). - Wrapper layer (
wrapToolWithAssembly) — the actual enforcement point: performs the real pre-execution allow / deny / pending check and throwsPolicyViolationErrorbefore the tool runs.
initAssembly() registers the callback handler and wraps your configured LangChain tools
for you, so you do not wire either layer by hand. If you attach AssemblyCallbackHandler
without wrapping the tool (e.g. a tool from a library you don't construct yourself),
denies are still emitted as audit events but not enforced — and on the default
path those events are dropped rather than sent, so there is nothing to read back
(see the callback-layer bullet above). Wrap the tool with wrapToolWithAssembly
(or pass it through initAssembly's langchain.tools) to actually block it.
Most frameworks expose a tool name that policies match on. Vercel AI SDK tools do
not — they have no .name field — so for that framework, write policies that match on
the tool's description content (or, in withAssembly, the tool-map key) instead of a
framework-level tool name.
src/
index.ts # public entrypoint — re-exports the supported surface
core/ # init-assembly flow + gateway/API-key resolution
adapters/ # Adapter interface + AdapterRegistry, LangChain adapter
hooks/ # auto-detection patches (Vercel AI, OpenAI Agents, LangGraph, Mastra)
gateway/ # gateway client (gRPC sidecar transport)
native/ # loader for the napi-rs in-process binding
wrappers/ # low-level withAssembly() tool wrapper
lineage/ # async-context store for parent/child agent lineage
audit/ # audit-event wire encode/decode helpers
errors/ # ConfigurationError, GatewayError, PolicyViolationError, …
types/ # AssemblyConfig, AssemblyContext, AssemblyMode, EnforcementMode, …
proto/generated/ # generated protobuf types
native/aa-ffi-node/ # the Rust napi-rs crate (built into a per-platform .node binary)
tests/ # unit + architecture tests
For how these layers fit together, see the Architecture guide.
Most consumers never build the native binding — a prebuilt binary is installed for their
platform. You only need this when working on the aa-ffi-node Rust crate (at
native/aa-ffi-node) or running on a platform with no prebuild.
Build commands:
pnpm native:build— debug build, for local development.pnpm native:build:release— release build that produces the per-platform artifact.pnpm native:check-types— strict type-check of the generated napiindex.d.ts.
Run the native integration acceptance test (skipped unless the binding is built):
AA_NATIVE_TEST=1 pnpm vitest run tests/native-napi-integration.test.ts
The build-addon GitHub workflow compiles the native addon (Node 20 and 22): an
ubuntu-only debug build on pull requests, and ubuntu + macOS builds on main and release
tags. The addon embeds a Unix-domain-socket transport and does not build on Windows —
see Platform support below.
Windows is not supported yet. The governance core (aa-sdk-client and its
Unix-domain-socket transport) is Unix-only, so no native .node binding is built for
Windows. The SDK still installs and its JavaScript API is fully usable on Windows, but it
cannot register the agent with the governance gateway — the agent runs unregistered /
ungoverned, does not appear in the dashboard or GET /api/v1/agents, and both install and
initAssembly() say so explicitly (postinstall prints an informational notice; initAssembly
writes a prominent stderr warning and sets registered: false on the returned context). Use
Linux or macOS for a governed agent until Windows support lands.
Supported today: Linux (x64) and macOS (arm64, x64).
The package publishes dual module outputs behind conditional exports:
- ESM entry:
./dist/esm/index.js - CJS entry:
./dist/cjs/index.js - Type declarations:
./dist/types/index.d.ts
The four @agent-assembly/runtime-* packages (runtime-linux-x64, runtime-linux-arm64,
runtime-darwin-x64, runtime-darwin-arm64) are declared as optionalDependencies and
constrained by os/cpu, so only the one matching your platform is installed. Each
carries the aasm runtime binary; there is no Windows runtime package. The napi-rs .node
addon is loaded at runtime by native/aa-ffi-node/index.cjs.
CI verifies the published shape with:
- ESM and CJS entry smoke tests,
- an
exportstypesmapping assertion, and npm packcontent and package-size guard tests.
Full guides, architecture deep-dives, and the complete API reference are published at:
https://docs.agent-assembly.com/node-sdk/
The site is built from the docs/ (content) and website/ (Docusaurus app) directories
and is re-published on every push to main via the publish-docs.yml workflow.
@agent-assembly/sdk is one client of the Agent Assembly platform. The authoritative
governance decisions are made by the core Rust runtime; the protocol it speaks is shared
across all SDKs.
| Project | What it is |
|---|---|
| agent-assembly | Core Rust runtime — gateway, policy engine, proxy, eBPF, CLI (aasm). The protocol specification lives here. |
| Documentation site | Canonical, cross-repo documentation for the whole platform. |
| python-sdk | Sibling SDK for Python. |
| go-sdk | Sibling SDK for Go. |
| examples | Runnable examples — learn by running small, framework-specific Node.js/TypeScript (and Python/Go) samples for policy enforcement, approvals, audit, trace, and runtime workflows. |
| Release notes | Per-version changelog for this package. |
| Organization profile | Index of every Agent Assembly repository and its status. |
- Questions and bug reports — open an issue on the
node-sdk issue tracker. Include
your Node.js version, OS/arch, and the SDK version (
pnpm why @agent-assembly/sdk). - Security vulnerabilities — please do not file a public issue. Report privately via the repository's security advisories page so a fix can be coordinated before disclosure.
- Canonical package names + verifying your install — see SECURITY.md
for the authoritative
@agent-assembly/*package list (to spot typosquats) and how to verify npm provenance (npm audit signatures) and the per-release CycloneDX SBOM. - Contributing — see CONTRIBUTING.md for environment setup, the adapter-authoring guide, and the test/commit conventions.