Documentation

Everything you need to build with BoringOS.

Getting Started

Quick Start

npx create-boringos my-app
cd my-app
npm run dev

Server starts on http://localhost:3000 with embedded Postgres. No external dependencies needed.

Or install manually

npm install @boringos/core

Create index.ts:

import { BoringOS } from "@boringos/core";

const app = new BoringOS({});
const server = await app.listen(3000);
console.log("Running at", server.url);

Run it:

npx tsx index.ts

That's it. Embedded Postgres boots, schema created, 6 runtimes registered, admin API + callback API + SSE events — all ready.

Build Guideline

Read the full BUILD_GUIDELINE.md for how to structure a BoringOS app — agent definitions, custom context providers, block handlers, seed data, and frontend setup.

Using an AI coding agent? Point it at these two files and let it build:

  1. CLAUDE.md — framework overview, all packages, every API endpoint
  2. BUILD_GUIDELINE.md — app structure, how to define agents, workflows, connectors, custom schema

Your AI agent reads the specs, understands the framework, and builds your app. That's the BoringOS way.

Core Concepts

Agents

AI agents run as CLI subprocesses — Claude Code, Codex, Gemini CLI, Ollama, or any command. The framework never calls LLM APIs directly. CLIs are the agents, BoringOS is the orchestrator.

Each agent has a role (engineer, researcher, PM, etc.) that determines its persona — how it thinks, writes, and approaches work.

Templates: Create agents from built-in templates with one call:

await createAgentFromTemplate(db, "engineer", { tenantId, name: "Code Bot" });

Teams: Create full teams with hierarchy pre-wired:

await createTeam(db, "engineering", { tenantId });
// → CTO + 2 Engineers + QA, all with reportsTo set

5 built-in teams: engineering, executive, content, sales, support.

Hierarchy: Agents have a reportsTo field. The framework injects org context ("You report to: CTO. Your reports: Engineer 1, Engineer 2."), handles delegation (match task to best report by role), and escalation (blocked → auto-create task for boss).

Tasks

Work items assigned to agents. Tasks have:

  • Status flow: backlog → todo → in_progress → in_review → blocked → done/cancelled
  • Auto-identifiers: BOS-001 (tenant-level) or ALPHA-001 (project-level)
  • Comments: threaded conversation between humans and agents
  • Work products: deliverables (PR, deploy, doc, test, file)
  • Labels, attachments, read states, checkout locks

Runtimes

The execution backend that spawns a CLI subprocess. 6 built-in:

RuntimeWhat it spawns
claudeClaude Code CLI
chatgptOpenAI Codex CLI
geminiGoogle Gemini CLI
ollamaLocal Ollama model
commandAny shell command
webhookHTTP POST to URL

Context Pipeline

Before each run, 12 composable providers build the agent's instructions:

System instructions (authoritative, via temp file):

  1. Header — agent identity
  2. Persona — SOUL.md + AGENTS.md + HEARTBEAT.md
  3. Tenant guidelines — company-wide rules
  4. Drive skill — file organization rules
  5. Memory skill — how to remember/recall
  6. Agent instructions — per-agent custom instructions
  7. Execution protocol — callback API curl examples

Context markdown (task-specific, via stdin):

  1. Session handoff or first-run orientation
  2. Task details — title, description, identifier
  3. Recent comments
  4. Memory context — relevant memories
  5. Approval details

Add your own: app.contextProvider(myProvider)

Memory

Pluggable cognitive memory. Every component ships skillMarkdown() that teaches agents how to use it — this is the Code + Knowledge pattern.

  • nullMemory — default, no-op
  • createHebbsMemory() — hebbs.ai client with remember, recall, prime, forget

Workflows

DAG-based execution engine:

Trigger → Fetch Emails → Condition (any new?)
                          ├─ true  → Wake Agent → Process
                          └─ false → Skip (save cost)

6 built-in handlers:

  • trigger — entry point
  • condition — true/false branching
  • delay — wait N milliseconds
  • transform — map/reshape data
  • wake-agent — wake an agent from within a workflow (enables "smart routines")
  • connector-action — call any connector action (e.g., list_emails, list_events) with auto credential lookup

Add custom handlers with app.blockHandler().

Workflow-triggered routines: Instead of waking an expensive agent on every cron tick, target a workflow that runs cheap checks first and only wakes the agent when needed.

Connectors

External service integrations with a clean SDK:

  • OAuth handled by the framework
  • Events typed and routed via EventBus
  • Actions invocable by agents via callback API
  • Skill files teach agents how to use each connector
  • Test harness for testing without real credentials

Builder API

The BoringOS class uses a builder pattern — chain methods to configure, then .listen() to start:

import { BoringOS, createHebbsMemory } from "@boringos/core";
import { slack } from "@boringos/connector-slack";
import { google } from "@boringos/connector-google";
import { createBullMQQueue } from "@boringos/pipeline";

const app = new BoringOS({
  database: { url: "postgres://..." },      // or omit for embedded
  drive: { root: "./data/drive" },           // or omit for default
  auth: { secret: "...", adminKey: "..." },  // JWT + API key auth
});

// Memory — cognitive recall for agents
app.memory(createHebbsMemory({
  endpoint: "https://api.hebbs.ai",
  apiKey: "...",
}));

// Connectors — external service integrations
app.connector(slack({ signingSecret: "..." }));
app.connector(google({ clientId: "...", clientSecret: "..." }));

// Queue — BullMQ for production (in-process by default)
app.queue(createBullMQQueue({ redis: "redis://..." }));

// Plugins — extensible jobs + webhooks
app.plugin(githubPlugin);

// Custom context provider
app.contextProvider({
  name: "company-knowledge",
  phase: "system",
  priority: 25,
  async provide(event) {
    return "## Company Knowledge\n\nOur coding standards...";
  },
});

// Custom schema — your own tables alongside framework tables
app.schema(`
  CREATE TABLE IF NOT EXISTS contacts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID REFERENCES tenants(id),
    name TEXT NOT NULL,
    email TEXT
  )
`);

// Event-to-inbox routing
app.routeToInbox({
  filter: (e) => e.type === "email_received",
  transform: (e) => ({
    source: "gmail",
    subject: e.data.subject,
    from: e.data.from,
  }),
});

// Lifecycle hooks
app.beforeStart(async (ctx) => { /* runs before server starts */ });
app.afterStart(async (ctx) => { /* runs after server starts */ });

// Custom Hono routes
app.route("/api/crm", crmRoutes);

// Start
const server = await app.listen(3000);

All builder methods

MethodDescription
.memory(provider)Set memory provider
.connector(definition)Register connector
.runtime(module)Register additional runtime
.queue(adapter)Set job queue (default: in-process)
.plugin(definition)Register plugin
.contextProvider(provider)Add custom context provider
.blockHandler(handler)Add workflow block handler
.persona(role, bundle)Register custom persona
.schema(ddl)Add custom database tables
.routeToInbox(config)Route events to inbox
.route(path, app)Mount custom Hono routes
.beforeStart(fn)Pre-boot lifecycle hook
.afterStart(fn)Post-boot lifecycle hook
.beforeShutdown(fn)Shutdown lifecycle hook
.listen(port?)Boot and start HTTP server

Packages

All packages are independently installable from npm. Use what you need.

PackageDescription
@boringos/coreApplication host — BoringOS class, builder API, HTTP server
@boringos/agentExecution engine — context pipeline, wakeups, personas, budget
@boringos/runtime6 runtime modules + registry + subprocess spawning
@boringos/memoryMemoryProvider interface + hebbs.ai provider + null provider
@boringos/driveStorageBackend + DriveManager with file indexing + memory sync
@boringos/dbDrizzle schema + embedded Postgres + migration manager
@boringos/workflowDAG workflow engine + 6 block handlers (incl. wake-agent, connector-action)
@boringos/pipelineQueueAdapter — in-process (default) or BullMQ
@boringos/connectorConnector SDK — OAuth, events, actions, test harness
@boringos/connector-slackSlack — messages, threads, reactions
@boringos/connector-googleGoogle Workspace — Gmail + Calendar
@boringos/uiTyped API client + headless React hooks
@boringos/sharedBase types, constants, Hook utility
create-boringosCLI generator — scaffold new projects

Install

# Just the core
npm install @boringos/core

# Full stack
npm install @boringos/core @boringos/memory @boringos/connector-slack @boringos/connector-google @boringos/pipeline @boringos/ui

# Or scaffold a complete project
npx create-boringos my-app --full

Admin API

All endpoints at /api/admin/*. Authenticated via X-API-Key header or session token (Authorization: Bearer). Requires X-Tenant-Id header when using API key auth.

Agents

MethodEndpointDescription
GET/agentsList agents
POST/agentsCreate agent
GET/agents/:idGet agent
PATCH/agents/:idUpdate agent
POST/agents/:id/wakeWake agent
GET/agents/:id/runsAgent run history

Tasks

MethodEndpointDescription
GET/tasksList tasks (filter by status, assignee)
POST/tasksCreate task (auto-generates identifier)
GET/tasks/:idGet task + comments + work products
PATCH/tasks/:idUpdate task
DELETE/tasks/:idDelete task
POST/tasks/:id/commentsPost comment
POST/tasks/:id/assignAssign to agent + optionally wake

Runs

MethodEndpointDescription
GET/runsList runs (filter by agent, status)
GET/runs/:idGet run detail
POST/runs/:id/cancelCancel run

Runtimes

MethodEndpointDescription
GET/runtimesList runtimes
POST/runtimesCreate runtime
PATCH/runtimes/:idUpdate runtime
DELETE/runtimes/:idDelete runtime
POST/runtimes/:id/defaultSet as default

Approvals

MethodEndpointDescription
GET/approvalsList pending approvals
GET/approvals/:idGet approval
POST/approvals/:id/approveApprove (with optional note)
POST/approvals/:id/rejectReject (with reason)

Other endpoints

  • Projects: GET/POST /projects, GET/PATCH /projects/:id
  • Goals: GET/POST /goals, PATCH /goals/:id
  • Labels: GET/POST /labels, POST/DELETE /tasks/:id/labels/:labelId
  • Budgets: GET/POST /budgets, DELETE /budgets/:id, GET /budgets/incidents
  • Routines: GET/POST /routines (supports assigneeAgentId OR workflowId), PATCH/DELETE /routines/:id, POST /routines/:id/trigger
  • Evals: GET/POST /evals, POST /evals/:id/run, GET /evals/:id/runs
  • Inbox: GET /inbox, GET /inbox/:id, POST /inbox/:id/archive, POST /inbox/:id/create-task
  • Drive: GET /drive/list, GET/PATCH /drive/skill, GET /drive/skill/revisions
  • Plugins: GET /plugins, GET /plugins/:name/jobs, POST /plugins/:name/jobs/:job/trigger
  • Search: GET /search?q=query
  • Activity: GET /activity
  • Onboarding: GET /onboarding, POST /onboarding/complete-step
  • Entities: POST /entities/link, GET /entities/:type/:id/refs
  • Costs: GET /costs

SSE Events

GET /api/events?apiKey=...&tenantId=...

Event types: run:started, run:completed, run:failed, task:created, task:updated, task:comment_added, agent:created, approval:decided

Authentication

User auth

Signup:

curl -X POST /api/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "alice@acme.com", "password": "...", "tenantId": "..."}'

Returns { userId, token }. The token is a session token valid for 30 days.

Login:

curl -X POST /api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "alice@acme.com", "password": "..."}'

Returns { userId, token, name, email }.

Current user:

curl /api/auth/me -H "Authorization: Bearer <token>"

Admin API auth

The admin API accepts both methods:

  1. API keyX-API-Key: your-admin-key + X-Tenant-Id: tenant-uuid
  2. Session tokenAuthorization: Bearer <token> (tenant resolved from session)

Agent callback auth

Agent subprocesses receive a signed JWT (4-hour expiry, HMAC-SHA256) via the BORINGOS_CALLBACK_TOKEN env var. The callback API verifies the signature and extracts agent/tenant claims.

Device auth (CLI login)

For CLI tools (like a future boringos CLI):

  1. CLI calls POST /api/auth/device/code → gets deviceCode + userCode
  2. User opens verification URL in browser, approves with the userCode
  3. CLI polls POST /api/auth/device/poll with deviceCode → gets session token

Challenges expire after 15 minutes.

UI Hooks

@boringos/ui provides a typed API client + headless React hooks. No markup, no styles — just data and mutations.

Setup

import { BoringOSProvider, createBoringOSClient } from "@boringos/ui";

const client = createBoringOSClient({
  url: "http://localhost:3000",
  apiKey: "your-admin-key",
  tenantId: "your-tenant-id",
});

function App() {
  return (
    <BoringOSProvider client={client}>
      <YourDashboard />
    </BoringOSProvider>
  );
}

Available hooks

HookReturnsMutations
useAgents()agents listcreateAgent, wakeAgent
useTasks(filters?)tasks listcreateTask
useTask(taskId)task + commentsupdateTask, postComment, assignTask, addWorkProduct
useRuns(filters?)runs list (polls 5s)cancelRun
useRuntimes()runtimes listcreateRuntime, setDefault
useApprovals(status?)approvals listapprove, reject
useConnectors()connectors listinvokeAction
useProjects()projects list
useGoals()goals list
useOnboarding()onboarding state
useEvals()evals list
useInbox(status?)inbox items
useEntityRefs(type, id)linked entities
useSearch(query)search results
useHealth()server status (polls 30s)

Example

import { useAgents, useTask } from "@boringos/ui";

function AgentList() {
  const { agents, isLoading, createAgent } = useAgents();

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      {agents.map(agent => (
        <div key={agent.id}>{agent.name} — {agent.role}</div>
      ))}
      <button onClick={() => createAgent({ name: "New Bot", role: "engineer" })}>
        Add Agent
      </button>
    </div>
  );
}

function TaskDetail({ taskId }: { taskId: string }) {
  const { task, comments, updateTask, postComment } = useTask(taskId);

  return (
    <div>
      <h2>{task?.title}</h2>
      <select
        value={task?.status}
        onChange={(e) => updateTask({ status: e.target.value })}
      >
        <option value="todo">Todo</option>
        <option value="in_progress">In Progress</option>
        <option value="done">Done</option>
      </select>
      {comments.map(c => <p key={c.id}>{c.body}</p>)}
    </div>
  );
}

Realtime

Subscribe to SSE events:

const unsubscribe = client.subscribe((event) => {
  console.log(event.type, event.data);
  // "run:started", "task:created", "approval:decided", etc.
});

// Later:
unsubscribe();