@cuylabs/agent-core
v0.4.0
Published
Embeddable AI agent infrastructure - streaming, sessions, resilience, capabilities
Maintainers
Readme
@cuylabs/agent-core
Embeddable AI agent infrastructure using Vercel AI SDK. Core building blocks for streaming AI agents with session management, resilience, and model capabilities.
Features
- Agent Framework — Create AI agents with tool support and streaming responses
- Session Management — Persistent conversation state with branching support
- LLM Streaming — Real-time streaming with proper backpressure handling
- Error Resilience — Automatic retry with exponential backoff
- Context Management — Token counting and automatic context pruning
- Tool Framework — Type-safe tool definitions with Zod schemas
- Middleware — Composable lifecycle hooks for tool interception, prompt injection, logging, and guardrails
- Skills — Modular knowledge packs with progressive disclosure (L1 summary → L2 content → L3 resources)
- Approval System — Configurable tool approval via middleware (rules + interactive prompts)
- Checkpoint System — Undo/restore capabilities for file operations
- Model Capabilities — Runtime model capability detection
- MCP Support — Extend agents with Model Context Protocol servers
- Sub-Agents — Fork agents with inherited config, run tasks in parallel, or let the LLM delegate via
invoke_agent - Presets — Reusable agent configurations with tool filtering
Installation
npm install @cuylabs/agent-core
# or
pnpm add @cuylabs/agent-coreYou'll also need at least one AI SDK provider:
npm install @ai-sdk/openai
# or @ai-sdk/anthropic, @ai-sdk/googleFor OpenAI-compatible endpoints (local or hosted), add:
npm install @ai-sdk/openai-compatibleQuick Start
import { createAgent, Tool } from "@cuylabs/agent-core";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
// Define tools
const greet = Tool.define("greet", {
description: "Greet a user by name",
parameters: z.object({
name: z.string().describe("Name to greet"),
}),
execute: async ({ name }) => ({
title: "Greeting",
output: `Hello, ${name}!`,
metadata: {},
}),
});
// Create agent
const agent = createAgent({
model: openai("gpt-4o"),
tools: [greet],
systemPrompt: "You are a helpful assistant.",
});
// Stream responses
for await (const event of agent.chat("session-1", "Hello!")) {
switch (event.type) {
case "text-delta":
process.stdout.write(event.text);
break;
case "tool-start":
console.log(`Calling tool: ${event.toolName}`);
break;
case "tool-result":
console.log(`Tool result: ${event.result}`);
break;
case "complete":
console.log("\nDone!");
break;
}
}API Reference
Local Models (Ollama Example)
import { createAgent, createModelResolver } from "@cuylabs/agent-core";
const resolveModel = createModelResolver({
engines: {
ollama: {
adapter: "openai-compatible",
settings: {
baseUrl: "http://localhost:11434/v1",
},
},
},
});
const agent = createAgent({
model: resolveModel("ollama/llama3.1"),
cwd: process.cwd(),
});Agent
import { createAgent, Agent } from "@cuylabs/agent-core";
import { openai } from "@ai-sdk/openai";
const agent = createAgent({
model: openai("gpt-4o"),
cwd: "/path/to/project",
tools: myTools,
systemPrompt: "You are a helpful assistant.",
temperature: 0.7,
maxSteps: 10,
});
// Streaming chat
for await (const event of agent.chat(sessionId, message)) {
// Handle events
}Session Management
import {
SessionManager,
MemoryStorage,
FileStorage
} from "@cuylabs/agent-core";
// In-memory storage (default)
const memoryStorage = new MemoryStorage();
// File-based persistent storage
const fileStorage = new FileStorage({
dataDir: "/path/to/data",
});
const sessions = new SessionManager(fileStorage);
await sessions.createSession({ id: "my-session" });Tool Framework
import { Tool } from "@cuylabs/agent-core";
import { z } from "zod";
const calculator = Tool.define("calculator", {
description: "Evaluate a math expression",
parameters: z.object({
expression: z.string(),
}),
execute: async ({ expression }, ctx) => {
// ctx provides sessionID, cwd, abort signal, host, etc.
return {
title: "Calculator",
output: String(eval(expression)), // Don't actually do this!
metadata: {},
};
},
});Middleware
Composable lifecycle hooks for tool interception, prompt injection, logging, and guardrails:
import { createAgent, type AgentMiddleware, approvalMiddleware } from "@cuylabs/agent-core";
// Simple logging middleware
const logger: AgentMiddleware = {
name: "logger",
async beforeToolCall(tool, args) {
console.log(`→ ${tool}`, args);
return { action: "allow" };
},
async afterToolCall(tool, _args, result) {
console.log(`← ${tool}`, result.title);
return result;
},
async onChatStart(sessionId, message) {
console.log(`Chat started: ${sessionId}`);
},
async onChatEnd(sessionId, { usage, error }) {
console.log(`Chat ended: ${usage?.totalTokens} tokens`);
},
};
// Guardrail middleware — block dangerous tools
const guardrail: AgentMiddleware = {
name: "guardrail",
async beforeToolCall(tool) {
if (tool === "bash") {
return { action: "deny", reason: "Shell commands are disabled." };
}
return { action: "allow" };
},
};
const agent = createAgent({
model: openai("gpt-4o"),
tools: myTools,
middleware: [
logger,
guardrail,
approvalMiddleware({
rules: [{ pattern: "*", tool: "read_file", action: "allow" }],
onRequest: async (req) => {
// Prompt user for approval
return await askUser(req);
},
}),
],
});Middleware hooks: beforeToolCall, afterToolCall, promptSections, onEvent, onChatStart, onChatEnd. Sub-agents inherit middleware via fork().
Skills
Modular knowledge packs with three-level progressive disclosure:
import { createAgent, createSkillRegistry, createSkillTools } from "@cuylabs/agent-core";
// Discover skills from SKILL.md files
const registry = await createSkillRegistry(process.cwd(), {
externalDirs: [".agents", ".claude"],
});
// Create skill tools (skill + skill_resource)
const skillTools = createSkillTools(registry);
// Inject L1 summary into system prompt, give agent skill tools
const agent = createAgent({
model: openai("gpt-4o"),
tools: [...myTools, ...skillTools],
systemPrompt: `You are a coding assistant.\n\n${registry.formatSummary()}`,
});Skills use the SKILL.md format with YAML frontmatter. See examples/skills/ for samples.
Built-in Sub-Agent Tools
Let the LLM delegate work to specialized sub-agents via tool calls:
import { createAgent, createSubAgentTools, Presets } from "@cuylabs/agent-core";
import type { AgentProfile } from "@cuylabs/agent-core";
const profiles: AgentProfile[] = [
{ name: "explorer", description: "Fast code search", preset: Presets.explore },
{ name: "reviewer", description: "Thorough code review", preset: Presets.review },
];
const parent = createAgent({ model, tools: codeTools });
const subTools = createSubAgentTools(parent, { profiles, async: true });
const agent = createAgent({
model,
tools: [...codeTools, ...subTools],
});
// The LLM can now call invoke_agent, wait_agent, and close_agentTools: invoke_agent (spawn), wait_agent (collect results), close_agent (cancel). Supports sync and async modes, depth limiting, and concurrency control. See docs/subagents.md.
Error Handling & Retry
import {
withRetry,
LLMError,
isRetryable,
getRetryDelay
} from "@cuylabs/agent-core";
const result = await withRetry(
async () => {
// Your operation
},
{
maxRetries: 3,
initialDelay: 1000,
maxDelay: 30000,
}
);Context Management
import {
ContextManager,
estimateTokens,
pruneContext
} from "@cuylabs/agent-core";
const ctx = new ContextManager({
maxInputTokens: 100000,
pruneThreshold: 0.9,
});
const tokens = estimateTokens("Your text here");Model Capabilities
import {
ModelCapabilityResolver,
getDefaultResolver
} from "@cuylabs/agent-core";
const resolver = getDefaultResolver();
const caps = await resolver.resolve("gpt-4o");
console.log(caps.supportsImages);
console.log(caps.supportsToolUse);
console.log(caps.maxOutputTokens);MCP (Model Context Protocol)
import {
createAgent,
createMCPManager,
stdioServer
} from "@cuylabs/agent-core";
import { anthropic } from "@ai-sdk/anthropic";
// Create MCP manager with servers
const mcp = createMCPManager({
filesystem: stdioServer("npx", [
"-y", "@modelcontextprotocol/server-filesystem", "/tmp"
]),
});
// Create agent with MCP tools
const agent = createAgent({
model: anthropic("claude-sonnet-4-20250514"),
mcp,
});
// MCP tools are automatically available
const result = await agent.send("session", "List files in /tmp");
// Clean up
await agent.close();Event Types
The chat() method yields these event types:
| Event Type | Description |
|------------|-------------|
| text-delta | Streaming text chunk |
| text-start / text-end | Text generation boundaries |
| tool-start | Tool invocation started |
| tool-result | Tool execution completed |
| tool-error | Tool execution failed |
| step-start / step-finish | LLM step boundaries with usage stats |
| reasoning-delta | Model reasoning chunk (if supported) |
| status | Agent state changes (thinking, calling-tool, etc.) |
| doom-loop | Repeated tool call pattern detected |
| context-overflow | Context window exceeded |
| message | Complete user/assistant message |
| complete | Stream finished with usage stats |
| error | Unrecoverable error |
See examples/03-streaming.ts for a complete event handling example.
Concurrency Note
When using runConcurrent() to run multiple tasks in parallel, be aware that all tasks share the same SessionManager instance. For independent conversations, create separate Agent instances or use distinct session IDs.
Examples
See examples/ for 17 runnable examples covering every feature — from basic chat to middleware, skills, and Docker execution. The examples README has setup instructions and run commands.
Documentation
See docs/ for in-depth guides on each subsystem.
