@nexalab/agent-sdk
v0.1.8
Published
Reusable agentic SDK extracted from Nexa Agent OS.
Readme
Nexa Agent SDK
@nexalab/agent-sdk is a composable TypeScript SDK for building agentic assistants, workers, CLIs, app backends, durable workflows, and multi-agent systems.
It is designed around a small happy path for quick integration, plus production hooks for providers, tools, approvals, storage, telemetry, memory, evals, replay, routing, policy, and evidence-backed runs.
Install
pnpm add @nexalab/agent-sdkFor local workspace use:
{
"dependencies": {
"@nexalab/agent-sdk": "workspace:*"
}
}Documentation
The full documentation site is built with VitePress and lives in docs/. The API reference under docs/api is generated automatically from the TypeScript sources with TypeDoc:
# Regenerate the API reference markdown from src/ into docs/api
pnpm docs:generate
# Develop the docs site (regenerates the API reference, then starts a dev server)
pnpm docs:dev
# Production build (regenerates the API reference, then builds the static site)
pnpm docs:build
# Preview the production build locally
pnpm docs:previewdocs:build requires a successful docs:generate and is also validated in CI for every push/PR. The generated docs/api directory is not committed — it is always produced from source.
Quick Start
Premium agent({...}) Kernel API
Five minutes from npm install to a running agent, with a single config object. model accepts a "provider:model" string resolved against the built-in provider presets (API key read from the matching env var, e.g. OPENAI_API_KEY):
import { agent } from "@nexalab/agent-sdk";
import { z } from "zod";
const assistant = await agent({
name: "sales-agent",
model: "openai:gpt-4o-mini",
instructions: "Eres un agente comercial. Ayudas al cliente a cotizar productos.",
memory: true
});
const result = await assistant.run("Cotizale 20 tanques a Abraham");
console.log(result.text);Grows into the full Kernel surface without changing shape — context engine, guardrails, hooks, sessions, structured outputs, and tools all compose onto the same object:
const result = await assistant.run("Analiza este prospecto", {
outputSchema: z.object({ score: z.number(), reason: z.string(), nextAction: z.string() })
});
result.output; // typed { score, reason, nextAction }
const session = assistant.session("customer-38273");
await session.run("Hola");
await session.run("Recuerdas mi pedido?");
const asTool = assistant.asTool(); // callable from another agent's native tool loopSee Public Modules below (context, sessions, guardrails, hooks, structured-output, kernel) for the building blocks this composes.
Builder API
The lower-level fluent builder remains fully supported for cases that need explicit control over provider wiring, storage, and tracing:
import {
createAgent,
defineSdkTool,
openaiProvider,
createInMemoryStorage,
createTelemetry
} from "@nexalab/agent-sdk";
import { z } from "zod";
const getTime = defineSdkTool({
id: "system.read_time",
capability: "system.time.read",
description: "Read the current system time.",
inputSchema: z.object({}),
handler: async () => ({
status: "ok",
output: { iso: new Date().toISOString() }
})
});
const builderAgent = await createAgent()
.useProvider(openaiProvider({
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-4o-mini"
}))
.useTool(getTime)
.useStorage(createInMemoryStorage())
.useTelemetry(createTelemetry())
.useConsoleTrace()
.build();
const result = await builderAgent.runMessage("puedes decirme que hora es?");
console.log(result.text);Core Capabilities
- Agentic task/run runtime
- Mission runtime
- Goal interpretation contracts
- Success criteria and evidence model
- Executable plan model
- Plan validation
- Completion gate
- Loop guard
- Normalized runtime events
- Permission-aware tool registry
- Zod-friendly tool factory
- Local tool executor
- Approval manager
- Human-in-the-loop approval primitives
- Multi-round transparent tool loop
- Native model-requested tool-call loop
- Async
runStream(...)event stream - In-memory event bus
- In-memory memory store
- Advanced memory with dedupe, recency boost, and contextual retrieval
- Vector memory with pluggable embeddings
- JSON file storage
- SQLite storage adapter contract
- Postgres storage adapter contract
- Redis storage adapter contract
- Pluggable storage interfaces
- Queue adapter and in-memory queue
- OpenAI-compatible provider registry
- Provider presets for OpenAI, OpenRouter, Ollama, and custom OpenAI-compatible APIs
- OpenAI-compatible
/v1/modelslookup - OpenAI-compatible chat completions and streaming
- Model routing and budget accounting
- Telemetry hooks for events, tool traces, and spans
- OpenTelemetry-compatible sink bridge
- Agent eval harness
- HTTP request handler
- Checkpointed workflow runtime
- Replay/debug timeline helpers
- Tool policy helpers for deny/approval rules
- Multi-agent coordinator and delegation helpers
- Premium
agent({...})Kernel config API on top of the builder/runtime - Context Engine (user/tenant/conversation/knowledge/dynamic sections, token budget, compression)
- Sessions/threads with history, compact, reset, and fork
- Structured outputs (real Zod -> JSON Schema conversion, typed
result.output) - Guardrails (declarative input/output checks, distinct from policy/approvals)
- Lifecycle hooks (beforeRun/afterRun/beforeModel/afterModel/beforeTool/afterTool/onError/...)
- Skills (instructions + tool subsets injected into an agent's context and native tool loop)
- Typed artifacts surfaced from tool evidence (
result.artifacts) - Multimodal message content (text/image/file parts) across OpenAI, Anthropic, and Gemini formats
- Native MCP client (stdio and streamable-HTTP transports, real JSON-RPC handshake and tool discovery)
- Agent-to-agent primitives:
agent.asTool(),agentAsTool(),handoffs, andcreateTeam()(supervisor/router/parallel strategies) - Knowledge/RAG (
createKnowledge, text/file/directory sources, chunking, retrieval over the existing vector memory store) - Provider capability registry (vision/tools/structuredOutputs/reasoning per provider) and adaptive model routing (
strategy: "adaptive") - Pluggable response cache (
createInMemoryCache) wired intoagent.run() - Execution guarantees: real per-step retries with backoff,
WAITING_FOR_APPROVAL/WAITING_FOR_RETRYstates, resumable runs that skip already-completed steps - Secrets (
secret()references resolved only inside tool execution, never placed into model-visible messages) - Tenancy:
agent.run(text, { tenant: { tenantId, userId } })namespaces memory/cache;config.permissionsfilters tools at agent construction - Node-process-level workspace sandbox (
createWorkspace,workspaceTools) — jailed fs root, timeout/output-cappedexec, snapshot/restore - YAML agent manifests (
loadAgentManifest) and anexaCLI (nexa run <manifest.yaml> "message",nexa inspect <manifest.yaml>) ModelHarness— a public, overridable contract for the agent's model/tool-calling strategy (basicHarness()ships as the OSS reference; third parties can implement their own)
Public Modules
import { createAgent, agent } from "@nexalab/agent-sdk";
import { openaiProvider, openRouterProvider, ollamaProvider, anthropicProvider, geminiProvider } from "@nexalab/agent-sdk/providers";
import { defineSdkTool, toOpenAIChatTool } from "@nexalab/agent-sdk/tools";
import { AgentRuntime } from "@nexalab/agent-sdk/runtime";
import { createInMemoryStorage } from "@nexalab/agent-sdk/storage";
import { createJsonFileStorage } from "@nexalab/agent-sdk/storage/file";
import { createSqliteStorage } from "@nexalab/agent-sdk/storage/sqlite";
import { createPostgresStorage } from "@nexalab/agent-sdk/storage/postgres";
import { createRedisStorage } from "@nexalab/agent-sdk/storage/redis";
import { createTelemetry } from "@nexalab/agent-sdk/telemetry";
import { createOpenTelemetrySink } from "@nexalab/agent-sdk/telemetry/otel";
import { runAgentEvals } from "@nexalab/agent-sdk/evals";
import { createAgentRequestHandler } from "@nexalab/agent-sdk/http";
import { runNativeToolLoop } from "@nexalab/agent-sdk/native-tool-loop";
import { createWorkflowRuntime } from "@nexalab/agent-sdk/workflow";
import { replayRun, replayEvents } from "@nexalab/agent-sdk/replay";
import { createToolPolicy } from "@nexalab/agent-sdk/policy";
import { createModelRouter } from "@nexalab/agent-sdk/model-router";
import { createMultiAgentCoordinator } from "@nexalab/agent-sdk/multi-agent";
import { createAdvancedMemoryStore } from "@nexalab/agent-sdk/advanced-memory";
import { createVectorMemoryStore } from "@nexalab/agent-sdk/vector-memory";
import { createInMemoryQueue } from "@nexalab/agent-sdk/queue";
import { runHarness } from "@nexalab/agent-sdk/harness";
import { agent, resolveModelString } from "@nexalab/agent-sdk/kernel";
import { createContextEngine } from "@nexalab/agent-sdk/context";
import { createSession, loadSession } from "@nexalab/agent-sdk/sessions";
import { defineGuardrail, runInputGuardrails, runOutputGuardrails } from "@nexalab/agent-sdk/guardrails";
import { createHookDispatcher } from "@nexalab/agent-sdk/hooks";
import { zodSchemaToJsonSchema, parseStructuredOutput } from "@nexalab/agent-sdk/structured-output";
import { defineFlow } from "@nexalab/agent-sdk/guided-flows";Guided Flows
Build stateful, multi-turn conversations that must satisfy explicit requirements before completion.
Guided Flows combine natural conversation with typed state, step-specific tools, validation, branching, confirmations, and structured final outputs. Attach one or more to agent({ flows }); the runtime fills state from free-form messages in any order, never re-asks for what it already has, scopes tool availability per step (never beyond what the agent itself is permitted), and only returns a Zod-validated structured output once every requirement holds.
import { agent, defineFlow } from "@nexalab/agent-sdk";
import { z } from "zod";
const gasOrderFlow = defineFlow({
id: "gas-order",
version: 1,
name: "Gas order",
goal: "Crear un pedido de gas valido y confirmado",
stateSchema: z.object({
customerName: z.string().optional(),
phone: z.string().optional(),
confirmed: z.boolean().default(false),
orderId: z.string().optional()
}),
initialState: { confirmed: false },
requirements: [
{ id: "customer", description: "Customer identity known", satisfied: ({ state }) => Boolean(state.customerName && state.phone) },
{ id: "confirmation", description: "Order confirmed", satisfied: ({ state }) => state.confirmed === true }
],
steps: [
{ id: "identify_customer", collect: ["customerName", "phone"] },
{ id: "confirmation", collect: ["confirmed"], exit: ({ state }) => state.confirmed === true, tools: { denied: ["order.create"] } },
{ id: "create_order", when: ({ state }) => state.confirmed === true, tools: { required: ["order.create"] }, exit: ({ state }) => Boolean(state.orderId) }
],
protectedFields: { orderId: ["tool"] },
tools: { bindings: { "order.create": { applyResult: ({ result }) => ({ set: { orderId: result.output.orderId }, source: "tool" } as const) } } },
completion: ({ requirements, state }) => requirements.every((r) => r.satisfied) && Boolean(state.orderId),
outputSchema: z.object({ type: z.literal("gas_order"), orderId: z.string() }),
mapOutput: ({ state }) => ({ type: "gas_order" as const, orderId: state.orderId! })
});
const salesAgent = await agent({ name: "gas-sales-agent", model: "openai:gpt-4o-mini", flows: [gasOrderFlow], tools: [orderCreateTool] });
const result = await salesAgent.session("whatsapp:5214771234567").run("Quiero pedir gas");
console.log(result.text, result.flow, result.state, result.output);See the full guide at docs/guide/guided-flows.md and a complete runnable conversation at examples/guided-gas-order (pnpm demo:guided-flow).
Agent Manifests + CLI
Agents can be declared as YAML and run without writing code for the identity/model/instructions surface:
# agent.yaml
name: nexa
model: openai:gpt-4o-mini
instructions: Eres un agente comercial. Ayudas al cliente a cotizar productos.
memory: truenexa run agent.yaml "Cotizale 20 tanques a Abraham"
nexa inspect agent.yamlTools/skills/guardrails/storage are code objects, not YAML-expressible — wire them in via loadAgentManifest(path, extend):
import { loadAgentManifest } from "@nexalab/agent-sdk/manifest";
const app = await loadAgentManifest("./agent.yaml", { tools: [myTool], guardrails: [myGuardrail] });Model Harness
ModelHarness is the pluggable strategy behind agent.run()'s tool-calling loop — how many rounds to run, when to stop, how to react to tool results:
import { agent } from "@nexalab/agent-sdk";
import { basicHarness, type ModelHarness } from "@nexalab/agent-sdk/model-harness";
const assistant = await agent({
model: "openai:gpt-4o-mini",
name: "assistant",
tools: [...],
harness: basicHarness() // default if omitted; wraps the built-in native tool loop unchanged
});Implement ModelHarness directly to swap in a custom orchestration strategy:
class MyHarness implements ModelHarness {
async run(ctx) {
// ctx.client / ctx.messages / ctx.tools — same shape basicHarness() receives
return { text: "...", messages: ctx.messages, toolCalls: [], toolResults: [], rounds: 1 };
}
}Supplying any explicit harness (including basicHarness()) switches the loop to this Promise-based contract, which does not emit the granular native-tool events agent.runStream() emits by default — that streaming behavior is unchanged when no harness is configured. See docs/architecture/open-core-target.md for the design rationale.
Not to be confused with src/harness.ts's runHarness/assertHarness — an unrelated CI/release-check runner (pnpm harness) used to smoke-test the package before publishing. Same word, two different concepts; see the architecture docs for why they aren't merged.
Architecture
The SDK is adapter-driven:
- Providers: bring OpenAI, OpenRouter, Ollama, Anthropic, Gemini, local gateways, or any OpenAI-compatible API.
- Tools: define metadata, capability, schema, and handler independently.
- Runtime: create runs, plans, observations, evidence, completion decisions, and events.
- Storage: persist runs, events, memory, approvals, and workflow checkpoints.
- Policy: allow, deny, or require approval based on tool id/capability/name.
- Telemetry: capture events, tool traces, spans, and bridge them to OpenTelemetry.
- Workflows: run checkpointed DAGs for durable background jobs.
- Evals: regression-test agent behavior with simple or custom judges.
- Replay: turn stored run/events into a timeline for debugging and audit.
- Multi-agent: delegate tasks to specialized workers by capability.
- Routing: select models under priority, tag, input-size, and budget constraints.
Native Tool Loop
When tools are registered, runMessage(...) can execute model-requested tool calls:
const result = await agent.runMessage("Read the time and answer in Spanish.");
console.log(result.nativeToolTurn?.toolCalls);
console.log(result.nativeToolTurn?.toolResults);For UI and worker integrations, use the structured event stream:
for await (const event of agent.runStream("do the task")) {
if (event.type === "status") console.log(event.label, event.detail);
if (event.type === "token") process.stdout.write(event.token);
if (event.type === "native-tool") console.log(event.event.type);
if (event.type === "final") console.log(event.result.text);
}Provider Compatibility
The SDK supports three provider formats:
openai: OpenAI-compatible chat-completions APIs such as OpenAI, OpenRouter, Groq, Ollama, LM Studio, and compatible gateways.anthropic: Anthropic Messages API, normalized into the SDK chat/tool-call result shape.gemini: GeminigenerateContent, normalized into the SDK chat/tool-call result shape.
Use presets:
const openai = openaiProvider({ apiKey, model: "gpt-4o-mini" });
const anthropic = anthropicProvider({ apiKey, model: "claude-3-5-sonnet-latest" });
const gemini = geminiProvider({ apiKey, model: "gemini-1.5-pro" });OpenAI-compatible providers support native SSE streaming. Anthropic and Gemini currently normalize non-streaming responses through the same complete(...) contract and expose a final stream chunk through stream(...).
Storage
In-memory storage is useful for tests and demos:
const storage = createInMemoryStorage();JSON file storage is useful for local durable workers:
const storage = createJsonFileStorage("./.nexa");SQLite is exposed as a driver adapter, so the SDK does not force a native dependency:
const storage = createSqliteStorage(db);Postgres and Redis are also exposed as driver adapters:
const postgresStorage = createPostgresStorage(pgClient);
await postgresStorage.migrate();
const redisStorage = createRedisStorage(redisClient, "my-app:nexa");Workflows
const workflow = createWorkflowRuntime({ storage });
const run = workflow.create({
name: "ship-report",
nodes: [
{ id: "collect", title: "Collect", run: () => ({ ok: true }) },
{ id: "verify", title: "Verify", dependsOn: ["collect"], run: () => ({ verified: true }) }
]
});
await workflow.run({ runId: run.id, nodes });Policy And Approvals
const policy = createToolPolicy([
...requireApprovalForCapabilities(["filesystem.write"], "LOCAL_WRITE"),
...denyCapabilities(["system.delete"])
]);
const risk = policy.approvalRiskFor(tool);Evals
const report = await runAgentEvals({
agent,
cases: [
{ id: "time-es", input: "que hora es?", expected: ["hora"] }
]
});Model Routing
const router = createModelRouter([
{ id: "fast", providerId: "openai", model: "gpt-4o-mini", priority: 10, tags: ["fast"] },
{ id: "deep", providerId: "openai", model: "gpt-4.1", priority: 5, tags: ["deep"] }
], {
maxCalls: 100,
maxEstimatedCost: 5
});
const decision = router.select({ prompt: "summarize this", requiredTags: ["fast"] });
router.record(decision);Multi-Agent
const coordinator = createMultiAgentCoordinator([
{
id: "builder",
description: "Builds SDK integrations",
capabilities: ["code", "examples"],
run: async ({ task }) => agent.runMessage(task)
}
]);
const delegated = await coordinator.delegate({
task: "build an integration example",
requiredCapabilities: ["examples"]
});Examples
The examples are documented in examples/README.md.
Run the full offline frontier v3 tour:
pnpm demo:v3That example walks through:
- JSON storage
- Telemetry
- Advanced memory
- Tool definitions
- Policy and approvals
- Checkpointed workflows
- Multi-agent coordination
- Model routing and budget accounting
- Evals
- HTTP adapter
- Replay/debug timeline
Other useful demos:
pnpm demo:message
pnpm demo:chain
pnpm demo:chat
pnpm demo:native-loop
pnpm demo:stream
pnpm demo:storage
pnpm demo:evals
pnpm demo:http
pnpm demo:workflow
pnpm demo:policy
pnpm demo:telemetryEnvironment
For real provider calls:
NEXA_BASE_URL=https://api.openai.com
NEXA_API_KEY=...
NEXA_MODEL=gpt-4o-mini
NEXA_TRACE=1PowerShell example:
$env:NEXA_BASE_URL="https://api.openai.com"
$env:NEXA_API_KEY="..."
$env:NEXA_MODEL="gpt-4o-mini"
pnpm demo:chatOpenRouter example:
$env:NEXA_BASE_URL="https://openrouter.ai/api"
$env:NEXA_API_KEY="..."
$env:NEXA_MODEL="openai/gpt-4o-mini"
pnpm demo:chatCurrent Status
This package currently targets Node.js >=22.19.0, TypeScript, ESM, and OpenAI-compatible chat APIs.
The package is designed to stay dependency-light. Optional production integrations such as SQLite drivers, OpenTelemetry SDKs, web frameworks, vector databases, and queues are expected to be provided by the host app through adapters.
Production Checks
pnpm typecheck
pnpm test
pnpm build
pnpm smoke
pnpm harness
pnpm pack:checkRecommended production wiring:
- Use
createPostgresStorage(...)orcreateRedisStorage(...)for app backends. - Use
createJsonFileStorage(...)for local workers and desktop apps. - Use
createOpenTelemetrySink(...)for traces. - Use
createToolPolicy(...)plusApprovalManagerfor risky tools. - Use
runStream(...)for UI or server-sent-event style integration. - Use
runAgentEvals(...)in CI for regression protection. - Use
createVectorMemoryStore(...)with your embedding provider for semantic memory. - Use
pnpm harnessas the production readiness gate. - Use
pnpm smokeandpnpm pack:checkbefore publishing.
## Frontier-ready architecture
Nexa Agent SDK is designed with a frontier-style agent architecture: native tool calling, structured streaming, durable storage adapters, policy and approvals, evals, telemetry, replay, workflows, routing, multi-agent coordination, vector memory, queue contracts, package smoke checks, and an extensible `ModelHarness` contract.
The SDK remains dependency-light. Production applications provide their own infrastructure drivers and deployment policies through adapters.
## Extensible by design
Nexa is built around stable contracts instead of hardcoded orchestration strategies.
Developers can extend or replace major runtime behaviors through interfaces for:
- model harnesses
- providers
- tools
- context
- memory
- storage
- routing
- telemetry
- evals
This makes it possible to build custom agent architectures without forking the core SDK.
## Release safety
The repository includes automated checks for package boundaries, accidental credential exposure, and npm tarball contents:
```bash
pnpm verify:boundaries
pnpm verify:secrets
pnpm verify:publish-safety