@agntz/core
v0.5.1
Published
TypeScript SDK for defining and running AI agents with first-class MCP support and pluggable storage
Downloads
1,429
Readme
@agntz/core
TypeScript SDK for defining and running AI agents. Agents are portable, JSON-serializable configurations — not code. Plug in any storage backend, any model provider, any tools.
This is the core package of the agntz monorepo.
Install
npm install @agntz/core @agntz/stores
# or
pnpm add @agntz/core @agntz/stores
# or
yarn add @agntz/core @agntz/storesThen install at least one model provider (all optional peer dependencies):
npm install @ai-sdk/openai # for OpenAI models
npm install @ai-sdk/anthropic # for Anthropic models
npm install @ai-sdk/google # for Google modelsSet your API key:
export OPENAI_API_KEY=sk-...
# or ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, etc.Quick Start
import { createRunner, defineAgent } from "@agntz/core";
import { MemoryStore } from "@agntz/stores/memory";
const runner = createRunner({ store: new MemoryStore() });
runner.registerAgent(defineAgent({
id: "greeter",
name: "Greeter",
systemPrompt: "You are a friendly greeter. Keep responses under 2 sentences.",
model: { provider: "openai", name: "gpt-5.6-terra" },
}));
const result = await runner.invoke("greeter", "Hello!");
console.log(result.output);
// → "Hey there! Welcome — great to have you here."Manifest and hosted-operation contract
@agntz/core/manifest parses and validates the portable YAML contract used by
the embedded SDK and hosted/self-hosted workers. The published schema is
available from @agntz/core/schema and at
https://agntz.co/schemas/agent-manifest.schema.json.
Manifest inputSchema, outputSchema, and callback-tool inputSchema fields
accept canonical object-root JSON Schema Draft 2020-12, including nested
objects, arrays, unions, local $ref, enums, and numeric/string constraints.
The older flat property-map syntax remains readable for compatibility.
The manifest union contains six kinds: llm, transcription, image, tool,
sequential, and parallel. @agntz/core supplies the parser and execution
boundary for hosted model operations; a worker host supplies transcription and
image adapters plus managed artifact storage.
Model declarations share portable controls such as maxTokens, topP,
topK, penalties, stop sequences, seed, and retries. Put non-portable controls
under a provider namespace:
model:
provider: mistral
name: mistral-small-2603
temperature: 0.2
maxTokens: 4096
providerOptions:
mistral:
safePrompt: trueHosted applications normally consume this surface through @agntz/client.
See the provider-replacement guide
for rich content, normalized results, artifacts, retention, and signed callback
tools.
Usage
Defining Agents
Agents are plain data objects — JSON-serializable, portable, and versionable:
import { defineAgent } from "@agntz/core";
const agent = defineAgent({
id: "writer",
name: "Writer",
description: "Writes concise, engaging copy",
version: "1.0.0",
systemPrompt: "You write concise, engaging copy.",
model: { provider: "openai", name: "gpt-5.6-sol" },
tags: ["content", "writing"],
});Tools
Define typed tools with Zod schemas and register them with the runner:
import { createRunner, defineAgent, defineTool } from "@agntz/core";
import { z } from "zod";
const lookupOrder = defineTool({
name: "lookup_order",
description: "Look up an order by ID",
input: z.object({ orderId: z.string() }),
async execute(input) {
return { status: "shipped", eta: "Tomorrow" };
},
});
const runner = createRunner({ tools: [lookupOrder] });
runner.registerAgent(defineAgent({
id: "support",
name: "Support Agent",
systemPrompt: "Help customers with their orders. Use tools to look up order info.",
model: { provider: "openai", name: "gpt-5.6-sol" },
tools: [{ type: "inline", name: "lookup_order" }],
}));
const result = await runner.invoke("support", "Where's my order #12345?");
console.log(result.toolCalls);
// → [{ name: "lookup_order", input: { orderId: "12345" }, output: { status: "shipped", ... } }]Sessions (Conversational Memory)
// First message
await runner.invoke("support", "Hi, I need help", { sessionId: "sess_abc" });
// Second message — agent remembers the conversation
await runner.invoke("support", "My order is #12345", { sessionId: "sess_abc" });Streaming
const stream = runner.stream("writer", "Write a short story about a robot");
for await (const event of stream) {
if (event.type === "text-delta") {
process.stdout.write(event.text);
} else if (event.type === "tool-call-start") {
console.log(`\nCalling tool: ${event.toolCall.name}`);
} else if (event.type === "done") {
console.log(`\nTokens used: ${event.result.usage.totalTokens}`);
}
}Stream events:
| Event | Description |
|---|---|
| text-delta | Incremental text chunk from the model |
| tool-call-start | Tool execution is starting |
| tool-call-end | Tool execution completed (with result) |
| step-complete | One iteration of the tool loop finished |
| done | Final result with full InvokeResult |
Agent Chains (Agent-as-Tool)
Agents can invoke other agents as tools:
runner.registerAgent(defineAgent({
id: "researcher",
name: "Researcher",
systemPrompt: "Research topics and return concise findings.",
model: { provider: "openai", name: "gpt-5.6-sol" },
}));
runner.registerAgent(defineAgent({
id: "writer",
name: "Writer",
systemPrompt: "Write articles. Delegate research to the researcher.",
model: { provider: "anthropic", name: "claude-sonnet-5" },
tools: [{ type: "agent", agentId: "researcher" }],
}));
// Writer invokes researcher as a tool during execution
const result = await runner.invoke("writer", "Write about MCP");Shared Context
Context lets agents share state without tight coupling:
Note: this section uses legacy
contextIdsscratchpad buckets. Runtime resource access usescontextnamespace grants instead.
// Researcher writes findings to context
await runner.invoke("researcher", "Find info about MCP", {
contextIds: ["project-alpha"],
});
// Writer reads the same context
await runner.invoke("writer", "Write an article using the research", {
contextIds: ["project-alpha"],
});Runtime Tool Context
Pass runtime data to tools without going through the LLM:
const updateProfile = defineTool({
name: "update_profile",
description: "Update the user's profile",
input: z.object({ field: z.string(), value: z.string() }),
async execute(input, ctx) {
// ctx.user comes from toolContext — injected at runtime
await db.users.update(ctx.user.id, { [input.field]: input.value });
return { success: true };
},
});
await runner.invoke("chat", message, {
toolContext: { user: { id: "u_123", name: "Aaron" } },
});Structured Output
runner.registerAgent(defineAgent({
id: "analyzer",
name: "Sentiment Analyzer",
systemPrompt: "Analyze the sentiment of input text.",
model: { provider: "openai", name: "gpt-5.6-sol" },
outputSchema: {
type: "object",
properties: {
sentiment: { type: "string", enum: ["positive", "negative", "neutral"] },
confidence: { type: "number" },
},
required: ["sentiment", "confidence"],
},
}));
const { output } = await runner.invoke("analyzer", "I love this!");
const parsed = JSON.parse(output);
// → { sentiment: "positive", confidence: 0.95 }MCP Integration
Use tools from any MCP-compatible server:
const runner = createRunner({
mcp: {
servers: {
github: { url: "http://localhost:3001/mcp" },
filesystem: { command: "npx", args: ["-y", "@anthropic/mcp-fs"] },
},
},
});
runner.registerAgent(defineAgent({
id: "code-reviewer",
name: "Code Reviewer",
systemPrompt: "Review code from GitHub PRs...",
model: { provider: "anthropic", name: "claude-sonnet-5" },
tools: [{ type: "mcp", server: "github", tools: ["get_file_contents"] }],
}));Expose your agents as an MCP server:
import { createMCPServer } from "@agntz/core";
const server = createMCPServer(runner);Evals
Evals are first-class records outside AgentDefinition: datasets, eval
definitions, async eval runs, latest scores, snapshots, and history. The
embedded SDK and hosted clients expose those records through their client
resources; the core runner stays focused on execution.
Storage
The default store is in-memory. For persistence, install a database adapter:
import { createRunner } from "@agntz/core";
import { SqliteStore } from "@agntz/stores/sqlite";
const runner = createRunner({
store: new SqliteStore("./data.db"),
});Database adapters:
| Package | Use Case |
|---|---|
| @agntz/stores | Persistent storage for embedded, single-server, and multi-server production |
You can also split stores by concern:
const runner = createRunner({
agentStore: myPostgresStore,
sessionStore: myRedisStore,
logStore: myElasticsearchStore,
});Custom Stores
Implement the store interfaces from @agntz/contracts:
interface AgentStore {
getAgent(id: string): Promise<AgentDefinition | null>;
listAgents(): Promise<AgentSummary[]>;
putAgent(agent: AgentDefinition): Promise<void>;
deleteAgent(id: string): Promise<void>;
}
interface SessionStore {
getMessages(sessionId: string): Promise<Message[]>;
append(sessionId: string, messages: Message[]): Promise<void>;
deleteSession(sessionId: string): Promise<void>;
listSessions(agentId?: string): Promise<SessionSummary[]>;
}
// Also: RunStore, TraceStore, EvalStore, SecretStore, ProviderStore,
// ConnectionStore, ContextStore, LogStore, and resource provider ports.
// Or implement UnifiedStore for all-in-one runtime persistence.API Reference
createRunner(config?: RunnerConfig): Runner
Creates the central orchestrator. All options are optional:
const runner = createRunner({
store: myStore, // Storage backend
tools: [myTool1, myTool2], // Inline tools
mcp: { servers: { ... } }, // MCP server config
session: { // Session trimming
maxMessages: 50,
strategy: "sliding", // "sliding" | "summary" | "none"
},
context: { // Context injection
maxEntries: 20,
maxTokens: 4000,
strategy: "latest", // "latest" | "summary" | "all"
},
defaults: { // Default model config
model: { provider: "openai", name: "gpt-5.6-terra" },
maxTokens: 4096,
},
retry: { // Retry with backoff
maxRetries: 3,
initialDelayMs: 1000,
backoffMultiplier: 2,
},
maxRecursionDepth: 3, // Agent-as-tool chain limit
telemetry: { ... }, // OpenTelemetry (opt-in)
});defineAgent(config): AgentDefinition
Creates a validated agent definition:
const agent = defineAgent({
id: "my-agent",
name: "My Agent",
systemPrompt: "...",
model: { provider: "openai", name: "gpt-5.6-sol" },
// ... all fields from AgentDefinition
});defineTool(config): ToolDefinition
Creates a typed tool with Zod input validation:
const tool = defineTool({
name: "my_tool",
description: "What this tool does",
input: z.object({ ... }),
async execute(input, ctx) { ... },
});Runner Methods
| Method | Description |
|---|---|
| runner.invoke(agentId, input, options?) | Invoke an agent and get the result |
| runner.stream(agentId, input, options?) | Stream an agent invocation |
| runner.registerAgent(agent) | Register an agent definition |
| runner.shutdown() | Clean up MCP connections and flush stores |
Key Types
| Type | Description |
|---|---|
| AgentDefinition | Full agent configuration object |
| ToolDefinition | Tool with name, description, schema, and execute function |
| ToolReference | Reference to a tool: inline, mcp, or agent |
| InvokeOptions | Options for invoke(): sessionId, context namespace grants, toolContext, etc. |
| InvokeResult | Result: output, toolCalls, usage, duration, model |
| InvokeStream | Async iterable of StreamEvent with .result promise |
| RunnerConfig | Full configuration for createRunner() |
| UnifiedStore | Combined runtime store contract re-exported from @agntz/contracts |
| ModelProvider | Interface for custom model providers |
Error Types
Most runtime errors extend AgntzError with a stable code field:
| Error | Code | Description |
|---|---|---|
| AgentNotFoundError | AGENT_NOT_FOUND | Agent ID doesn't exist |
| ToolNotFoundError | TOOL_NOT_FOUND | Tool name not registered |
| ToolExecutionError | TOOL_EXECUTION_ERROR | Tool threw during execution |
| ModelError | MODEL_ERROR | Model provider returned an error |
| ProviderNotFoundError | PROVIDER_NOT_FOUND | No provider SDK installed |
| InvocationCancelledError | INVOCATION_CANCELLED | AbortSignal triggered |
| MaxStepsExceededError | MAX_STEPS_EXCEEDED | Tool loop hit step limit |
| MaxRecursionDepthError | MAX_RECURSION_DEPTH | Agent chain too deep |
| RetryExhaustedError | RETRY_EXHAUSTED | All retries failed |
| ValidationError | VALIDATION_ERROR | Invalid input |
Templates
Starter agent configurations for common patterns:
import { templates } from "@agntz/core/templates";
import { defineAgent } from "@agntz/core";
runner.registerAgent(defineAgent({
...templates.chatbot,
id: "my-bot",
}));Available templates: chatbot, codeReviewer, summarizer, dataExtractor, creativeWriter, customerSupport, fitnessCoach, researcher
CLI
# Generate a YAML manifest
npx @agntz/sdk create "Answer support questions in a concise tone" -o ./agents/support.yaml
# Run it locally
npx @agntz/sdk run ./agents/support.yaml --input "Hello!"
# Validate a file or directory, including cross-file refs
npx @agntz/sdk validate ./agents
# Show all current commands
npx @agntz/sdk --helpThe current CLI is distributed through @agntz/sdk; the executable name is
agntz when installed globally. It supports create, edit, validate,
run, publish, hosted auth, runs, traces, and eval commands. Use the SDK
directly for local tools, resource providers, and app-specific runtime wiring.
Model Providers
agntz uses the Vercel AI SDK internally — calls go directly to providers with your API keys. No middleman.
defineAgent({
model: { provider: "openai", name: "gpt-5.6-sol" }, // OPENAI_API_KEY
model: { provider: "anthropic", name: "claude-sonnet-5" }, // ANTHROPIC_API_KEY
model: { provider: "google", name: "gemini-3.6-flash" }, // GOOGLE_GENERATIVE_AI_API_KEY
model: { provider: "openrouter", name: "anthropic/claude-sonnet-5" }, // OPENROUTER_API_KEY
});Supported providers: openai, anthropic, google, openrouter, mistral, xai, groq, deepseek, perplexity, cohere, azure.
OpenRouter is a meta-provider that proxies to hundreds of models (Anthropic, Google, Meta, DeepSeek, open-source) with a single API key — use provider: "openrouter" and reference any model by its OpenRouter slug (e.g. anthropic/claude-sonnet-5, deepseek/deepseek-v4-pro). Per-request cost is reported by OpenRouter and surfaced on TokenUsage.cost.
Or bring your own model provider:
const runner = createRunner({
modelProvider: myCustomProvider, // implements ModelProvider interface
});OpenTelemetry
Opt-in observability:
import { trace } from "@opentelemetry/api";
const runner = createRunner({
telemetry: {
tracer: trace.getTracer("my-app"),
recordIO: false,
baseAttributes: { "service.name": "my-app" },
},
});Span hierarchy: agent.invoke → agent.model.call / agent.tool.execute
Zero overhead when telemetry is not configured.
Related Packages
| Package | Description |
|---|---|
| @agntz/sdk | Embedded SDK, local client, and CLI |
| @agntz/client | Hosted/self-hosted HTTP client |
| @agntz/contracts | Shared types, store/resource ports, and leaf utilities |
| @agntz/db | Shared SQLite/Postgres connection and migration plumbing |
| @agntz/stores | Store contracts plus in-memory, SQLite, and Postgres adapters |
| @agntz/worker | Hono HTTP worker for executing agents |
| @agntz/app | Next.js web UI — multi-tenant, Clerk auth |
Contributing
See the main CONTRIBUTING.md for guidelines.
License
MIT © Aaron Bidworthy
