@backendkit-labs/agent-core

中文文档
0.37.1发布于 2026/10/28TypeScriptCommonJS近 30 天下载 4971

Generic multi-agent engine — model-agnostic, transport-agnostic

原始使用文档
文档目录67 节
  1. Table of contents
  2. Installation
  3. Quick start
  4. defineTool — typed tools with Zod
  5. Basic tool
  6. Optional parameters and defaults
  7. Structured / nested input
  8. Returning JSON
  9. Tool that throws (error signaling)
  10. AgentEngine — the execution core
  11. Constructor options
  12. Running the engine
  13. Iteration modes
  14. Multi-turn sessions
  15. createBaseEngine — high-level factory
  16. With MCP servers and orchestration
  17. Context compaction (token budget)
  18. Guardrails
  19. Tool cancellation and time limits
  20. Providers
  21. Custom LLM provider
  22. Transports
  23. StdoutTransport
  24. CallbackTransport
  25. AgentEvent types
  26. SessionMemory
  27. MCP client
  28. Stdio (most common)
  29. HTTP transport
  30. SSE transport (legacy servers)
  31. Multiple servers via engine options
  32. Disconnect detection (health monitoring)
  33. Workflow engine
  34. Sequential workflow
  35. Context passing between steps
  36. Parallel steps
  37. Conditional steps
  38. WorkflowRunner (direct usage)
  39. Advanced memory system
  40. Creating a full memory system
  41. Using each layer directly
  42. Forgetting curve (v0.22.3+)
  43. In-memory store (testing)
  44. JSON file store (production)
  45. MemoryStoreBackend interface
  46. Memory tools (agents writing their own memory)
  47. Observability
  48. Enable observability on the engine
  49. Cost tracking
  50. Metrics snapshot
  51. Custom span exporter
  52. Audit log (NDJSON)
  53. Skills and slash commands
  54. Loading built-in skills
  55. Inline skill definition
  56. Slash commands
  57. Checkpoints
  58. Save a checkpoint
  59. List and restore
  60. Compact a long session
  61. Workspaces
  62. Autonomous agents and triggers
  63. Scheduled agent (cron)
  64. Event-driven agent
  65. TriggerManager (low-level)
  66. Delegation bus
  67. Type reference

@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' | 'step-by-step' | 'interactive' (default)

  // 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

ModeBehavior
'auto'Runs until complete or maxIterations reached
'step-by-step'Calls onStep after every iteration; return false to stop
'interactive'Calls onIterationLimit when cap is reached; return true to continue. Without that callback the run stops at the cap

Tool approval is independent of the mode: whenever onToolApproval is set, the engine asks before write_file, edit_file, run_command and package installs.

// Approval — user approves destructive tool calls
const engine = new AgentEngine({
  ...opts,
  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
});

Context compaction (token budget)

maxContextMessages caps the history by message count and discards what it cuts. With context the history is kept under a token budget instead, and old turns are summarized rather than lost:

const engine = createBaseEngine({
  // ...
  context: {
    maxTokens: 60_000,                                         // budget for the history (not the system prompt)
    summarizer: { provider: 'deepseek', model: 'deepseek-chat' }, // cheap model for summaries (default: the default provider)
    // strategy: 'summarize' | 'drop'   (default 'summarize'; a failing summarizer falls back to 'drop')
    // keepRecentTurns: 4               (last user turns are never touched)
    // toolResultMaxChars: 8000         (older tool results are cut head+tail)
    // threshold: 0.85                  (compact above 85% of maxTokens, down to 70%)
    // estimateTokens: (msg) => number  (default ~4 chars per token, calibrated with real provider usage)
  },
});

Before every LLM call the engine (1) truncates bulky old tool results, (2) folds the oldest turns into one <conversation_summary> block merged into the first kept user turn, extending the previous summary instead of nesting a new one, and (3) as a last resort drops them. Cuts always fall on a user-turn boundary, so tool_call / tool result pairs are never orphaned. It emits compacting events and a [Context] system line; summarizer usage is reported as metrics events like any other call, so CostTracker accounts for it. Compaction is best-effort: any failure is reported and the run continues. Can be combined with maxContextMessages.


Guardrails

Checks on what enters and leaves the agent loop, applied to the main agent and every sub-agent:

PhaseText inspectedWhen a guardrail blocks
inputthe user messagethe run ends with an error event; the model is never called
tool_callthe JSON arguments of a tool call (before approval)the tool does not run; the model is told why and the arguments are scrubbed from the history
tool_resultwhat a tool returned (before the model reads it)the result is replaced by a notice
outputthe model's answerthe answer is replaced by a notice in the history
import { createBaseEngine, secretLeakGuardrail, piiGuardrail, promptInjectionGuardrail } from '@backendkit-labs/agent-core';

const engine = createBaseEngine({
  // ...
  guardrails: [secretLeakGuardrail(), piiGuardrail(), promptInjectionGuardrail()],
  guardrailOutputMode: 'after',   // or 'buffer'
});

Ready-made: piiGuardrail (email, phone [heuristic], card [Luhn], IBAN [mod 97], US SSN), secretLeakGuardrail (literal AgentProfile.secrets of every agent plus common key formats; always blocks them in tool arguments), promptInjectionGuardrail (heuristic: wraps suspicious tool results as <untrusted_content>; not a guarantee), maxLengthGuardrail, denyListGuardrail, llmJudgeGuardrail (one extra LLM call per check).

Write your own: { name, phases, check(ctx) => { action: 'allow' } | { action: 'redact', content, reason } | { action: 'block', reason }, failMode?, dryRun? }. redact feeds the next guardrail; the first block wins. A guardrail that throws blocks (failMode: 'closed', default) unless you set failMode: 'open'. dryRun: true only reports, which is the way to tune false positives.

guardrailOutputMode: with after (default) tokens stream as they arrive; if an output guardrail changes the answer, the stored history is corrected and a guardrail event carries the replacement for the UI. With buffer tokens are held until the answer is checked: nothing unchecked is ever shown, but there is no streaming.

Every redaction, block and error emits a { type: 'guardrail', name, phase, action, reason, ... } event, is written to the auditLog as guardrail:<phase>:<name>, and is counted in MetricsSnapshot.guardrails.


Tool cancellation and time limits

Every tool call receives ctx.signal, an AbortSignal that fires when the engine is aborted (engine.abort(), or a budget stop with abortOnBudget) or when the call's time limit passes. Hand it to fetch, child processes and anything else cancellable:

const fetchPage = defineTool({
  name: 'fetch_page',
  description: 'Fetch a URL',
  input: z.object({ url: z.string().url() }),
  timeoutMs: 15_000,                       // this tool's limit; overrides the engine default
  execute: async ({ url }, ctx) => (await fetch(url, { signal: ctx.signal })).text(),
});

const engine = createBaseEngine({
  // ...
  toolTimeoutMs: 60_000,                   // default limit for tools without their own (default: none)
  toolTimeoutGraceMs: 2000,                // wait for a tool to wind down after its signal fires
});

When a limit passes, the signal is aborted and, after the grace period, the model receives Error: TOOL_TIMEOUT: tool "fetch_page" exceeded 15000ms (tool_result.outcome = 'timeout'); a result that arrives later is discarded. After engine.abort() a running tool reports Error: aborted (outcome = 'aborted'). Time spent waiting for approval does not count. A tool with side effects that times out may still have completed, so make such tools idempotent.


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

TypeKey fieldsDescription
'chunk'content: stringIncremental LLM token
'tool_call'name, args, resultTool called and returned
'agent_switch'from, toMulti-agent delegation
'iteration'count, maxIteration counter
'done'final: stringRun completed
'error'message: stringRecoverable 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.

LayerPurposeUse case
EpisodicMemoryWhat happened — events and interactions"Last week we refactored auth"
SemanticMemoryWhat is known — facts and domain knowledge"Alice owns the auth module"
ProceduralMemoryHow 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' | '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

版本与发布记录

@backendkit-labs/agent-core 共发布 39 个版本,首次发布于 2026/06/05;过去 12 个月发布了 39 个版本。

版本发布日期距今
0.37.1latest2026/10/2810 天前
0.37.02026/10/2711 天前
0.36.22026/10/2712 天前
0.36.12026/10/2712 天前
0.36.02026/10/2712 天前
0.35.02026/10/2612 天前

关于 @backendkit-labs/agent-core 的常见问题

@backendkit-labs/agent-core 是什么?

Generic multi-agent engine — model-agnostic, transport-agnostic

如何安装 @backendkit-labs/agent-core?

在项目目录运行 npm install @backendkit-labs/agent-core,或使用 pnpm add / yarn add / bun add。

@backendkit-labs/agent-core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@backendkit-labs/agent-core 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@backendkit-labs/agent-core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@backendkit-labs/agent-core 可以商用吗?

@backendkit-labs/agent-core 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@backendkit-labs/agent-core 的下载量有多少?

根据 npm 官方下载统计,@backendkit-labs/agent-core 在 2026/10/08 – 2026/11/06 这 30 天内被下载 4,971 次,日均约 166 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@backendkit-labs/agent-core 还在维护吗?

最新版本 0.37.1 发布于 2026/10/28(10 天前);至今共发布 39 个版本,其中过去 12 个月发布了 39 个;首次发布于 2026/06/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。