@coopah/bentley-core
v0.3.0
Published
Agent runtime, workspace memory, skills, workflows, and DI container
Readme
@coopah/bentley-core
The core runtime for Bentley — a modular AI agent orchestration framework. Provides the DI container, event bus, ReAct agent loop, file-based workspace memory, skill system, multi-agent workflows, and the plugin architecture that all other @coopah/bentley-* packages build on.
Install
pnpm add @coopah/bentley-coreYou'll also want at least one LLM provider:
@coopah/bentley-provider-openai@coopah/bentley-provider-anthropic@coopah/bentley-provider-ollama@coopah/bentley-provider-copilot
Usage
import { createBentley } from "@coopah/bentley-core";
import { bentleyOpenAIPlugin } from "@coopah/bentley-provider-openai";
const bentley = createBentley({
plugins: [bentleyOpenAIPlugin()],
shellsPath: "./shells",
});
// One-shot invocation
const result = await bentley.invoke("my-shell", {
input: "Summarise this document",
threadId: "thread-1",
});
// Streaming invocation
for await (const chunk of bentley.invokeStream("my-shell", { input: "Hello" })) {
if (chunk.type === "token") process.stdout.write(chunk.content ?? "");
if (chunk.type === "tool_start") console.log(`Calling ${chunk.toolName}...`);
}Key Concepts
Shells — A running agent instance backed by a workspace directory containing SOUL.md (identity/system prompt), MEMORY.md (persistent facts), HEARTBEAT.md (recurring tasks), and history/*.jsonl (conversation logs).
ReAct Loop — Streaming AsyncGenerator<ShellChunk> that interleaves LLM reasoning with tool execution. Chunk types: token, tool_start, tool_end, done, error, react:iteration.
Skills — Markdown files with YAML frontmatter (name, description, version) that teach agents new capabilities at runtime.
Plugins — The extension point for registering LLM providers, tools, MCP servers, skills, shell presets, evaluators, and channel adapters via BentleyPluginContext.
Shell Examples
Registering a shell via a plugin
Shells are registered as presets inside a plugin's register() method:
import type { BentleyPlugin } from "@coopah/bentley-core";
export const myPlugin: BentleyPlugin = {
id: "my-plugin",
version: "1.0.0",
register(ctx) {
// A simple Q&A assistant
ctx.registerShellPreset({
id: "qa-assistant",
model: "openai/gpt-4o",
tools: ["memory_write", "memory_read"],
maxIterations: 5,
memory: {
strategy: "sliding_window",
maxMessages: 30,
},
});
// A research agent with skills, MCP servers, and cognitive features
ctx.registerShellPreset({
id: "researcher",
model: "anthropic/claude-sonnet-4-20250514",
tools: ["memory_write", "memory_read", "web_search"],
skills: ["deep-research", "citation-style"],
mcpServers: ["notion"],
maxIterations: 20,
memory: {
strategy: "token_budget",
maxTokens: 32_000,
},
cognitive: {
enabled: true,
innerMonologue: true,
planning: true,
reflection: true,
episodicMemory: true,
},
scopes: [
{ type: "notion_database", label: "Research Notes", target: "abc-123" },
],
});
// A proactive ops agent with heartbeat and channel routing
ctx.registerShellPreset({
id: "ops-bot",
model: "openai/gpt-4o-mini",
tools: ["memory_write", "exec"],
maxIterations: 10,
heartbeat: {
enabled: true,
intervalMs: 15 * 60 * 1000, // every 15 minutes
activeHours: { start: 8, end: 22 },
suppressIfOk: true,
},
channels: {
slack: { channel: "ops-alerts" },
webchat: true,
},
});
},
};Workspace directory structure
Each shell stores its identity and memory as plain files under shellsPath:
shells/
qa-assistant/
SOUL.md # System prompt / personality
MEMORY.md # Long-term facts (auto-appended)
HEARTBEAT.md # Proactive task checklist (optional)
SCRATCHPAD.md # Cognitive working memory (optional)
history/
thread-1.jsonl # Conversation history per thread
thread-2.jsonl
researcher/
SOUL.md
MEMORY.md
...SOUL.md — The shell's identity. Written once by you, read every invocation:
You are a research assistant specializing in AI safety.
Always cite your sources. Be precise and thorough.MEMORY.md — Persistent facts appended at runtime via the memory_write tool:
- [2026-03-10T09:15:00Z] User prefers APA citation style.
- [2026-03-10T10:42:00Z] Project deadline is March 28, 2026.HEARTBEAT.md — A checklist the agent runs on a schedule (when heartbeat.enabled is true):
- [ ] Check if new papers were published on arxiv.org/list/cs.AI
- [ ] Summarise any Slack threads in #research that mention "alignment"Invoking shells
const bentley = createBentley({
plugins: [myPlugin, bentleyOpenAIPlugin()],
shellsPath: "./shells",
});
// One-shot invocation
const { output } = await bentley.invoke({
shellId: "qa-assistant",
input: "What did we discuss last time?",
threadId: "user-42:chat-1",
});
// Streaming with tool events
for await (const chunk of bentley.invokeStream({
shellId: "researcher",
input: "Find recent papers on RLHF and add them to Notion",
threadId: "user-42:research",
})) {
switch (chunk.type) {
case "token":
process.stdout.write(chunk.content ?? "");
break;
case "tool_start":
console.log(`\n🔧 ${chunk.toolName}...`);
break;
case "tool_end":
console.log(` ✓ ${chunk.toolName} done`);
break;
case "error":
console.error("Error:", chunk.error);
break;
}
}
// Structured output with a Zod schema
import { z } from "zod";
const { parsedOutput } = await bentley.invoke({
shellId: "researcher",
input: "List the top 3 AI safety labs",
threadId: "user-42:structured",
outputSchema: z.object({
labs: z.array(z.object({
name: z.string(),
focus: z.string(),
})),
}),
});
// parsedOutput.labs → [{ name: "Anthropic", focus: "..." }, ...]
// With timeout and abort signal
const controller = new AbortController();
const result = await bentley.invoke({
shellId: "ops-bot",
input: "Run the health check",
threadId: "ops:health",
timeoutMs: 30_000,
abortSignal: controller.signal,
});API
Bootstrap
createBentley(options)— Wire up DI, registries, plugins; returns aBentleyInstanceBentleyInstance—invoke(),invokeStream(),startGateway(),stopGateway()
Agent Runtime
bentleyReactLoop/bentleyReactLoopCollect— Core ReAct loop (streaming / collected)BentleyShellRuntime— Shell lifecycle, context resolution, invocationMiddlewarePipeline— Composable middleware chain for shell execution
Workspace & Memory
BentleyFileMemory— File-based implementation ofBentleyWorkspaceMemoryresolveShellPrompt/assembleBentleyPrompt— System prompt assembly from SOUL + skills + memoryapplyMemoryStrategy/scoreMessagesByKeywords— Memory reduction strategies
Workflows
BentleyWorkflowGraph— Define multi-agent DAGs with conditional edgesCompiledBentleyWorkflow— Executable compiled graphSTART_NODE/END_NODE— Graph boundary markers
Plugin System
BentleyPlugin/BentleyPluginContext— Plugin interface and registration contextBentleyPluginManager— Plugin lifecycle managementBentleyToolRegistry— Register and resolve toolsBentleySkillRegistry— Load and resolve skill markdownBentleyMcpRegistry— MCP server connection management
LLM
BentleyProviderRegistry— Multi-provider model resolutionwithRetry/withFallback— Model middleware for resilience
Built-in Middleware
bentleyCostTrackingMiddleware— Track token usage and costbentleyRateLimitMiddleware— Per-shell rate limitingbentleyGuardrailMiddleware— Content safety guardrailsbentleyMemoryExtractionMiddleware— Auto-extract facts to MEMORY.md
Infrastructure
BentleyContainer/createToken/TOKENS— Lightweight DI containercreateEventBus— Typed event bus (mitt-based)BentleyHeartbeatRunner— Cron-like recurring task execution
Related Packages
| Package | Role |
|---------|------|
| @coopah/bentley-server | HTTP/SSE server (Hono) |
| @coopah/bentley-gateway | Multi-channel router (WhatsApp, Slack, Telegram, etc.) |
| @coopah/bentley-react | React hooks & SSE client |
| @coopah/bentley-langfuse | LangFuse observability via OpenTelemetry |
| @coopah/bentley-store-sqlite | SQLite storage backend |
| @coopah/bentley-store-postgres | PostgreSQL + pgvector storage backend |
License
MIT
