English | 中文
HTTP facade that exposes Pi coding agent sessions behind the frozen DAC apiproxy contract, so a DAC manager can run and operate Pi nodes exactly like DSH nodes.
Status: v0.1.0 — accepted 2026-10-03. Verified end to end against a
production DAC manager with real deepseek-flash rounds: streaming chat,
tool calls, history replay, per-run billing (including continued runs), and
per-run git audit commits. Contract details:
docs/contract-map.md · operations:
docs/runbook.md · release history:
CHANGELOG.md.
DAC manager ──(frozen apiproxy contract, HTTP/WS)──> pi-api-facade (this repo)
└── Pi SDK in-process
└── AgentSession × N
- Northbound: the same wire contract
ohdsh-api-facade(dsh-api-gateway) froze for the manager — API-key auth, fail-closed method whitelist, client-request/server-response envelopes,events.muxWebSocket, respond receipts, per-session sandbox mode. - Southbound: the Pi TypeScript SDK (
createAgentSessionfrom@earendil-works/pi-coding-agent, version pinned by the DAC version matrix). One facade process hosts all sessions of one node concurrently.
cp .env.example .env # fill DEEPSEEK_API_KEY + PI_FACADE_API_KEYS (openssl rand -hex 16)
mkdir -p workspaces
docker compose up -d --build
node scripts/compose-smoke.mjs # wiring check (needs FACADE_URL/FACADE_KEY env; see script header)The nginx front door on HTTP_PORT (default 8090, deliberately clear of the
DAC production surface) is the only external port; it proxies /api-gw/ and 404s
everything else. The facade port is not published. Session workspaces live under
./workspaces (clients pass cwd as /workspace/<name>); the node's Pi home is
the agent-home volume. Full walkthrough: docs/runbook.md.
examples/demos/compose.demos.yml adds two web demos behind the same nginx front
door — KB Studio (/kb/, knowledge-base management with an AI steward pinned
to workspace-write) and a Support widget (/cs/, read-only agent grounded
in the same knowledge base, which is this repo's own docs). The demo apps are
single-sourced in
ohdsh-api-facade (the
contract's reference implementation); the overlay only re-points them at the Pi
facade and swaps in pi personas/doc seeds.
git clone https://github.com/litestartup-com/dsh-api-gateway ../dsh-api-gateway # or set GATEWAY_DIR in .env
node scripts/sync-demo-docs.mjs
mkdir -p workspaces/kb workspaces/cs
docker compose -f docker-compose.yml -f examples/demos/compose.demos.yml up -d --build
DEMO_BASE=http://127.0.0.1:8090 node scripts/demo-smoke.mjsPi-runtime notes vs the DSH stack: question cards ride the ask_user custom
tool (the model decides when to ask — cards render in both demos and in the
manager); approval cards fire only in the danger tier, so the KB demo's
auto-approve policy is a no-op here (the sandbox pin is the real guard).
npm install
npm startRequires Node.js >= 22.19.0 (pure ESM; the Pi SDK constraint).
All configuration is environment-driven (no config files, no secrets on disk):
| Variable | Default | Purpose |
|---|---|---|
PI_FACADE_HOST |
127.0.0.1 |
Bind interface. Exposing beyond loopback is an ops decision. |
PI_FACADE_PORT |
3091 |
HTTP port. |
PI_FACADE_PREFIX |
/api-gw/v1 |
Route prefix (matches the frozen contract). |
PI_FACADE_API_KEYS |
— | Comma-separated API keys for the northbound surface. Empty = every authenticated call is rejected (fail closed). |
PI_FACADE_ENGINE |
— | sdk boots the production Pi SDK engine (in-process, multi-session). fake boots the in-memory development engine (loud warning). Unset refuses to boot. |
PI_FACADE_MODEL |
deepseek/deepseek-flash |
Default model for new sessions, as provider/model. |
PI_AGENT_DIR |
~/.pi/agent |
Pi agent home for this node (auth/models/settings/sessions). One directory per node. |
PI_FACADE_ALLOW_FULL_ACCESS |
false |
Unlock the danger-full-access sandbox tier on this node. |
DEEPSEEK_API_KEY |
— | Provider credential, read by Pi at runtime. Never stored by the facade. |
OPENAI_API_KEY |
— | Native Pi credential for the built-in openai provider. |
PI_OPENAI_BASE_URL |
— | Point the node at any OpenAI-compatible endpoint (OpenAI, OneAPI/new-api proxies, vLLM, SGLang, Ollama, ...). At boot the facade synthesizes a models.json provider entry (api: openai-completions); select it with PI_FACADE_MODEL=<provider>/<model-id>. |
PI_OPENAI_API_KEY |
— | Credential for that endpoint, referenced as $PI_OPENAI_API_KEY env interpolation — it never lands on disk. Omit for key-less endpoints (Ollama). |
PI_OPENAI_PROVIDER |
openai |
Provider id to synthesize. The default overrides the built-in openai endpoint and keeps its catalog model ids; a custom id requires PI_OPENAI_MODELS. |
PI_OPENAI_MODELS |
— | Comma-separated model ids the catalog lacks (e.g. vLLM-served aliases); added/replaced on the synthesized provider. |
Shared vocabulary with dsh-api-gateway: every
PI_OPENAI_*variable above also accepts the neutral name —OPENAI_BASE_URL,OPENAI_API_KEY,OPENAI_PROVIDER,OPENAI_MODELS(plusOPENAI_MODELas the single-model shorthand) — andPI_FACADE_MODELacceptsFACADE_MODEL. These are the same names the sibling dsh-api-gateway standalone stack uses, so one operator — or one DAC manager wiring layer — feeds a Pi node and a DSH node the same model configuration. On conflict thePI_*name wins. One semantic note: herePI_OPENAI_MODELSadds to the provider's catalog, where the DSH stack's list replaces it — against a custom gateway you list what it serves either way.
npm run check # eslint + tsc --noEmit + node --testThe whole test suite is offline: unit tests drive a fake engine, integration
tests drive the real Pi SDK against a scripted local stub provider (no keys,
no external calls). CI: .github/workflows/ci.yml.
Discipline (see AGENTS.md): tests first; npm run check green before every
commit; dependencies pinned to exact versions; commit messages in English.
- docs/runbook.md — first boot, manager wiring (including the mandatory billing pin), smoke verification, restart/upgrade/rollback, troubleshooting.
- openapi.yaml — the northbound surface as an OpenAPI 3 document (the mux WebSocket is annotated in the info description).
docker/+docker-compose.yml— the standalone stack (thin runtime image; nginx front door;PI_FACADE_ENGINE=fakemode for wiring tests).scripts/start-facade.ps1— Windows launcher (per-node agent home, project trust seeding, key handling).deploy/pi-api-facade.service.example— systemd unit for Linux nodes.scripts/smoke-facade.mjs— wire-level smoke against a running facade with a real provider (spends tokens; manual only).scripts/compose-smoke.mjs— fake-engine wiring smoke for the compose stack (no tokens, CI-safe).- docs/contract-map.md — the frozen contract, endpoint by endpoint, cited against the manager's consumer code.
- The cross-implementation conformance kit lives in the gateway repo
(
conformance/, pinned by commit in CI): one suite both facades must pass — red there means the runtimes drifted apart on the frozen contract.
MIT — see LICENSE.