npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

npm

Status: core — Required by the enterprise use case. Breaking changes go through full review before merging.

Table of contents


Installation

npm install @backendkit-labs/agent-core

Requires 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 server

HTTP 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 — boolean

Advanced 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 full

The 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.005750

Metrics 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 context

Slash 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