A lightweight control plane for coding agents in isolated workspaces.
The development pocketcoder executable embeds PGlite, its seed and migrations.
It needs no Bun install or source checkout. With Docker running, pocketcoder serve starts at 127.0.0.1:8090 and keeps private state in ./pc_data.
Follow the commit-pinned download and workspace round trip.
See candidate installation for all three targets and Node package consumers.
CI measures startup, peak memory and executable size. Development builds are
Actions artifacts named by full commit; they do not publish stable releases or
change latest.
- a template is a reviewed, versioned definition of a coding-agent environment;
- a workspace is one isolated instance of a template and the lifecycle record for a single coding-agent session.
Documentation lives in docs/ — start with getting started, then the CLI reference, Node SDK, templates, HTTP API, deployment, and architecture.
PocketCoder owns requests, session messages, replay, cancellation, and retained history. Native producers send typed text, tool calls, tool results, errors, and file metadata. The CLI, SDK, and Prompt Studio clients consume the same data.
Echo is credential-free. Pi and Onefin use Pi's JSON RPC protocol. Templates pin the installed image, runtime, and producer artifacts. See the session transport.
bun install
bun run test # full suite, using memory and disk PGlite
The manifests in examples/templates intentionally contain placeholder image
and gateway values. Validate them offline, but do not use that directory as a
runnable server catalog.
Set a private data folder on local disk or a block volume.
The fixed pocketcoder schema migrates automatically. With PGlite the controller
creates and keeps its own key bundle. Set POCKETCODER_AUTH_PEPPER only with
PostgreSQL, and keep it in private operator configuration for later restarts.
Start the server first: principal and key commands use HTTP.
POCKETCODER_TEMPLATE_DIR=/absolute/path/to/reviewed/runtime-templates \
bun run pcd -- serve --dir ./pc_data --http 127.0.0.1:8090In another terminal, create the owner key through the private local socket, then issue a scoped backend key:
export POCKETCODER_URL=http://127.0.0.1:8090
export POCKETCODER_KEY=$(bun run pcd -- superuser create --dir ./pc_data --json | jq -r .token)
bun run pcd principals create --name my-backend --scopes templates:read,workspaces:create,workspaces:read,workspaces:cancel,workspaces:preserve,workspaces:restore,checkpoints:read,checkpoints:delete,outputs:read,conversations:read,conversations:delete,services:relay,attachments:write,logs:read,network:read,terminal:attach,terminal:read --templates '*' --json
bun run pcd keys issue --principal-id <returned-id> --request-id <operation-id> \
--scopes templates:read,workspaces:create,workspaces:read,workspaces:cancel,workspaces:preserve,workspaces:restore,checkpoints:read,checkpoints:delete,outputs:read,conversations:read,conversations:delete,services:relay,attachments:write,logs:read,network:read,terminal:attach,terminal:read \
--templates '*' \
--expires "$(bun -e 'console.log(new Date(Date.now() + 3_600_000).toISOString())')" --jsonIssuance requires explicit scopes, a request ID and an expiry inside the calling
key's remaining lifetime. Omitting --templates snapshots the principal's current
grants. Change a principal's grants with pcd principals update --id <principal-id> --scopes ...; pass keys issue --scopes ... for a key-specific
restriction.
For the included Pi harness, the repository convenience builds and materializes that runtime catalog before starting the gateway and server:
OPENAI_API_KEY=... OPENAI_MODEL=... \
bun run local:up -- --template pi-harness --openaiTo manage the server independently, run local:prepare, keep
local:gateway in a separate terminal, configure
the native template directory and its host issuer configuration, and use pcd server start|status|stop; see the getting-started guide.
Then add the backend key to .env in the repository root:
POCKETCODER_URL=http://127.0.0.1:8090
POCKETCODER_KEY=pkt_…The CLI discovers this file automatically:
bun run pcd -- workspaces create --template pi-harness --wait
bun run pcd -- workspaces list --active
bun run pcd -- workspaces logs --id $WS
bun run pcd -- workspaces chat --id $WS
bun run pcd -- workspaces terminal --id $WS
bun run pcd -- workspaces preserve --id $WS
# Send a structured request once the workspace is ready. Reuse both IDs on retry.
curl -s -X POST "$POCKETCODER_URL/v1/workspaces/$WS/requests" \
-H "Authorization: Bearer $POCKETCODER_KEY" -H "content-type: application/json" \
-d '{"requestId":"<request-uuid>","conversationId":"<conversation-uuid>","message":"fix the failing test","files":[]}'
# Durable history remains available after the workspace stops:
curl -s "$POCKETCODER_URL/v1/workspaces/$WS/conversation?limit=100" \
-H "Authorization: Bearer $POCKETCODER_KEY"The OpenAPI document is served at /v1/openapi.json. The candidate workflow
publishes six image roles under ghcr.io/<owner>/<repo>/ after native scans and
role smoke pass. Tags identify the full source commit. See
candidate images; it does not publish stable or version tags.
bun run check # Biome, knip, and package-boundary checks
bun run format # format supported files with Biome
bun run test # unit + integration + example + policy suites
bun run test:unit # package test suites through Lerna only
bun run typecheck # strict TypeScript across the Lerna workspace
bun run build # bundle packages through Lerna with Nx caching
bun run pack:check # pack the published packages and inspect their tarballs
bun run db:generate -- --name=<change> # generate a Drizzle migration
bun run packages # list packages managed by Lerna
bun run start # pocketcoder-server
bun run pcd -- server start|status|stop # manage only the configured server process
bun run pcd -- … # pcd
bun run local:prepare -- --template pi-harness --openai # build/materialize only
bun run local:gateway -- --openai # host gateway only
bun run local:up -- --template pi-harness --openai # persistent local Pi deployment convenience
bun run example:e2e:local # disposable full-stack E2E with the echo harness
bun run example:e2e:pi # native Pi reads a workspace fixtureBun installs and links workspace dependencies and runs each package's scripts.
Lerna coordinates tasks across packages/* and the private
examples/harnesses/* packages; its Nx integration
provides the project graph and task cache configured in nx.json.
The PGlite memory and disk suites run without an external database. Linux
database tests also require strace for the real seed durability ordering
regression. That Linux-only regression is skipped on Darwin. Docker suites
require a running daemon, and
POCKETCODER_KUBERNETES_CONFORMANCE=1 runs the real-cluster driver probe.
Harness integrations live under examples/. The
deterministic Pi E2E runs the remote Pi CLI through native JSON RPC and proves a real
workspace file read. Consumer applications remain separate projects and
integrate through the machine API.
Two npm packages are public:
| Package | What it is |
|---|---|
@pstdio/pocketcoder-cli |
the pcd operator and diagnostics CLI |
@pstdio/pocketcoder-sdk |
Node ESM and TypeScript client for the control plane |
Every other workspace remains private, is linked by Bun with workspace:*, and
is imported through its @pstdio/pocketcoder-* package boundary. Published
packages bundle the private packages they use at build time, so they do not
declare a private workspace dependency at runtime — bun run check fails if
one ever does. The server, agent, and egress proxy are distributed as container
images.
packages/sdk ships Node ESM and bundled TypeScript declarations. Its only
runtime dependency is zod; private workspace packages are bundled. The
package check installs its tarball in a clean project, type-checks a consumer,
and runs that consumer with Node.
The Release Packages workflow and Changesets configuration version and
publish only non-private packages. Add a changeset whenever a change affects a
published package's behavior, API, contracts, or packaged output; do not add
changesets for private-package-only tests or refactors:
bun run changesetAfter changes land on main, Release Packages opens or updates a version pull
request for non-private packages only. Merging that pull request publishes the
packages and creates a package tag and GitHub release for each one. A CLI
release also pushes the matching v<cli-version> tag. That tag build publishes
multi-architecture server, workspace, and egress images and records their
immutable digests on the release.
For tokenless publishing, configure an npm trusted publisher for each published
package, using organization pufflyai, repository pocketcoder, and workflow
filename release-packages.yml.
A package that has never been published cannot use trusted publishing for its
first release: npm requires the package to exist before a trusted publisher can
be attached to it, and OIDC-only runs fail with ENEEDAUTH. Bootstrap a new
package once outside this workflow with a short-lived token, then configure its
trusted publisher. Do not keep a publish token in the repository.
| Variable | Default | Purpose |
|---|---|---|
POCKETCODER_STORE |
postgres with a database URL, else pglite |
Store driver |
POCKETCODER_DATABASE_URL |
none | PostgreSQL URL; selects the PostgreSQL store |
POCKETCODER_DATABASE_SCHEMA |
pocketcoder |
Schema for all PocketCoder tables |
POCKETCODER_DIR |
./pc_data |
Private controller folder; with PGlite it also holds the database |
POCKETCODER_JOURNAL_DIR |
PGlite: recorded folder; PostgreSQL: required | Deletion journal outside the data folder |
POCKETCODER_AUTH_PEPPER |
PGlite: generated key bundle; PostgreSQL: required | Keyed digest secret for machine keys |
POCKETCODER_EVENT_SIGNING_KEY |
pepper | HMAC key for lifecycle event signatures |
POCKETCODER_EVENT_SINK_URL |
none | Callback URL for signed lifecycle events |
POCKETCODER_TEMPLATE_DIR |
none | Directory of reviewed template manifests |
POCKETCODER_HOST / POCKETCODER_PORT |
127.0.0.1 / 8090 |
Operator listen address |
POCKETCODER_AGENT_HTTP |
0.0.0.0:8091 |
Separate agent listener |
POCKETCODER_WORKSPACE_SERVER_URL |
Docker: http://host.docker.internal:8091 |
Agent origin workspaces use to reach the server |
POCKETCODER_MAX_ACTIVE_WORKSPACES |
100 |
Global concurrency limit |
POCKETCODER_MAX_ACTIVE_PER_PRINCIPAL |
20 |
Per-principal concurrency limit |
POCKETCODER_MAX_QUEUED_WORKSPACES |
1000 |
Durable queue bound |