Boring UI is an opinionated framework for building agent-centric apps, built on Pi.
Traditional SaaS is built around workflows users drive by hand: buttons, forms, pages, dashboards.
Agents change that.
When software can understand intent and act, every app collapses to two surfaces:
- Chat — tell the agent what to do.
- Workbench — inspect, steer, and refine the results.
That's what the Boring UI core provides: a shell the agent can control and reshape.
npx @hachej/boring-ui-cliStarts a full agent workspace pointed at the current directory — chat, file tree, panels, command palette. No clone. No database. No setup.
Set ANTHROPIC_API_KEY or OPENAI_API_KEY before running (see Pi providers for LLM setup).
Area.mp4
A real session: ask the agent for a summary → it opens the README in the workbench → ask it to take notes → a new notes.md appears in the tree → search for it via the command palette. Chat in, workbench out.
Of course every app, every workflow, every use case is different.
Different data. Different visualisations. Different agent skills.
That's why we created a plugin system.
Boring builds on Pi's plugin system for agent customization (prompt, skills, tools) and extends it with UI-aware surfaces.
Pi handles the agent loop (tool calling, sessions, skills, prompts).
Boring adds the workbench, panels, and commands on top.
The two halves are fully compatible — any Pi package works out of the box.
In practice, a plugin is a Node package with two manifest blocks:
pi.*— agent side: skills, prompts, tools (loaded by Pi)boring.*— UI side: panels, commands, catalogs, surface resolvers
Plugins compose at the app shell: register multiple packages/plugins side by side, or publish a package that wraps shared primitives. Swap a single surface, or add a brand-new pane type. Plugins ship through npm like any other dependency — no patching, no monorepo entanglement required.
For generated/runtime plugins, use boring-ui scaffold-plugin <name> from the workspace-local CLI. For app/internal publishable package plugins, use packages/cli/templates/plugin as a reference example. The exact manifest shape is in Plugin shape below.
MacroAnalyst — an AI analyst for macroeconomic research. Ask in plain English, get charts back in under a minute. 800,000+ economic series from FRED, BLS, BEA, and Treasury, all behind one chat and one workbench.
Production, paying customers, single codebase. Built on @hachej/boring-core + @hachej/boring-agent + @hachej/boring-workspace + custom domain plugins.
More on the same chassis in flight: boring-accountant, boring-design, boring-lawyer.
| Package | Role | README |
|---|---|---|
@hachej/boring-agent |
Agent runtime, tools, chat UI | packages/agent |
@hachej/boring-workspace |
Workbench, panels, plugin system | packages/workspace |
@hachej/boring-core |
Auth, DB, app factory | packages/core |
@hachej/boring-ui-kit |
Shared UI primitives | packages/ui |
@hachej/boring-ui-cli |
Zero-setup local entrypoint | packages/cli |
| Plugin | What it adds | README |
|---|---|---|
@hachej/boring-ask-user |
Agent-to-user question/answer surface and ask_user tool |
plugins/ask-user |
@hachej/boring-data-explorer |
Searchable, faceted data tables — the primitive for explorer-style panels | plugins/data-explorer |
@hachej/boring-data-catalog |
Configurable catalog tab built on data-explorer |
plugins/data-catalog |
| App/internal plugin template | Publishable package-plugin reference; generated plugins use boring-ui scaffold-plugin |
packages/cli/templates/plugin |
| App | Purpose | README |
|---|---|---|
apps/full-app |
Production-shaped reference: auth, DB, multi-workspace | apps/full-app |
apps/agent-playground |
@hachej/boring-agent alone — no workbench, no DB |
apps/agent-playground |
apps/workspace-playground |
@hachej/boring-workspace + plugins — no auth backend |
apps/workspace-playground |
Plugins are standard Node packages. package.json#pi describes hot-reloadable
agent resources, while package.json#boring describes workspace UI/static app
integration:
{
"name": "my-plugin",
"keywords": ["pi-package"],
"pi": {
"extensions": ["agent/index.ts"],
"skills": ["agent/skills"],
"prompts": ["agent/prompts"],
"systemPrompt": "Short agent guidance."
},
"boring": {
"label": "My Plugin",
"front": "front/index.tsx",
"server": "server/index.ts"
}
}pi.*— hot-reloadable agent resources loaded by Pi (extensions,skills,prompts,systemPrompt)boring.front— workbench UI fromdefinePlugin({ ... }): panels, commands, catalogs, surface resolvers, providers, bindingsboring.server— explicit static/boot-time server integration fromdefineServerPlugin({ ... }): agent tools that need backend state and HTTP routes. Restart the workspace server after changes.
Run boring-ui plugin create <name> for a publishable package plugin, or start from packages/cli/templates/plugin. For a front/Pi hot-reloadable local plugin, run boring-ui scaffold-plugin <name>.
| Plugin surface | Local .pi/extensions / CLI |
App/internal package plugins | Notes |
|---|---|---|---|
pi.systemPrompt, pi.skills, pi.prompts, pi.extensions |
hot-reload via /reload |
hot-reload when discovered as plugin package resources | Agent context updates without server restart. |
boring.front panels/commands/catalogs/surface resolvers |
hot-reload via /reload in dev/playground |
static by default; package front assets can be rediscovered in dev | Browser import failures are surfaced and previous version is kept. |
boring.server / defineServerPlugin({ routes, agentTools }) |
not hot-reloaded | boot-time only | Restart/redeploy after changes. Generated runtime plugins should omit boring.server. |
| Runtime plugin frontend in packaged CLI static mode | not yet | n/a | Planned: local plugin-dev transform endpoint / embedded Vite for CLI. |
Planned direction: keep app/internal plugins powerful and boot-composed, but keep generated/runtime plugins route-free. Generated plugins should use manifest-declared front surfaces plus brokered tools/RPC rather than custom backend routes.
What you can add:
- Panels — arbitrary React panes in the workbench (editors, charts, tables, anything)
- Left tabs — persistent sidebars (data catalogs, file navigators, status views)
- Commands — entries in the command palette, triggered by user or agent
- Catalogs — searchable, faceted data explorers the agent can surface
- Agent tools — new capabilities the model can call, with schema-defined parameters
- Skills + prompts — domain knowledge and reasoning patterns the agent follows
| Plugin | Description |
|---|---|
| ask-user | Agent-to-human Q&A with a UI prompt |
| data-explorer | Searchable, faceted data tables |
| data-catalog | Catalog tab built on data-explorer |
| coming: llm-wiki | LLM powered second brain |
| coming: tasks | Task tracking, Kanban boards the agent can read and update |
| coming: workflows | Multi-step agent orchestration — chain steps, define branches, trigger sub-agents |
| coming: data-branch | Fork, explore, and compare agent-generated datasets side by side in the workbench |
See Pi extensions docs for the full Pi plugin surface.
MacroAnalyst — an interactive macroeconomic analyst powered by Boring UI.
Ask in plain English, get charts back in under a minute. Behind the scenes the agent:
- Fetches live time series from a database of 800,000+ series
- Transforms, resamples, and joins them using Python functions it chooses and writes
- Renders interactive decks charts in the workbench
| App | Status |
|---|---|
| boring-accountant — accounting workflows | Coming |
| boring-design — design review and iteration | Coming |
| boring-lawyer — legal research and document review | Coming |
The repo is structured in packages, each with a focused scope.
| Package | Role | README |
|---|---|---|
@hachej/boring-agent |
Agent runtime, tools, chat UI | packages/agent |
@hachej/boring-workspace |
Workbench, panels, plugin system | packages/workspace |
@hachej/boring-core |
Auth, DB, app factory | packages/core |
@hachej/boring-ui-kit |
Shared UI primitives | packages/ui |
@hachej/boring-ui-cli |
Zero-setup local entrypoint | packages/cli |
The repo also ships reference apps — drop one into any agent and it will scaffold a custom app for you in seconds.
| App | Purpose | README |
|---|---|---|
apps/full-app |
Production-shaped reference: auth, DB, multi-workspace | apps/full-app |
apps/agent-playground |
@hachej/boring-agent — single agent chat on top of Pi |
apps/agent-playground |
apps/workspace-playground |
@hachej/boring-workspace — agent chat and the workbench |
apps/workspace-playground |
Two layers connected by a bridge.
Frontend is React + Vite — renders chat, file tree, and workbench. The workbench is a pane container that displays files, tables, charts, or custom plugin views.
UiBridge is the link between frontend and backend. The agent or server posts commands (openFile, openPanel, openSurface) and the workbench dispatches them. This is how the agent drives the UI without touching the DOM.
Backend is Node.js.
Agent runtime is the Pi agent loop (AgentHarness). It runs natively on the backend — no VMs, no containers needed. It receives user messages, streams chat responses, delegates tool calls to a ToolCatalog, and manages sessions. It knows nothing about files, shells, or UI — only AgentTool[].
AgentHarness is an interface, not a hardcoded dependency. The design leaves room for swapping in a different harness later. For now, Pi is the only implementation.
The agent just calls the tools — we handle where they actually run.
That's why Workspace and Sandbox exist: they abstract the execution layer so the same tools (ls, read, write, exec) work identically whether hitting the local filesystem, a Linux container, or a remote VM.
| Interface | Defined in | Used for | Adapters |
|---|---|---|---|
Workspace |
@boring/agent/shared |
Read/write files (agent + UI filetree) | NodeWorkspace, VercelSandboxWorkspace |
Sandbox |
@boring/agent/shared |
Shell execution (agent commands) | DirectSandbox, BwrapSandbox, VercelSandboxExec |
UiBridge |
@boring/workspace/shared |
Workbench control (agent + command palette) | in-memory bridge (room for browser-side adapter) |
AgentHarness |
@boring/agent/shared |
Agent loop | Pi (room for more) |
Sandbox abstracts isolated execution. The agent runs commands through it — the same bash tool works identically regardless of where the shell is:
| Sandbox | Implementation | When to use |
|---|---|---|
| direct | child_process.exec |
Local dev, no isolation |
| bwrap | Linux bubblewrap | Local dev with filesystem isolation |
| vercel-sandbox | Vercel Firecracker VM | Remote sandbox |
Workspace is the filesystem abstraction that both the agent tools and the frontend file routes consume. It defines operations — readFile, writeFile, readdir, stat, watch — and each adapter implements them for its target environment.
Pi ships native tools for read, write, edit, find, grep, ls. In local mode they call node:fs directly. In remote mode we adapt them to call through the Workspace interface over HTTP. Same tools, same agent, different backend.
| Workspace | Implementation |
|---|---|
| NodeWorkspace | Local filesystem via node:fs |
| VercelSandboxWorkspace | Remote filesystem over HTTP to a sandbox VM |
Sandbox and Workspace are always created together as a pair so they share the same filesystem:
| Mode | Sandbox | Workspace |
|---|---|---|
| direct | DirectSandbox |
NodeWorkspace |
| local | BwrapSandbox |
NodeWorkspace |
| vercel-sandbox | VercelSandboxExec |
VercelSandboxWorkspace |
The full app ships two deployment targets:
- Fly.io — Docker container + Postgres. The
apps/full-appDockerfile builds the monorepo in dependency order. Runfly launch, set secrets (DATABASE_URL,AUTH_SECRET), deploy. - Vercel — serverless function for agent routes + edge static assets.
@boring/coreships avercelEntryand build script (build-vercel-api.mjs) that bundle the backend into a single Vercel Function.
Both targets use the same @boring/core app factory (createCoreApp) — swap the entry point, same app.
pnpm install
pnpm build # build all packages
pnpm dev # run all dev servers
pnpm typecheck # tsc --noEmit across all packages
pnpm test # vitest across all packages
pnpm lint:invariants # plugin contract + agent isolation lint
pnpm ci # lint + typecheck + test + invariants + e2eScoped commands during development:
pnpm --filter @hachej/boring-workspace test
pnpm --filter @hachej/boring-agent test:watch
pnpm --filter full-app devApps that consume @hachej/boring-workspace source need the workspace built once first:
pnpm --filter @hachej/boring-workspace build && pnpm --filter workspace-playground testMIT


