Put an AI agent inside your app.
Chat, files, document viewers, approvals and background work for React and Node apps, built on the Pi agent harness. You keep authentication, data, permissions and model keys.
// Browser: a whole agent app (sessions, chat, artifact viewers) from one registry block.
import { AgentWorkspace, useConversations, useRemoteChat } from '@/components/pi-app/agent-workspace';
import { defaultKinds } from '@/components/pi-app-kinds/pi-app-kinds';
const kinds = defaultKinds(); // the viewers it draws: Markdown, HTML, PDF, images, mock-ups, diagrams…
const conversations = useConversations({ endpoint: '/api/conversations', fetch, activeId, onSelect });
const chat = useRemoteChat({ conversationId: activeId, endpoint: id => `/api/chat?conversation=${id}`, fetch, identity });
<AgentWorkspace artifactKinds={kinds} conversationId={activeId} conversations={conversations} revisionProvider={revisionProvider}
controller={chat.status === 'ready' ? chat.controller : undefined} />;// Server: your process opens the Pi Harness and the workspace; the kit adds an agent definition and HTTP handlers.
const agent = defineAgent({ id: 'writer', model: { provider: 'openai', modelId: 'gpt-5-mini' },
tools: [createPresentTool({ workspace: { files, root, access } })] });
agent.install(registry);
const harness = await Harness.open(storage, { registry, models, env: () => env }, context);
const chatHandler = createChatTransportHandler({ authenticate }); // your auth decides who opens which conversationNode.js 22.19 or later. The scripted model needs no API key.
git clone https://github.com/hachej/boring-ui && cd boring-ui
npm ci && npm run build
STUDIO_MODEL=scripted npm run workspace-appOpen the printed URL and click Draft a plan. The agent writes a fictional picnic plan and opens it in the side panel. Drop STUDIO_MODEL=scripted and set OPENAI_API_KEY to use a real model.
- The agent lives in your app. A side panel, a floating bar, a bottom drawer on phones, or the whole page. You pick the placement.
- You own the server. Your process opens the Pi Harness. The kit never holds your keys, your users or your policy.
- One place for files. The agent's
read/write/edit, the viewers and the file tree all go through one workspace provider, with revisions and conflict checks. - Copy the UI, keep it updatable. Blocks install through the shadcn CLI into your code, use your shadcn theme, and update with one command.
- Durable by default. Conversations, questions, approvals and schedules survive a restart, because Pi stores them.
npm i @hachej/boring-ui-kit @hachej/boring-agent @hachej/boring-files \
@earendil-works/pi-durable@~1.4.0 @earendil-works/pi-ai@~1.4.0 @earendil-works/chord@~1.4.0Then add UI blocks to a React app that has shadcn set up (components.json, Base UI base-nova style). Blocks import the app's shadcn components through its ui alias and use its tokens, so the app installs those components once, first (what the app needs):
npx shadcn@4.23.0 add alert attachment avatar badge bubble button card checkbox collapsible dialog drawer dropdown-menu empty input input-group item label message message-scroller native-select popover progress scroll-area separator sidebar spinner switch table textarea toggle toggle-group tooltip trace
npx boring-ui-blocks add https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.8/r/pi-app.jsonFrom the release after 0.4.23, also add .../r/pi-app-kinds.json and pass its defaultKinds() as artifactKinds, as above: pi-app draws only the kinds it is given. Add .../r/theme.json once if your app has no shadcn tokens yet. The full walk-through is the build-agent-app skill.
PiChat is the conversation: queue, steer, stop, attachments, @ file mentions, voice messages, question and approval cards. AmbientChat floats the same chat over an existing site. AppShell puts one agent above any app, as a drawer on phones and a panel on wide screens.
import { AmbientChat } from '@/components/pi-ambient/ambient';
<AmbientChat controller={controller} actions={actions} labels={{ title: 'Console assistant' }} />See the block list.
The agent writes a file and calls present. The page opens it in a viewer: Markdown (editable), HTML, PDF, images, code, mock-ups, diagrams, typed records and boards.
import { createPresentTool } from '@hachej/boring-agent/artifacts';
defineAgent({ id: 'writer', model, tools: [createPresentTool({ workspace: { files, root, access } })] });See Whole agent app and Artifacts.
createWorkspaceProvider puts revisions, conditional writes and a change feed over a Pi FileSystem (disk, virtual or SQLite). createFileGuard routes Pi's own file tools through it, so an agent edit never overwrites a save the person just made.
import { createWorkspaceProvider } from '@hachej/boring-files/workspace';
import { createFileGuard } from '@hachej/boring-agent/file-guard';
const files = createWorkspaceProvider({ identity, fs: env, journal: createWorkspaceJournal(db) });
const guard = createFileGuard({ workspace: { files, root, access } });The browser reaches it through one route, createRevisionHandler: reads, saves, the file list, @ search and uploads (a conditional create with a receipt, binary-safe, up to maxFileBytes) all use the same resolve and access. See @hachej/boring-files and One file tool set.
Gate any tool behind an Approve/Deny card, let the agent ask the person a question, hand work to a sub-agent, or schedule a follow-up. Each one is a native Pi tool or extension.
import { createApprovalExtension } from '@hachej/boring-agent/approval';
const approvals = createApprovalExtension({ tools: { write: true, edit: true } });
defineAgent({ id: 'writer', model, extensions: [fileTools, approvals] }); // Approve/Deny before every write or editSee Approval gate, Asking the person and Sub-agents and schedules.
A virtual Bash and Git workspace in memory or SQLite, or a remote shell and file system you already run. The agent's tools see one view of the files either way.
People point at what they see, dictate a note and send it. Works with or without an agent.
| Package | What it gives you |
|---|---|
@hachej/boring-ui-kit |
Headless controllers, editors and viewers, the shadcn registry (r/) and the boring-ui-blocks command |
@hachej/boring-agent |
Pi agent pieces: defineAgent, chat transport, conversations, present, approvals, questions, schedules, memory |
@hachej/boring-files |
The workspace provider: revisions, conditional writes, history, remote access |
@hachej/boring-execution |
Virtual Bash and Git, remote shell and file system adapters |
@hachej/boring-feedback |
In-app feedback capture and delivery |
Two more packages stay private to this repository: boring-browser (the agent in a browser worker) and boring-testing (a scripted keyless model and a headless browser driver).
Every example uses fictional data. Run it with npm run <name> after npm run build; npm run <name>:journey drives it in a real browser.
| Example | What it shows |
|---|---|
workspace-app |
The smallest whole agent app: sessions, chat, artifact panel, about 120 lines of server |
ambient |
An agent floating over an existing settings console |
app-shell |
A phone-first app with one agent in a bottom drawer |
dock-app |
Chat and files in resizable docks beside a host page |
shared-chat |
A team conversation with several people and agents |
bot |
One lifelong conversation with memory and a self it can edit |
browser-agent |
A coding agent that runs entirely in one browser tab |
hosts |
The same agent on Cloudflare Durable Objects and AWS |
workspace-demo |
A fictional accounts-payable week with approvals |
The studio is a playground over every block (npm run studio).
This repository ships Agent Skills that teach Claude Code, Codex and other coding agents to build with the kit:
npx skills add hachej/boring-ui| Skill | Use it to |
|---|---|
boring-ui |
Learn the kit, its rules, and which skill to use next |
build-agent-app |
Scaffold an app with an agent inside, or add one to an existing app |
add-blocks |
Find, install, update and theme registry blocks |
agent-capabilities |
Give the in-app agent tools, files, approvals, questions, sub-agents and schedules |
| Topic | Start here |
|---|---|
| UI blocks and their props | Registry guide |
| Upgrading | Migration guide |
| What is implemented, with tests | Feature map |
| How the kit works with Pi | Pi integration and invariants |
| Files, Git and execution | Workspace architecture |
| An agent beside an existing website | Website integration |
| Agents that change their own tools | Self-evolution |
| Product scope | Requirements and roadmap |
| Releasing | npm release guide |
Apps built by the Boring Factory start from its app template and ship through the Factory; this repository provides the packages and blocks they consume.
Read AGENTS.md and INVARIANTS.md first. Use fictional data only.
npm ci
npm run check # docs links, package boundaries, registry blocks, design rules
npm run typecheck
npm test
npm run verify # all of the above plus invariants and design tests
npm run check:pack # audits the real package tarballsverify lists deferred proofs; they are backlog, not release blockers. Browser journeys and isolated consumer checks run separately in CI. Keep raw local evidence in .cache/evidence/. A package change adds its note to MIGRATING.md under Unreleased. Merging a PR does not publish packages; see the release guide.
Release qualification is tracked in the implementation checkpoint and VERIFY.json. Passing one test does not qualify every provider or deployment.
MIT. Optional dependencies keep their own licenses. Qualify tldraw licensing and assets for your deployment; see the canvas guide.