@backendkit-labs/agent-core
v0.23.0
Published
Generic multi-agent engine — model-agnostic, transport-agnostic
Readme
@backendkit-labs/agent-core
The foundation engine for the BackendKit Labs agent framework. Provides everything needed to build, run, and compose AI agents: typed tool definitions, provider adapters, session memory, MCP client, workflow orchestration, and advanced multi-layer memory.
Status:
core— Required by the enterprise use case. Breaking changes go through full review before merging.
Table of contents
- Installation
- Quick start
- defineTool — typed tools with Zod
- AgentEngine — the execution core
- createBaseEngine — high-level factory
- Providers
- Transports
- SessionMemory
- MCP client
- Workflow engine
- Advanced memory system
- Observability
- Skills and slash commands
- Checkpoints
- Workspaces
- Autonomous agents and triggers
- Delegation bus
- Type reference
Installation
npm install @backendkit-labs/agent-coreRequires Node.js ≥ 18 and TypeScript ≥ 5.0.
Quick start
The fastest path to a running agent:
import {
AgentEngine,
AgentRegistry,
ToolRegistry,
ProviderRegistry,
defineTool,
z,
StdoutTransport,
} from '@backendkit-labs/agent-core';
// 1. Define a tool
const weatherTool = defineTool({
name: 'get_weather',
description: 'Returns current weather for a city',
input: z.object({
city: z.string().describe('City name'),
}),
execute: async ({ city }) => `Weather in ${city}: sunny, 22°C`,
});
// 2. Register tools and agents
const tools = new ToolRegistry();
tools.register(weatherTool);
const agents = new AgentRegistry();
agents.register({
id: 'general',
name: 'General Assistant',
systemPrompt: 'You are a helpful assistant. Use available tools when relevant.',
tools: ['get_weather'],
});
// 3. Register an LLM provider
const providers = new ProviderRegistry();
providers.register('my-provider', myLLMProvider);
// 4. Run
const engine = new AgentEngine({
model: { provider: 'my-provider', id: 'gpt-4o' },
agents,
tools,
providers,
defaultProvider: 'my-provider',
defaultAgentId: 'general',
transport: new StdoutTransport(),
maxIterations: 10,
iterationMode: 'auto',
});
await engine.run("What's the weather in London?");defineTool — typed tools with Zod
defineTool wraps a function with a Zod input schema. The engine validates inputs before calling execute, and the TypeScript types flow through automatically.
Basic tool
import { defineTool, z } from '@backendkit-labs/agent-core';
const greetTool = defineTool({
name: 'greet',
description: 'Greet a user by name',
input: z.object({ name: z.string() }),
execute: async ({ name }) => `Hello, ${name}!`,
});Optional parameters and defaults
const searchTool = defineTool({
name: 'search_products',
description: 'Search the product catalog',
input: z.object({
query: z.string().describe('Search term'),
category: z.string().optional().describe('Filter by category'),
limit: z.number().min(1).max(50).default(10),
sort: z.enum(['price_asc', 'price_desc', 'relevance']).default('relevance'),
}),
execute: async ({ query, category, limit, sort }) => {
const results = await db.products.search({ query, category, limit, sort });
return results.map(p => `${p.name} — $${p.price}`).join('\n');
},
});Structured / nested input
const createOrderTool = defineTool({
name: 'create_order',
description: 'Create a new customer order',
input: z.object({
customer: z.object({
id: z.string(),
email: z.string().email(),
}),
items: z.array(z.object({
productId: z.string(),
qty: z.number().int().positive(),
})).min(1),
shippingAddress: z.string(),
notes: z.string().optional(),
}),
execute: async ({ customer, items, shippingAddress, notes }) => {
const order = await orders.create({ customer, items, shippingAddress, notes });
return `Order ${order.id} created for ${items.length} item(s).`;
},
});Returning JSON
Return a JSON string so the LLM can reason about structured data:
const analyzeCodeTool = defineTool({
name: 'analyze_code',
description: 'Analyze code and return structured issues',
input: z.object({
code: z.string(),
language: z.string(),
}),
execute: async ({ code, language }) => {
const issues = await analyzer.run(code, language);
return JSON.stringify({
issueCount: issues.length,
critical: issues.filter(i => i.severity === 'critical'),
warnings: issues.filter(i => i.severity === 'warning'),
});
},
});Tool that throws (error signaling)
When a tool throws, the engine catches the error and tells the LLM, which can retry with corrected arguments or apologize to the user:
const lookupUserTool = defineTool({
name: 'lookup_user',
description: 'Look up a user by email',
input: z.object({ email: z.string().email() }),
execute: async ({ email }) => {
const user = await db.users.findByEmail(email);
if (!user) throw new Error(`No user found with email ${email}`);
return JSON.stringify(user);
},
});AgentEngine — the execution core
AgentEngine is the stateful agent runner. It owns the conversation loop, tool dispatch, multi-agent delegation, and iteration control.
Constructor options
new AgentEngine({
// Required
model: { provider: 'openai', id: 'gpt-4o' },
agents,
tools,
providers,
defaultProvider: 'openai',
defaultAgentId: 'general',
// I/O
transport: new StdoutTransport(),
sessionId: 'session-xyz',
// Iteration control
maxIterations: 50,
iterationMode: 'auto', // 'auto' | 'manual' | 'step-by-step' | 'interactive'
// Memory
sessionMemory: mySessionMemory,
memorySystem: myMemorySystem,
historyPath: '/path/to/history.json',
// MCP servers (connected lazily on first run)
mcpServers: [
{ name: 'github', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'] }
],
// Observability
observability: obsManager,
auditLog: '/path/to/audit.ndjson',
// Context injected into the system prompt
projectContext: 'You are working in project X...',
})Running the engine
// Basic run — returns when done
const result = await engine.run('Summarize the README.md file');
console.log(result.final); // final assistant response
// Run with a specific agent
const result = await engine.run('Review this code for security issues', {
agentId: 'security-reviewer',
});
// Stream events to a UI
const events: AgentEvent[] = [];
await engine.run('Analyze the database schema', {
onEvent: (event) => {
if (event.type === 'chunk') process.stdout.write(event.content);
events.push(event);
},
});Iteration modes
| Mode | Behavior |
|------|----------|
| 'auto' | Runs until complete or maxIterations reached |
| 'manual' | Pauses before write/execute tool calls, calls onToolApproval |
| 'step-by-step' | Calls onStep after every iteration; return false to stop |
| 'interactive' | Calls onIterationLimit when cap is reached; return true to continue |
// Manual approval — user approves destructive tool calls
const engine = new AgentEngine({
...opts,
iterationMode: 'manual',
onToolApproval: async (toolName, agentId, argsPreview) => {
console.log(`[${agentId}] → ${toolName}:`, argsPreview);
const answer = await prompt('Approve? (y/n/all) ');
if (answer === 'y') return 'approve';
if (answer === 'all') return 'approve_all';
return 'reject';
},
});// Step-by-step — inspect each iteration
const engine = new AgentEngine({
...opts,
iterationMode: 'step-by-step',
onStep: async (stats) => {
console.log(`Iteration ${stats.count}/${stats.max} — agent: ${stats.agentId}`);
return true; // false to stop
},
});Multi-turn sessions
The engine retains conversation history between run() calls on the same instance:
await engine.run('My name is Alice.');
await engine.run("What's my name?"); // → "Your name is Alice."createBaseEngine — high-level factory
createBaseEngine assembles all internals from a flat options object so you don't have to instantiate registries manually.
import { createBaseEngine } from '@backendkit-labs/agent-core';
const engine = createBaseEngine({
providers: { openai: myOpenAIProvider },
defaultProvider: 'openai',
agents: myAgentList, // AgentProfile[]
tools: myToolList, // ToolDefinition[]
defaultAgent: 'general',
transport: new StdoutTransport(),
maxIterations: 100,
workingDir: process.cwd(),
});
await engine.run('Hello!');With MCP servers and orchestration
const engine = createBaseEngine({
providers: { anthropic: myAnthropicProvider },
defaultProvider: 'anthropic',
agents: myAgents,
tools: myTools,
defaultAgent: 'general',
mcpServers: [
{
name: 'github',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-github'],
env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN! },
},
{
name: 'postgres',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-postgres', process.env.DATABASE_URL!],
},
],
orchestration: true, // enables intent detection + domain routing + QA review
});Providers
Providers wrap LLM APIs and implement the LLMProvider interface. ProviderRegistry holds named providers that the engine selects per-agent.
import { ProviderRegistry } from '@backendkit-labs/agent-core';
const providers = new ProviderRegistry();
// Register multiple providers — engine selects per-agent or falls back to defaultProvider
providers.register('openai', openAIProvider);
providers.register('anthropic', anthropicProvider);
providers.register('local', ollamaProvider);Custom LLM provider
Implement LLMProvider to add any API:
import type { LLMProvider, LLMMessage, LLMStreamCallbacks, ToolDefinition } from '@backendkit-labs/agent-core';
class MyProvider implements LLMProvider {
async chat(
messages: LLMMessage[],
tools: ToolDefinition[],
callbacks: LLMStreamCallbacks,
): Promise<void> {
const stream = await myAPI.stream({ messages, tools });
for await (const chunk of stream) {
if (chunk.type === 'text') callbacks.onChunk(chunk.text);
if (chunk.type === 'tool_use') callbacks.onToolCall({ id: chunk.id, name: chunk.name, arguments: chunk.input });
}
callbacks.onDone();
}
}
providers.register('my-api', new MyProvider());Transports
Transports receive AgentEvent objects and deliver them to consumers.
StdoutTransport
Prints events as formatted text — good for CLI tools:
import { StdoutTransport } from '@backendkit-labs/agent-core';
const engine = new AgentEngine({ ...opts, transport: new StdoutTransport() });CallbackTransport
Routes events to your own function — use this for server integrations or streaming UIs:
import { CallbackTransport } from '@backendkit-labs/agent-core';
const transport = new CallbackTransport((event) => {
switch (event.type) {
case 'chunk': process.stdout.write(event.content); break;
case 'tool_call': console.log(`[tool] ${event.name}`); break;
case 'agent_switch': console.log(`→ ${event.to}`); break;
case 'done': console.log('\n[done]'); break;
}
});AgentEvent types
| Type | Key fields | Description |
|------|-----------|-------------|
| 'chunk' | content: string | Incremental LLM token |
| 'tool_call' | name, args, result | Tool called and returned |
| 'agent_switch' | from, to | Multi-agent delegation |
| 'iteration' | count, max | Iteration counter |
| 'done' | final: string | Run completed |
| 'error' | message: string | Recoverable error |
SessionMemory
SessionMemory stores conversation messages for the current session. AgentEngine manages it automatically; use it directly for custom initialization or testing.
import { SessionMemory } from '@backendkit-labs/agent-core';
const memory = new SessionMemory();
// Seed with prior context
memory.addMessage({ role: 'user', content: 'My name is Alice.' });
memory.addMessage({ role: 'assistant', content: 'Nice to meet you, Alice!' });
const engine = new AgentEngine({ ...opts, sessionMemory: memory });
await engine.run("What's my name?"); // → "Your name is Alice."MCP client
MCPClientManager connects to external MCP (Model Context Protocol) servers, discovers their tools, and makes them available to the engine. Supports stdio, Streamable HTTP, and SSE transports.
Stdio (most common)
import { MCPClientManager } from '@backendkit-labs/agent-core';
const mcp = new MCPClientManager();
await mcp.connectStdio({
name: 'filesystem',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/workspace'],
env: {},
});
const tools = mcp.getTools(); // ToolDefinition[] — discovered from the serverHTTP transport
await mcp.connectStreamableHTTP({
name: 'remote-server',
baseUrl: 'https://mcp.example.com',
apiKey: process.env.MCP_API_KEY,
});SSE transport (legacy servers)
await mcp.connectSSE({
name: 'legacy-server',
url: 'http://localhost:8080/sse',
});Multiple servers via engine options
Passing mcpServers to createBaseEngine or AgentEngine is the recommended path — the engine manages the lifecycle, retries, and disconnect detection:
const engine = createBaseEngine({
...opts,
mcpServers: [
{ name: 'github', command: 'npx', args: ['-y', '@modelcontextprotocol/server-github'], env: { GITHUB_TOKEN: '...' } },
{ name: 'slack', command: 'npx', args: ['-y', '@modelcontextprotocol/server-slack'], env: { SLACK_TOKEN: '...' } },
{ name: 'postgres', command: 'npx', args: ['-y', '@modelcontextprotocol/server-postgres'], env: { DATABASE_URL: '...' } },
],
});Disconnect detection (health monitoring)
The client detects server crashes via client.onclose and marks them 'failed', preventing stale 'connected' state:
// The engine's MCPRegistrar wires this automatically.
// For manual use:
mcp.setDisconnectHandler((serverName) => {
console.warn(`MCP server "${serverName}" disconnected`);
// Re-attempt connection on next engine.run() call
});Workflow engine
Build declarative multi-agent workflows with WorkflowBuilder. Steps can run sequentially, in parallel, or conditionally.
Sequential workflow
import { workflow, agent } from '@backendkit-labs/agent-core';
const reviewPipeline = workflow('code-review')
.step('fetch', agent('general', 'List all TypeScript files in src/'))
.step('analyze', agent('security-reviewer', 'Review the files for security issues'))
.step('report', agent('general', 'Summarize the findings into a report'));
const result = await engine.runWorkflow(reviewPipeline);
console.log(result.steps.report.output);Context passing between steps
Steps reference previous outputs with {{stepName.output}}:
const dataPipeline = workflow('etl')
.step('ingest', agent('data-agent', 'Read /data/sales.csv'))
.step('transform', agent('data-agent', 'Clean {{ingest.output}}: fix nulls, normalize dates'))
.step('analyze', agent('analyst', 'Find the top 5 trends in: {{transform.output}}'));Parallel steps
const auditWorkflow = workflow('full-audit')
.parallel([
agent('frontend-agent', 'Audit React components for accessibility'),
agent('backend-agent', 'Audit API endpoints for security'),
agent('database-agent', 'Audit database schema for missing indexes'),
])
.step('merge', agent('general', 'Synthesize all three audit reports'));Conditional steps
const smartReview = workflow('smart-review')
.step('classify', agent('general', 'Classify this PR: feature, bug fix, or refactor. Reply with just the type.'))
.when('{{classify.output}} includes "bug"',
agent('qa-engineer', 'Focus on regression risk and edge cases')
)
.otherwise(
agent('general', 'Perform a standard code quality review')
);WorkflowRunner (direct usage)
import { WorkflowRunner } from '@backendkit-labs/agent-core';
const runner = new WorkflowRunner(engine);
const result = await runner.run(myWorkflow, {
initialContext: { projectName: 'my-app', version: '2.0' },
onStepStart: (name) => console.log(`▶ ${name}`),
onStepDone: (name, out) => console.log(`✓ ${name}: ${out.slice(0, 80)}`),
});
// result.steps — Map<stepName, { output, durationMs }>
// result.total — total wall time in ms
// result.failed — booleanAdvanced memory system
Beyond session history, agents can use a three-layer persistent memory. Each layer is independently configurable and backed by a pluggable store.
| Layer | Purpose | Use case |
|-------|---------|---------|
| EpisodicMemory | What happened — events and interactions | "Last week we refactored auth" |
| SemanticMemory | What is known — facts and domain knowledge | "Alice owns the auth module" |
| ProceduralMemory | How to do it — step-by-step patterns | "Deploy: build → push → apply" |
Creating a full memory system
import { createMemorySystem } from '@backendkit-labs/agent-core';
const memory = createMemorySystem({
episodic: {
provider: 'json',
path: '~/.bk-agent/projects/my-project/memory/episodes.json',
},
semantic: {
provider: 'json',
path: '~/.bk-agent/projects/my-project/memory/knowledge.json',
},
procedural: {
provider: 'json',
path: '~/.bk-agent/projects/my-project/memory/patterns.json',
},
retention: {
maxEpisodes: 500, // prunes by relevance score when exceeded (v0.22.3+)
summarizeAfter: 50,
// Optional: compress old episodes with an LLM summary
summarizeFn: async (episodes) => {
const list = episodes.map((e, i) => `${i + 1}. ${e.content}`).join('\n');
return await llm.summarize(list);
},
},
});
// Pass to engine
const engine = new AgentEngine({ ...opts, memorySystem: memory });Using each layer directly
const { episodic, semantic, procedural } = memory;
// --- Episodic ---
await episodic.add({
content: 'User asked to refactor the auth module',
tags: ['refactor', 'auth'],
timestamp: Date.now(),
});
const recentAuth = await episodic.search('auth', { limit: 5 });
// [{ id, content, tags, timestamp }, ...]
// --- Semantic ---
await semantic.add({
key: 'auth-module-owner',
value: 'Alice (Backend Team)',
tags: ['ownership', 'auth'],
});
const owner = await semantic.get('auth-module-owner');
// 'Alice (Backend Team)'
const authKnowledge = await semantic.search('auth', { limit: 10 });
// --- Procedural ---
await procedural.add({
name: 'deploy-to-staging',
steps: ['npm run build', 'docker build .', 'kubectl apply -f k8s/staging/'],
tags: ['deployment'],
});
const deploySteps = await procedural.get('deploy-to-staging');Forgetting curve (v0.22.3+)
When maxEpisodes is set and the limit is exceeded, EpisodicMemory prunes by relevance score rather than raw age:
relevanceScore = recency(0.6) × 1/(1 + daysSince × 0.1)
+ utility(0.4) × log(1 + recallCount)Episodes that are recalled frequently survive longer than episodes that sit idle. recall() automatically increments recallCount on every matched record — no extra calls needed.
// Episode recalled 4× over 30 days: score ~0.43
// Episode that sat idle for 30 days: score ~0.15
// → idle episode is pruned first when the store is fullThe recallCount and lastRecalledAt fields are persisted in JsonFileStore and survive restarts.
In-memory store (testing)
import { InMemoryStore, EpisodicMemory } from '@backendkit-labs/agent-core';
const episodic = new EpisodicMemory(new InMemoryStore());
await episodic.add({ content: 'test event', tags: [], timestamp: Date.now() });JSON file store (production)
import { JsonFileStore, SemanticMemory } from '@backendkit-labs/agent-core';
const semantic = new SemanticMemory(new JsonFileStore('/app/data/knowledge.json'));MemoryStoreBackend interface
All backends implement:
interface MemoryStoreBackend {
insert(record: StoredRecord): void;
upsert(id: string, record: StoredRecord): void;
getAll(): StoredRecord[];
deleteOldest(keepNewest: number): void;
deleteByIds(ids: string[]): void; // v0.22.3+: used by relevance-based pruning
count(): number;
}deleteByIds is used internally by EpisodicMemory pruning. Use it in custom backends to support the forgetting curve.
Memory tools (agents writing their own memory)
buildMemoryTools() creates tool definitions that let agents read and write all three memory layers:
import { buildMemoryTools } from '@backendkit-labs/agent-core';
const memTools = buildMemoryTools(memory);
// Returns: remember_episode, recall_episodes, store_knowledge, recall_knowledge,
// store_pattern, recall_pattern
memTools.forEach(t => tools.register(t));Observability
ObservabilityManager ties together cost tracking, metrics collection, and distributed tracing.
Enable observability on the engine
import { ObservabilityManager } from '@backendkit-labs/agent-core';
const obs = new ObservabilityManager({
costTracking: true,
metricsEnabled: true,
tracing: true,
});
const engine = new AgentEngine({ ...opts, observability: obs });Cost tracking
import { CostTracker, MODEL_PRICING } from '@backendkit-labs/agent-core';
const tracker = new CostTracker();
// MODEL_PRICING[model] = { input: $/1M tokens, output: $/1M tokens }
console.log(MODEL_PRICING['gpt-4o']); // { input: 2.50, output: 10.00 }
console.log(MODEL_PRICING['claude-sonnet-4-6']); // { input: 3.00, output: 15.00 }
tracker.record({ model: 'gpt-4o', inputTokens: 1500, outputTokens: 200 });
console.log(`Cost: $${tracker.getTotalCostUSD().toFixed(6)}`); // $0.005750Metrics snapshot
import { MetricsCollector } from '@backendkit-labs/agent-core';
const metrics = new MetricsCollector();
// After some runs:
const snap = metrics.getSnapshot();
// {
// sessionsActive: 3,
// runsTotal: 47,
// runsSucceeded: 44,
// runsFailed: 3,
// avgDurationMs: 1823,
// }Custom span exporter
Export traces to Jaeger, Honeycomb, Datadog, or any OpenTelemetry backend:
import { Tracer } from '@backendkit-labs/agent-core';
const tracer = new Tracer({
onSpan: (span) => {
honeycomb.sendEvent({
'span.name': span.name,
'duration_ms': span.durationMs,
'agent.id': span.agentId,
'tool.calls': span.toolCallCount,
});
},
});Audit log (NDJSON)
Every tool call can be logged to a newline-delimited JSON file:
const engine = new AgentEngine({
...opts,
auditLog: '/var/log/agent-audit.ndjson',
// Each line: { ts, agentId, toolName, args, result, durationMs }
});Skills and slash commands
Skills are reusable prompt snippets that augment agent behavior. Slash commands let users invoke agent features directly.
Loading built-in skills
import { SkillLoader, createSkillManager } from '@backendkit-labs/agent-core';
const manager = createSkillManager('my-app');
// Install a skill pack from GitHub
await manager.install({
source: 'github:backendkit-labs/skills#main',
pack: 'backend',
});
const installed = manager.listInstalled();
// [{ id, name, pack, version }, ...]Inline skill definition
import { SkillRegistry, SkillActivator } from '@backendkit-labs/agent-core';
const registry = new SkillRegistry();
registry.register({
id: 'pr-review',
name: 'PR Review',
description: 'Perform a thorough pull request review',
prompt: `
Review this pull request with the following criteria:
1. Correctness — does the code do what the PR description claims?
2. Security — any new attack surface?
3. Performance — any obvious regressions?
4. Tests — are changes covered?
Output a concise review with LGTM or CHANGES REQUESTED at the end.
`,
tags: ['github', 'review'],
});
const activator = new SkillActivator(registry);
const systemContext = activator.buildContext(['pr-review']);
// Returns the skill prompt injected into the system contextSlash commands
Built-in commands: /help, /clear, /status, /skills, /agents, /workspace, /checkpoint.
import { SlashCommandRegistry, registerBuiltinCommands } from '@backendkit-labs/agent-core';
const cmds = new SlashCommandRegistry();
registerBuiltinCommands(cmds);
// Custom command
cmds.register({
name: 'deploy',
description: 'Trigger a deployment to an environment',
handler: async (ctx) => {
const env = ctx.args[0] ?? 'staging';
await ci.triggerDeploy(env);
return `Deployment to ${env} triggered. Watch: https://ci.company.com/jobs/latest`;
},
});
// Handle user input
const input = '/deploy production';
if (input.startsWith('/')) {
const response = await cmds.handle(input, agentContext);
console.log(response);
}Checkpoints
Save and restore session state for recovery and branching.
Save a checkpoint
import { createCheckpoint, listCheckpoints, readCheckpoint } from '@backendkit-labs/agent-core';
// After a successful milestone
await createCheckpoint({
sessionId: 'session-abc',
messages: engine.getMessages(),
projectDir: '/app/.bk-agent/projects/my-project',
description: 'After auth module refactor — all tests pass',
});List and restore
// List all checkpoints
const summaries = await listCheckpoints('/app/.bk-agent/projects/my-project');
// [{ id, timestamp, description, messageCount }, ...]
// Restore a specific checkpoint
const checkpoint = await readCheckpoint(summaries[0].id, '/app/.bk-agent/projects/my-project');
const restoredMemory = new SessionMemory();
restoredMemory.loadMessages(checkpoint.messages);
const engine = new AgentEngine({ ...opts, sessionMemory: restoredMemory });
await engine.run('Continue from where we left off');Compact a long session
import { compactSession } from '@backendkit-labs/agent-core';
// Compress a 200-message conversation to a short summary
const compacted = await compactSession(
engine.getMessages(),
async (messages) => await llm.summarize(messages),
);
// Replace memory with compacted version to reduce context size
engine.replaceMessages(compacted);Workspaces
Manage multiple projects and let agents switch context between them.
import {
loadWorkspaces, createWorkspace, addProject,
setActiveProject, buildWorkspaceContext,
} from '@backendkit-labs/agent-core';
// One-time setup
const config = await loadWorkspaces('my-app');
await createWorkspace('my-app', 'work');
await addProject('my-app', 'work', {
name: 'api-server',
path: '/home/user/projects/api-server',
});
await addProject('my-app', 'work', {
name: 'frontend',
path: '/home/user/projects/frontend',
});
// Switch active project
await setActiveProject('my-app', 'work', 'frontend');
// Build context string injected into the engine's system prompt
const ctx = buildWorkspaceContext(await loadWorkspaces('my-app'));
// "Active project: frontend (/home/user/projects/frontend)\nWorkspace: work\n..."Autonomous agents and triggers
Create fire-and-forget agents that run on schedules or in response to events.
Scheduled agent (cron)
import { createAutonomousAgent } from '@backendkit-labs/agent-core';
const standupAgent = createAutonomousAgent({
engine: myEngine,
schedule: '0 9 * * 1-5', // cron: weekdays at 9 AM
buildPrompt: () => 'Generate a daily standup summary from recent git commits and open PRs.',
onResult: (output) => slack.postMessage('#standup', output),
onError: (err) => logger.error('[standup-agent]', err),
});
await standupAgent.start();
// Stops the cron job
await standupAgent.stop();Event-driven agent
import { EventBus } from '@backendkit-labs/agent-core';
const eventBus = new EventBus();
// Subscribe to an event
eventBus.on('deployment.failed', async (payload) => {
const analysis = await engine.run(
`Investigate this deployment failure:\n${JSON.stringify(payload, null, 2)}`
);
await pagerduty.alert({ summary: analysis.final });
});
// Publish from your CI/CD system
await eventBus.emit('deployment.failed', {
service: 'payment-api',
version: '2.1.4',
errorLog: lastError,
});TriggerManager (low-level)
import { TriggerManager } from '@backendkit-labs/agent-core';
const triggers = new TriggerManager();
// Cron trigger
triggers.addSchedule({
id: 'daily-report',
schedule: '0 8 * * *',
handler: async () => engine.run('Generate daily report'),
});
// Webhook trigger
triggers.addWebhook({
id: 'github-pr',
path: '/webhooks/github',
handler: async (body) => engine.run(`Review PR: ${body.pull_request.title}`),
});
await triggers.start();Delegation bus
DelegationBus enables multi-agent collaboration — orchestrators delegate sub-tasks to specialized agents and collect results.
import { DelegationBus } from '@backendkit-labs/agent-core';
const bus = new DelegationBus();
// Register specialized agents
bus.register('security-agent', async (req) => ({
output: (await securityEngine.run(req.prompt)).final,
agentId: 'security-agent',
}));
bus.register('data-agent', async (req) => ({
output: (await dataEngine.run(req.prompt)).final,
agentId: 'data-agent',
}));
// Delegate from orchestrator
const secResult = await bus.delegate({
targetAgentId: 'security-agent',
prompt: 'Audit this SQL query for injection risks: SELECT * FROM users WHERE id=' + input,
context: currentMessages,
});
console.log(secResult.output);Type reference
Key types exported from @backendkit-labs/agent-core:
// Engine
AgentEngineOptions // Full constructor options
BaseEngineOptions // Options for createBaseEngine
// Agents
AgentProfile // { id, name, systemPrompt, tools, provider? }
// Tools
ToolDefinition // { name, description, inputSchema, execute }
TypedToolOptions // Options for defineTool
ExecutionContext // Runtime context passed to tool.execute
// LLM
LLMProvider // Interface for LLM adapters
LLMMessage // { role, content }
LLMToolCall // { id, name, arguments }
LLMStreamCallbacks // { onChunk, onToolCall, onDone, onError }
ModelConfig // { provider, id }
// Memory
SessionMemoryData // Serialized session memory
MemorySystemConfig // createMemorySystem options
MemoryStoreBackend // 'json' | 'memory'
Episode // Episodic memory entry
Knowledge // Semantic memory entry
Pattern // Procedural memory entry
// MCP
MCPServerConfig // { name, command, args, env?, baseUrl?, url? }
// Workflows
WorkflowDef // Compiled workflow definition
WorkflowResult // { steps, total, failed }
WorkflowRunOptions // WorkflowRunner.run() options
// Iteration
IterationMode // 'auto' | 'manual' | 'step-by-step' | 'interactive'
IterationStats // { count, max, agentId }
ToolApprovalDecision // 'approve' | 'approve_all' | 'reject'
// Observability
ObservabilityConfig // { costTracking, metricsEnabled, tracing }
MetricsSnapshot // Current counters snapshot
Span // Tracing span
SpanExporter // Interface for custom exporters
// Events
AgentEvent // Union of all transport event types