Agent scaffold · built on the pi-agent SDK

π-starter

Clone it and it runs. Add tools, extensions, skills, knowledge, or a new persona — and it becomes a vertical agent.

release v0.4.2 node >=22.19 license MIT upstream pi-coding-agent 0.83.0
505test cases
53test files
84backend source files
47HTTP route handlers

Repo: github.com/LPK3215/pi-starter · Upstream SDK: earendil-works/pi · Mirrored on CNB, where the gates run · Shareable one-page image: project card

What it solves

Positioning, value, and how it differs

A runnable skeleton, not a demo

Wraps the SDK's createAgentSession into a single buildAgent(), then layers HTTP+SSE, WebSocket, an in-process CLI, and the official RPC stdio JSONL transport on the same core.

Vertical-agent friendly

Business logic enters through injection params (extraTools, extraSkillPaths, extraKnowledgeDirs, extraExtensions). User-level ~/.pi scanning stays off so nothing leaks into a deployed agent.

Safety is a first-class axis

Three built-in-tool tiers (off / readonly / coding), fail-closed path guard, read-only SQL, and an approval gate whose deny rules cannot be overridden and whose timeout closes the turn.

Single-process by design: REST holds one conversation per buildAgent(); WebSocket keeps up to 8 open conversations per client and evicts the least recently used. There is no authentication by default, so the server binds 127.0.0.1 and warns at startup when the bind address is widened. Multi-user and public exposure are out of scope (see Security).

Architecture panorama

Generated from source — scripts/visualization/generate_architecture.mjs

pi-starter layered architecture

Transports

CLI, HTTP+SSE, WebSocket, library, official RPC stdio — one assembly core behind all of them.

Session stack

SessionHub → ClientSession → Conversation → SnapshotEmitter → ws: state is a snapshot with a rev chain, delivered through per-connection queues.

Assembly

model + prompts + tools + skills + knowledge(+retrieval) + memory + database + extensions → AgentSession, with scopedModels cycling.

Interactive architecture views

Self-contained, click-to-explore pages generated by the archify skill — pan / zoom / search / focus / guided views. They complement the CI-checked static docs/*.svg above, which stay the gated source of truth (npm run docs:svg:check).

Pluggable knowledge retrieval (RAG)

The SDK ships no RAG — it prescribes "register your own searchable tool". Generated by generate_retrieval.mjs.

Pluggable knowledge retrieval pipeline

Two entry points, one source

search_knowledge (SDK tool) and GET /knowledge/search share the same Retriever — no second implementation to drift.

Default & opt-in

KeywordRetriever is zero-dependency by default. PI_KNOWLEDGE_RETRIEVAL=vector switches to VectorRetriever (chunk → embed → cosine → aggregate by doc), with pluggable EmbeddingProvider + VectorStore.

Knowledge is a document set; memory is a fact store. src/memory/store.ts keeps cross-session facts behind remember / recall tools and GET|POST|DELETE /memory, written atomically with owner-only file modes; PI_MEMORY=off turns it (and its routes) off.

Request → SSE lifecycle

Generated by generate_request_flow.mjs

POST /chat SSE lifecycle

Tech stack

Every version below is read from package.json / web/package.json at generation time

LayerChoiceVersion
RuntimeNode.js (built-in node:sqlite, built-in test runner)>=22.19
LanguageTypeScript, strict ESM^5.6.0
Agent SDK@earendil-works/pi-agent-core · pi-ai · pi-coding-agent0.83.0 (pinned)
HTTPExpress^5.2.1
SchemaTypeBox^1.1.39
WebSocketws^8.18.0
Embeddings (optional)@huggingface/transformers^4.3.1
Frontend (in web/)React + Vite + assistant-ui + Tailwindreact ^19.2.8 · vite ^8.3.0 · assistant-ui ^0.15.25
Teststsx --test — offline contract tests, no real model call53 files · 505 cases (+19 frontend)

Capabilities

Everything wired into the core

Layered prompts (persona + rules)
Pluggable tools + registry
Per-tool enable/disable at runtime
Skills (SKILL.md)
Knowledge base + pluggable RAG
Cross-session memory (remember / recall)
Prompt templates (/name)
Multi-model + runtime switch
Model cycling (scopedModels)
Provider auth status + key store
Auto + manual compaction
Session tree (branch/edit/rollback/labels)
Session import
Steer / follow-up
Image input
Extension hooks
Custom providers
Process execution (foreground / background)
Approval gate + watchdog
Ask-the-user (human in the loop)
Read-only SQLite tools
Web search / fetch
MCP (stdio) bridge
Subagents (delegate)
Plan mode (read-only)
Files service (read / write / upload)
SSE + JSONL event streams
Snapshot-driven WebSocket
Official RPC mode
Structured logs + /logs query
Runtime settings (PATCH /settings)
AGENTS.md opt-in
Dockerfile sandbox

Project structure

Directory list is checked against the filesystem by npm run docs:overview:check

src/ — all TypeScript source
approval/
policy.ts · rules.ts · gate.ts · watchdog.ts — tool-permission approval; a deny rule cannot be disabled, and a timeout or dispose resolves to deny
context/
budget.ts — context-window budgeting / auto-compaction (official SDK)
conversation/
conversation.ts · messages.ts — the one turn engine behind CLI, REST, WS and RPC; event → snapshot projection, steer, follow-up, abort
db/
index.ts — node:sqlite, zero-dep, in-memory by default, read-only SQL enforced
exec/
runner.ts — non-interactive process execution: foreground/background, timeout, kill process tree
extensions/
index.ts · guard.ts · audit.ts · shell-rules.ts · example-command.ts · input-resources.example.ts · provider-hooks.example.ts · custom-provider.example.ts · sandbox.example.ts · tool-result-redaction.example.ts
files/
service.ts — safe workspace file read/write with fail-closed path checks and an audit trail
http/
routes.ts · file-routes.ts · approval-routes.ts · provider-key-routes.ts · log-routes.ts · errors.ts · hardening.ts · rate-limit.ts · request-context.ts
knowledge/
index.ts · retrieval.ts · embeddings.ts · embeddings-transformers.ts · vector-store-sqlite.ts · about.md — pluggable RAG
mcp/
client.ts · bridge.ts — stdio MCP server, tool discovery, call forwarding
memory/
store.ts — cross-session facts: atomic write, owner-only modes, PI_MEMORY=off disables it
modes/
plan-mode.ts — read-only planning via the official tool whitelist
prompt-templates/
index.ts · review.md — slash-command templates via SDK additionalPromptTemplatePaths
prompts/
composer.ts · persona.md · rules.md — layered system prompt
sessions/
store.ts · edit.ts — session tree, entry editing, resume guards
skills/
index.ts · summarize/SKILL.md — SDK additionalSkillPaths
subagents/
index.ts — delegate a task to an independent sub-agent session
tools/
registry.ts · index.ts · knowledge.ts · memory.ts · database.ts · exec.ts · web.ts · ask-user-question.ts · current-time.ts
transport/
ws.ts — snapshot-driven WebSocket with per-connection queues and rev ordering
Top-level, non-test (30): agent.ts · app.ts · capabilities.ts · child-env.ts · cli-args.ts · client-session.ts · config.ts · errors.ts · extension-ui-bridge.ts · graceful.ts · index.ts · lib.ts · log-sink-file.ts · log.ts · metrics.ts · models.ts · prompt-images.ts · protocol.ts · provider-keys.ts · rpc.ts · sdk-adapter.ts · secret-files.ts · server.ts · session-hub.ts · settings.ts · setup.ts · snapshot.ts · sse.ts · test-server.ts (test helper, not in dist) · test-tmp.ts (test helper, not in dist)
docs/ — 17 entries, generated and hand-written
.nojekyll · architecture.svg · assistant-ui.md · engineering-review.md · index.html · knowledge-retrieval.svg · sse-protocol.svg · 使用指南.md · 前端调研.md · 参考手册.md · 官方SDK接口文档.md · 嵌入指南.md · 智能体视角评估.md · 能力与边界.md · 项目分析报告.md · archify/ · project_overview/
scripts/ — generators, gates and smoke runners
coverage-thresholds.mjs · coverage.mjs · dist-assets.cjs · e2e-restart.mjs · npm-invocation.mjs · probe-providers.mjs · publish-local.mjs · rag-smoke.mjs · release.mjs · smoke-ws.mjs · verify-audit.mjs · verify-embed.mjs · verify.mjs · visualization/
web/
Product frontend — its own package.json (Vite + React + assistant-ui). src/pi/client.ts is the WebSocket client over the snapshot protocol and src/pi/usePiRuntime.ts the ExternalStore adapter; both share src/protocol.ts types with the backend. Built to web/dist and served at / by createApp().
.github/workflows/ + .cnb.yml
Two definitions of the same gate list. CNB is the one that actually runs for this account; the GitHub workflow is kept in sync so a fork gets the same checks.
Root: .cnb.yml · .dockerignore · .env.example · .gitattributes · .gitignore · AUTHORS · CHANGELOG.md · CODE_OF_CONDUCT.md · CONTRIBUTING.md · Dockerfile · FAQ.md · LICENSE · README.md · README.zh-CN.md · SECURITY.md · diagram-input.md · package.json · pipeline.config.json · tsconfig.build.json · tsconfig.json · hooks/ — tracked files only (git ls-files), so local scratch directories never reach the page.

Quickstart

Pick a mode

npm install
cp .env.example .env      # fill PI_API_KEY / PI_MODEL
npm run setup             # merge credentials into ~/.pi/agent/
npm run dev               # interactive chat;  /models  /model  /cycle
npm run web               # serves the built frontend + API on 127.0.0.1:3000
curl http://localhost:3000/health/ready
curl http://localhost:3000/capabilities
curl "http://localhost:3000/knowledge/search?q=model"
npm run dev -- --mode rpc # official SDK runRpcMode over stdio JSONL
# headless; shares the same resourceLoaderOptions as buildAgent()
npm run build             # → dist/ (test helpers are excluded)
import { buildAgent, createApp } from "pi-starter";
const agent = await buildAgent({
  builtinTools: "off",
  extraTools: [myTool],
  extraKnowledgeDirs: ["/path/to/docs"],
  knowledgeRetrieval: "vector",   // opt-in RAG
});
const { app, dispose } = createApp({ agent, staticDir: false });
docker build -t pi-starter .
docker run --rm -p 127.0.0.1:3000:3000 \
  -v "$HOME/.pi/agent:/home/pi/.pi/agent:ro" pi-starter
# publish to loopback only — the service has no auth

HTTP API

47 route handlers, grouped by what they are for. This table is generated from the route registrations themselves — a route that matches no group fails the build, so it cannot go stale.

Method · pathPurpose
Health & introspection
GET /healthLiveness: reports the running mode and the bound model.
GET /health/readyReadiness: each dependency is checked separately and reported as it is, so a stale claim cannot pass as ready.
GET /metricsCumulative counters and gauges (turns, tokens, pending approvals); ?format=prometheus for the exposition format.
GET /infoCapability snapshot of the live session: model, tools, skills, knowledge, limits.
Chat, models & context
POST /chatStream one prompt: SSE by default, ?format=jsonl for the raw event stream.
POST /modelSwitch the model on the shared session.
POST /model/cycleRotate to the next model in scopedModels.
GET /providersConfigured providers with their checkAuth result.
POST /context/compactCompact the context now; auto-compaction runs on its own budget.
Resources: skills, knowledge, templates
GET /skillsRegistered skills (name + description).
GET /skills/:nameOne skill's full SKILL.md.
GET /knowledgeKnowledge documents in scope.
GET /knowledge/:nameOne document, verbatim.
GET /knowledge/searchSearch through the same Retriever the search_knowledge tool uses — there is no second implementation to drift.
GET /prompt-templatesTemplates available as /name.
GET /prompt-templates/:nameOne template body.
Database (read-only)
GET /dbTables and row counts.
GET /db/notesRows of the seeded notes table.
GET /db/notes/:idOne row by id.
POST /db/queryRun SQL. SELECT only — writes are rejected before the statement reaches sqlite.
Cross-session memory — Mounted only when memory is on (PI_MEMORY=off removes these routes).
GET POST /memoryList the stored facts, or write one.
DELETE /memory/:idDelete one fact by id.
Workspace files — Mounted only when a FileService is wired; every path goes through the fail-closed guard and is audited.
GET /files/listDirectory listing.
GET /files/readFile as text (binary extensions are refused).
GET /files/rawFile as bytes, for download.
POST /files/writeReplace a file's contents; creates parent directories, enforces a size cap.
POST /files/createCreate a file — conflicts if it already exists.
POST /files/renameRename / move inside the workspace; target must not exist.
POST /files/copyCopy inside the workspace.
POST /files/deleteDelete a file, or a subtree when recursive is set.
POST /files/uploadUpload a file into the workspace.
Approval rules — Mounted only when a rules store is wired.
GET POST PUT /approval/rulesList the effective rules (user first, built-in flagged), replace the whole user set, or add one. Built-in rules are read-only and every rule is re-validated on the way in — the same validator that guards the on-disk file.
DELETE /approval/rules/:idDelete one user rule.
Provider keys — Mounted only when a key store is wired; stored secrets are never echoed back.
GET POST /provider-keysList providers with their key names and which is active, or store a key. The first key becomes active and is applied to the live session.
POST /provider-keys/activateActivate a stored key through the same path as a model switch, so no conversation keeps using the old one.
POST /provider-keys/removeRemove a stored key.
Runtime settings, tools & sessions — Mounted only when the tool registry / settings service is wired.
GET PATCH /settingsRead, or merge a partial update into the runtime settings (persisted; unknown fields are rejected).
POST /tools/:name/enabledEnable or disable a tool for the running session.
GET /capabilitiesThe capability matrix — the same object the WebSocket capabilities frame sends.
POST /sessions/importImport an existing session file and resume from it.
Structured logs — Mounted only when a log directory is configured.
GET /logsQuery entries by level, module, request id or keyword, with cursor paging over the daily files.
GET /logs/statsCounts per level and module for the same selection.
WebSocket: GET /ws?session= — one connection, many conversations. Client commands and server frames (including approval round-trips, which have no REST route by design) are enumerated in docs/参考手册.md.

Testing & quality

Offline contract tests, never call a real model

505

backend cases in 53 files · 19 frontend cases

23

runtime WebSocket smoke checks against a real server

48

e2e assertions in scripts/e2e-restart.mjs: real process restart and session resume, no API key

9 gates in npm run verify: typecheck · lint:unused · test · test:web · docs:check · verify:audit · smoke · build · verify:embed . npm run verify:all adds the real-process e2e. Coverage is a ratchet (CI only): lines ≥ 92 / branches ≥ 81 / functions ≥ 85 over src/**.

Doubles for contracts, real processes for behaviour

Route handlers, approval decisions and the SSE translator run against an in-process SDK session double — never a stubbed answer to the question under test. The smoke and e2e layers spawn the real server.

Docs are code

The README metric tables, docs/参考手册.md, the three SVGs and this page are generated and re-checked in CI (npm run docs:check).

Platform honesty

Windows skips POSIX-only permission and symlink cases with a printed reason instead of passing them silently; a skip is reported, never counted as green.

Documentation index

Every docs/*.md on disk is listed here — the generator fails if one is missing

License & authors

MIT License

Copyright (c) 2026 LPK3215. Full text: LICENSE.

Authors

LPK3215 <17538703215@163.com> — creator and primary maintainer. See AUTHORS and the contributors graph.

Cite as: LPK3215/pi-starter — an agent scaffold on the @earendil-works/pi-coding-agent SDK, v0.4.2.