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:
- CLAUDE.md — framework overview, all packages, every API endpoint
- 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) orALPHA-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:
| Runtime | What it spawns |
|---|---|
claude | Claude Code CLI |
chatgpt | OpenAI Codex CLI |
gemini | Google Gemini CLI |
ollama | Local Ollama model |
command | Any shell command |
webhook | HTTP POST to URL |
Context Pipeline
Before each run, 12 composable providers build the agent's instructions:
System instructions (authoritative, via temp file):
- Header — agent identity
- Persona — SOUL.md + AGENTS.md + HEARTBEAT.md
- Tenant guidelines — company-wide rules
- Drive skill — file organization rules
- Memory skill — how to remember/recall
- Agent instructions — per-agent custom instructions
- Execution protocol — callback API curl examples
Context markdown (task-specific, via stdin):
- Session handoff or first-run orientation
- Task details — title, description, identifier
- Recent comments
- Memory context — relevant memories
- 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-opcreateHebbsMemory()— 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 pointcondition— true/false branchingdelay— wait N millisecondstransform— map/reshape datawake-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
| Method | Description |
|---|---|
.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.
| Package | Description |
|---|---|
@boringos/core | Application host — BoringOS class, builder API, HTTP server |
@boringos/agent | Execution engine — context pipeline, wakeups, personas, budget |
@boringos/runtime | 6 runtime modules + registry + subprocess spawning |
@boringos/memory | MemoryProvider interface + hebbs.ai provider + null provider |
@boringos/drive | StorageBackend + DriveManager with file indexing + memory sync |
@boringos/db | Drizzle schema + embedded Postgres + migration manager |
@boringos/workflow | DAG workflow engine + 6 block handlers (incl. wake-agent, connector-action) |
@boringos/pipeline | QueueAdapter — in-process (default) or BullMQ |
@boringos/connector | Connector SDK — OAuth, events, actions, test harness |
@boringos/connector-slack | Slack — messages, threads, reactions |
@boringos/connector-google | Google Workspace — Gmail + Calendar |
@boringos/ui | Typed API client + headless React hooks |
@boringos/shared | Base types, constants, Hook utility |
create-boringos | CLI 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
| Method | Endpoint | Description |
|---|---|---|
| GET | /agents | List agents |
| POST | /agents | Create agent |
| GET | /agents/:id | Get agent |
| PATCH | /agents/:id | Update agent |
| POST | /agents/:id/wake | Wake agent |
| GET | /agents/:id/runs | Agent run history |
Tasks
| Method | Endpoint | Description |
|---|---|---|
| GET | /tasks | List tasks (filter by status, assignee) |
| POST | /tasks | Create task (auto-generates identifier) |
| GET | /tasks/:id | Get task + comments + work products |
| PATCH | /tasks/:id | Update task |
| DELETE | /tasks/:id | Delete task |
| POST | /tasks/:id/comments | Post comment |
| POST | /tasks/:id/assign | Assign to agent + optionally wake |
Runs
| Method | Endpoint | Description |
|---|---|---|
| GET | /runs | List runs (filter by agent, status) |
| GET | /runs/:id | Get run detail |
| POST | /runs/:id/cancel | Cancel run |
Runtimes
| Method | Endpoint | Description |
|---|---|---|
| GET | /runtimes | List runtimes |
| POST | /runtimes | Create runtime |
| PATCH | /runtimes/:id | Update runtime |
| DELETE | /runtimes/:id | Delete runtime |
| POST | /runtimes/:id/default | Set as default |
Approvals
| Method | Endpoint | Description |
|---|---|---|
| GET | /approvals | List pending approvals |
| GET | /approvals/:id | Get approval |
| POST | /approvals/:id/approve | Approve (with optional note) |
| POST | /approvals/:id/reject | Reject (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(supportsassigneeAgentIdORworkflowId),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:
- API key —
X-API-Key: your-admin-key+X-Tenant-Id: tenant-uuid - Session token —
Authorization: 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):
- CLI calls
POST /api/auth/device/code→ getsdeviceCode+userCode - User opens verification URL in browser, approves with the
userCode - CLI polls
POST /api/auth/device/pollwithdeviceCode→ 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
| Hook | Returns | Mutations |
|---|---|---|
useAgents() | agents list | createAgent, wakeAgent |
useTasks(filters?) | tasks list | createTask |
useTask(taskId) | task + comments | updateTask, postComment, assignTask, addWorkProduct |
useRuns(filters?) | runs list (polls 5s) | cancelRun |
useRuntimes() | runtimes list | createRuntime, setDefault |
useApprovals(status?) | approvals list | approve, reject |
useConnectors() | connectors list | invokeAction |
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();