Quick Start
This page takes you from nothing to a governed agent in a few minutes. Everything here is copy-paste; the snippets mirror the patterns the SDK's own test suite exercises.
1. Install
- pnpm
- npm
- yarn
- bun
pnpm add @agent-assembly/sdk
npm install @agent-assembly/sdk
yarn add @agent-assembly/sdk
bun add @agent-assembly/sdk
The package ships dual ESM/CJS entries and selects a prebuilt native binding for your
platform during postinstall, so there is no extra build step for typical consumers.
The public surface (initAssembly, withAssembly) is stabilizing. It may change
between pre-releases. Pin an exact version for reproducible installs:
npm install @agent-assembly/sdk@0.0.1-rc.6
2. Make sure a gateway is reachable
The SDK enforces policy by talking to an Agent Assembly gateway. You have two options:
- Let the SDK auto-start a local gateway. If you have the
aasmbinary on yourPATH(brew install ai-agent-assembly/tap/aasm, orcurl -fsSL https://agent-assembly.com/install.sh | sh) and setAA_AUTO_START=1, a zero-configinitAssembly()will probehttp://localhost:7391and start a local gateway for you if nothing is running. Auto-start is opt-in — without it, a missing gateway throws aConfigurationErrorinstead of spawning anything. - Point at a gateway you already run. Set
AA_GATEWAY_URL(andAA_API_KEYif it requires auth), or passgatewayUrlexplicitly.
:7391 REST + :50051 gRPCStarting the local gateway binds two loopback surfaces in one process:
aasm start --mode local
This exposes the REST API on http://localhost:7391 (what gatewayUrl points to, and
what the SDK probes and, with AA_AUTO_START=1, auto-starts) and the gRPC
AgentLifecycleService on 127.0.0.1:50051, which is the endpoint the native
aa-sdk-client binding dials to register your agent. You don't configure :50051
yourself — registration dials it automatically — so a no-argument initAssembly() both
connects and shows the agent in the dashboard once a gateway is reachable.
To confirm both surfaces are actually up rather than guessing from the SDK's behavior, check them directly:
curl http://localhost:7391/healthz # REST — real JSON: mode, storage, version, uptime_secs
nc -z localhost 50051 && echo "gRPC port open" # gRPC has no health endpoint yet; this only confirms the port accepts connections
See Configuration for the full resolution order.
3. Govern your first agent with withAssembly
withAssembly wraps a map of tools so the local policy is enforced before each one
runs: an allowed call executes normally, while a denied call throws a
PolicyViolationError and the tool body never runs. Pick your framework below —
each tab is the governance-wiring excerpt from that framework's runnable
example, vendored
into this repo and kept in lock-step with this page by a CI drift check — the check
catches this page drifting from the vendored snippet, not the vendored snippet
drifting from the upstream example. Copy the full, runnable script — imports,
tools, and the agent run — from the linked example; the slice below is just the
part that wires in governance.
LangChain.js is the validated path; the remaining frameworks are experimental.
The excerpts are ESM / TypeScript; under CommonJS, swap the import for
const { withAssembly } = require("@agent-assembly/sdk").
- LangChain.js
- Custom (no framework)
- OpenAI (Node) (Experimental)
- Vercel AI SDK (Experimental)
- LangGraph.js (Experimental)
- Mastra (Experimental)
import { withAssembly } from "@agent-assembly/sdk";
import { createPolicyGatewayClient } from "./policy.js";
import { TOOLS } from "./tools.js";
const tools = withAssembly(
{
get_weather: {
execute: async (args: Record<string, unknown>) => TOOLS.get_weather(args).output
},
delete_file: {
execute: async (args: Record<string, unknown>) => TOOLS.delete_file(args).output
}
},
{ gatewayClient: createPolicyGatewayClient(), agentId: "langchain-js-example-agent" }
);
Base tool abstractions like Tool moved out of the langchain monolith into @langchain/core when LangChain split the package in v0.1.0 (Jan 2024; @langchain/core first published to npm 2023-11-22).
langchain< 0.1.0:import { Tool } from "langchain/tools";langchain/@langchain/core≥ 0.1.0 (current):import { tool } from "@langchain/core/tools";
import { withAssembly } from "@agent-assembly/sdk";
import { createPolicyGatewayClient } from "./policy.js";
import { readFile, writeFile } from "./tools.js";
const tools = withAssembly(
{
read_file: {
execute: async (args: Record<string, unknown>) =>
readFile(typeof args.path === "string" ? args.path : "").output
},
write_file: {
execute: async (args: Record<string, unknown>) =>
writeFile(
typeof args.path === "string" ? args.path : "",
typeof args.content === "string" ? args.content : ""
).output
}
},
{ gatewayClient: createPolicyGatewayClient(), agentId: "custom-tool-policy-agent" }
);
import { withAssembly } from "@agent-assembly/sdk";
import { createPolicyGatewayClient } from "./policy.js";
import { searchWeb, sendEmail } from "./tools.js";
const tools = withAssembly(
{
search_web: {
execute: async (args: Record<string, unknown>) =>
searchWeb(typeof args.query === "string" ? args.query : "").output
},
send_email: {
execute: async (args: Record<string, unknown>) =>
sendEmail(
typeof args.to === "string" ? args.to : "",
typeof args.subject === "string" ? args.subject : ""
).output
}
},
{ gatewayClient: createPolicyGatewayClient(), agentId: "openai-node-example-agent" }
);
import { withAssembly } from "@agent-assembly/sdk";
import { createPolicyGatewayClient } from "./policy.js";
import { getWeatherTool, sendEmailTool } from "./tools.js";
// withAssembly wraps each tool's `execute`, keying the policy by the map key.
// The Vercel AI SDK tools run unchanged; only governance is layered on top.
const tools = withAssembly(
{
get_weather: getWeatherTool,
send_email: sendEmailTool,
},
{ gatewayClient: createPolicyGatewayClient(), agentId: "vercel-ai-example-agent" }
);
The React UI hooks were extracted out of the core ai package into a dedicated package in AI SDK 5.0, which removed the deprecated ai/react export. The tool() factory used above is unaffected — it still imports from "ai" unchanged through the 7.x line this example pins.
- AI SDK 4.x:
import { useChat } from "ai/react"; - AI SDK ≥ 5.0 (current):
import { useChat } from "@ai-sdk/react";
import { withAssembly } from "@agent-assembly/sdk";
import { createPolicyGatewayClient } from "./policy.js";
import { TOOLS } from "./tools.js";
/**
* Build governed tools once, then call them from inside graph nodes.
* withAssembly enforces the local policy before each tool runs.
*/
function buildGovernedTools() {
return withAssembly(
{
search_docs: {
execute: async (args: Record<string, unknown>) => TOOLS.search_docs(args).output,
},
execute_shell: {
execute: async (args: Record<string, unknown>) => TOOLS.execute_shell(args).output,
},
},
{ gatewayClient: createPolicyGatewayClient(), agentId: "langgraph-js-example-agent" }
);
}
import { withAssembly } from "@agent-assembly/sdk";
import { createPolicyGatewayClient } from "./policy.js";
import { getStockPriceTool, placeTradeTool } from "./tools.js";
// Wrap the Mastra tools with withAssembly. Each governed entry delegates to the
// real Mastra tool's execute, so the policy is enforced before the tool runs.
const tools = withAssembly(
{
get_stock_price: {
execute: async (args: Record<string, unknown>) => runMastraTool(getStockPriceTool, args),
},
place_trade: {
execute: async (args: Record<string, unknown>) => runMastraTool(placeTradeTool, args),
},
},
{ gatewayClient: createPolicyGatewayClient(), agentId: "mastra-example-agent" }
);
Mastra v1 moved every export except Mastra itself off the @mastra/core root entry point onto subpaths (see the npx @mastra/codemod@latest v1/mastra-core-imports codemod). This example's @mastra/core pin (^1.50.1) already uses the new layout.
@mastra/core0.x:import { Agent, Workflow, createTool } from "@mastra/core";@mastra/core≥ 1.0 (current):import { createTool } from "@mastra/core/tools";(similarlyAgentfrom@mastra/core/agent,Workflowfrom@mastra/core/workflows)
4. What to expect
- Allow. The tool runs normally and returns its result. A governance event is
emitted, and where it goes depends on the gateway client: the native one hands it
to the runtime's event channel, while the no-op one has no channel and drops it.
Neither is an assurance the event was retained — the handoff is unacknowledged, so
the SDK cannot report arrival. Downstream,
AAASM-5783 is open on
report_eventpayloads reaching neither the live stream nor the durable entry, so no SDK can claim ADR 0033 §6 Observed until it lands.initAssemblywarns when the event is dropped and reportsauditSinkon the returned context (AAASM-5750). - Deny. The wrapped
invoke()rejects with aPolicyViolationErrorwhose message includes the tool name and the gateway's reason — the tool body never runs. - Pending (needs approval). The call waits up to
langchain.approvalTimeoutMs(default applies if unset) for a human decision, then either proceeds or rejects.
That deny-on-policy behavior is the whole point: a denied tool call throws instead of executing. If you want to watch what would be blocked without actually blocking it while you tune policy, register the agent in observe mode:
const ctx = await initAssembly({
agentId: "demo",
enforcementMode: "observe", // dry-run: actions proceed, violations recorded as shadow events
langchain: { tools: { searchWeb } }
});
Next steps
- Guides — the full LangChain walkthrough, the low-level
withAssemblywrapper, experimental frameworks, and how to handle allow/deny decisions and errors. - Configuration — every
AssemblyConfigfield and the gateway/API-key resolution precedence. - Core Concepts — what the native binding, the
adapter registry, and the
initAssemblylifecycle actually do.