原始内容
In short
This monorepo is my attempt to direct and produce a controllable environment for working with llm's to build things in code.
I'm considering renaming it "pi-prometheus-bound." Or maybe just "pi-bound." I currently run it with a script that wholly constrains the main Pi agent to tools given by the extensions, plus read and grep and ls.
You should (is the intention) be able to create exactly the kind of context substrates you need -- and switch between them, or inter-related them (think git branches, but for substrate shapes in your projects) -- and out of those substrates, construct the on-the-fly agents you need, for the particular purposes you might have.
Philosophy
削斧柯,其则不远
When cutting wood to make an axe handle, the model is right there in your hand.
The use of the tool shows you the shape of how to make the version of the tool that you want. Reshape it in-air based on your experience using it.
Packages
| Package | npm | Description |
|---|---|---|
| @davidorex/pi-context | npm:@davidorex/pi-context |
Pi extension. Schema-driven project state: typed JSON blocks with write-time validation, generic block CRUD tools, content-addressed item identity, closure-table relations, and state derived from the substrate. Block types are added by declaring JSON Schemas in the substrate; no code change. |
| @davidorex/pi-context-cli | npm i -g @davidorex/pi-context-cli |
Standalone CLI (not a Pi extension). Exposes pi-context's op-registry as a pi-context <op> [flags] binary; subcommands and flags are derived by reflection over the registry, so they track the op set without per-command code. Calls the same library functions the extension calls; no running Pi instance required. Depends on @davidorex/pi-context. |
| @davidorex/pi-jit-agents | npm:@davidorex/pi-jit-agents |
Library (not a Pi extension). Loads, compiles, and executes .agent.yaml specs in-process, with phantom-tool structured-output enforcement. Consumed by pi-workflows and pi-behavior-monitors. |
| @davidorex/pi-workflows | npm:@davidorex/pi-workflows |
Pi extension. Workflow orchestration from .workflow.yaml specs: DAG execution, typed step types, typed data flow between steps, an expression engine, and checkpoint/resume. Output schemas validate the boundary between steps. |
| @davidorex/pi-behavior-monitors | npm:@davidorex/pi-behavior-monitors |
Pi extension. Monitors that classify agent activity against JSON pattern libraries via side-channel LLM calls, steer corrections, and write structured findings. |
| @davidorex/pi-agent-dispatch | npm:@davidorex/pi-agent-dispatch |
Pi extension. In-pi orchestrator surface: agent-as-tool dispatch (call-agent), capability-grant authoring (author-tool-grant), real-check gate (run-real-checks), attested commit (commit-attested), bounded loop (run-work-order-loop), and dynamic composite tools per config.tool_operations[]. A per-tool authorization gate at the pi-dispatch layer intercepts write-class tools, prompts via ctx.ui.confirm, and stamps the verified operator identity on the write. |
| @davidorex/pi-project-workflows | npm:@davidorex/pi-project-workflows |
Meta-package. Bundles the four extensions (pi-context, pi-workflows, pi-behavior-monitors, pi-agent-dispatch) as a single pi install; depends on all four and registers each. |
Quick Start
# Install all four extensions in any Pi project (one command, via the meta-package)
pi install npm:@davidorex/pi-project-workflows
# Or install the extensions individually
pi install npm:@davidorex/pi-context
pi install npm:@davidorex/pi-workflows
pi install npm:@davidorex/pi-behavior-monitors
pi install npm:@davidorex/pi-agent-dispatch
# Optional: the standalone pi-context CLI (not a Pi extension; no running Pi required)
npm i -g @davidorex/pi-context-cli
# In this monorepo, install the operator pi-context binary as a publish-free
# packed copy of the working tree (no npm link; a repo rebuild can't touch it):
npm run promote:cli
# Initialize project structure
/context init <substrate-dir> # bootstrap pointer + substrate/schemas dirs only (no config, no schemas, no blocks)
/context accept-all # adopt the packaged conception (samples/conception.json) as config.json
/context install # reconciles the substrate against installed_schemas + installed_blocks + installed_agents in config.json
/context check-status # read-only: report which installed schemas are behind the catalog + the version gap (writes nothing beyond the idempotent ceremony seeds: config migration decls + catalog-implied block-schema chains)
/workflow init # creates .workflows/ for run state
Block kinds reach the substrate only by declaring their names in config.json's installed_* arrays (via /context accept-all or by hand) and running /context install, which copies them from the package-shipped samples catalog (samples/). The substrate (config + lenses + closure-table relations) is degree-zero state that defines where the rest lives and how items group into views.
Substrate-management primitive (/context switch family)
A substrate-dir is selected by the .pi-context.json pointer (contextDir field). /context switch is the substrate-management primitive parallel to git switch:
/context switch <existing-dir> # flip pointer to an existing substrate dir
/context switch -c <new-dir> # bootstrap a fresh substrate dir + flip pointer in one operation
/context switch - # round-trip to previous_contextDir
/context list # enumerate substrate dirs with config.json (active marked)
/context archive <dir> # move a (non-active) substrate dir to archive/
Pointer-flip mutations (context-switch / context-archive / context-init / context-accept-all and other write-class tools) route through the pi-agent-dispatch auth-gate, which prompts via ctx.ui.confirm and stamps the verified operator identity on the substrate write — agent-issued tool calls become human-authorized at the pi-dispatch boundary regardless of caller-supplied writer fields.
This enables the per-arc substrate engagement pattern: significant design/spec arcs (multi-step feature work, spec drafting, dependency migrations) get their own .context-<arc-name>/ substrate dir with bespoke vocabulary (custom block kinds + relation_types + lenses) decomposing the design space. The substrate IS the spec; the markdown form at analysis/*.md is source-of-content; the substrate is source-of-structure + cross-references.
Constrained pi session (pi-context pi-bound)
The standalone pi-context CLI also provides a process mode that launches a pi coding-agent session restricted to the composed pi-extension tool surface:
pi-context pi-bound [--grant <id>]... [...pi-args]
On every launch it registers the extensions into the target dir's .pi/ (pi install -l against the @davidorex/pi-project-workflows root), derives the static tool allowlist from the installed packages' generated skills/*/SKILL.md (@davidorex/pi-context + @davidorex/pi-project-workflows), always adds the built-in read-only tools read,ls,grep,find, appends the bounded composites declared in the active substrate's config.tool_operations[], then launches pi --tools <union> ...pi-args. It runs from the process cwd and reads that dir's .pi-context.json for composites (warns, non-fatally, if absent).
--grant <id>(repeatable) — scope the bounded composites to only the named ids (default: all declared).- Any other token passes through verbatim to
pi(e.g.--continue/-cto resume).
This process mode replaces the former scripts/launch-constrained-pi.sh launch script. When /context update finds a locally-modified schema with irreconcilable conflicts against the catalog, it surfaces the conflict set to the calling agent (it does not spawn a subordinate mergetool); the agent reconciles each conflict and commits it with /context resolve-conflict, which writes the resolved body, advances the merge base to the catalog, and registers a known migration chain when the reconciliation advances the schema's version.
Directory Ownership
After initialization, three directories coexist in a project:
.pi/ — Pi platform (agents, skills, settings). Managed by Pi itself.
.pi-context.json — bootstrap pointer naming the single ACTIVE substrate dir (contextDir)
.pi-context-registry.json — project-root registry enumerating ALL substrates by substrate_id (git-tracked)
<substrate-dir>/ — pi-context. Created by /context init <substrate-dir> (skeleton-only).
config.json — substrate bootstrap: root, substrate_id, naming, hierarchy, lenses, installed_*
relations.json — closure-table edges (created on first authored edge)
migrations.json — per-substrate schema-version migration registry
schemas/ — JSON Schema files (empty until /context install reifies declared names)
objects/ — content-addressed object store, one objects/<content_hash>.json per content version (git-tracked)
<name>.json — block data files (each a /context install target or user-authored)
.workflows/ — pi-workflows (run state). Created by /workflow init.
runs/ — workflow execution state, session logs, outputs
.pi/ is Pi's territory — neither extension writes to it. The substrate directory is tracked in git (substrate, schemas, and blocks are source). .workflows/ is gitignored (runtime state). config.json and relations.json always live at the substrate-dir root (the bootstrap-chosen dir, pointer-resolved; they define root); everything else lives under <config.root>/... and a relocated root reaches every read/write because all path construction routes through resolveContextDir(cwd).
What Each Extension Provides
pi-context
Tool families: block CRUD (read/write/append/update/remove-block-item, top-level + nested), item-level read/query (read-block-item, read-block-page, filter-block-items, resolve-item(s)-by-id, join-blocks, find-references, walk-ancestors, context-walk-descendants, context-edges-for-lens, context-lens-view, gather-execution-context), substrate writes (append-relation, amend-config, write-schema, write-schema-migration, rename-canonical-id), content-addressing lifecycle (promote-item), discovery/introspection (read-config, read-schema, read-samples-catalog, read-catalog-schema, list-tools, context-current-state, context-bootstrap-state), lifecycle/state (context-status, context-validate, context-validate-relations, context-reconcile, complete-task — the validator ops, context-roadmap-validate included, accept optional narrowing filters + offset/limit pagination bounding the returned issues[]: severity/code on all three, block additionally on context-validate only (its issues carry a block field; the relation/roadmap validators reject block as an unknown parameter); status always reflects the FULL evaluation, and each op's over-cap boundary refusal names its own parameters), substrate management (context-init, context-accept-all, context-install, context-switch, context-list, context-archive), and the derived roadmap (context-roadmap-load/render/validate — a view over authored milestone_precedes_milestone edges with per-milestone phase/task rollups) — all writes carry automatic schema validation. Read packages/pi-context/skills/pi-context/SKILL.md or call list-tools for the current set.
Item identity + cross-substrate. Identity-bearing items carry a three-layer identity — a mutable id refname, a content-independent oid minted once and immutable, a content_hash over the item's content projection (persisted to a git-tracked objects/<content_hash>.json store), and a content_parent version chain. Each substrate's config.json carries a substrate_id; a project-root .pi-context-registry.json enumerates all substrates by substrate_id so closure-table edges can point across substrates (a {kind:"item", oid, substrate_id} endpoint), resolved/classified by resolveRef as active/foreign/dangling/unregistered.
Commands:
/context init <substrate-dir>— write the substrate skeleton (bootstrap pointer + dirs; no config, no schemas, no blocks); refuses with loud error when the existing pointer'scontextDirdiffers from the caller's argument (points to/context switch -c <new-dir>as the correct command for that operation)/context accept-all— adopt the packaged conception (samples/conception.json) as config.json/context install— reconcile<substrate-dir>/againstinstalled_schemas/installed_blocks/installed_agentsdeclared inconfig.jsonfrom the package registry. Populated block data is never overwritten (reported aspreserved), and empty or absent blocks get the catalog starter. Declared agent specs materialize underagents/(output schemas underagents/schemas/) as editable project-tier specs, never overwritten even under--updateand never baselined. Install also base-stamps each as-installed schema body into the object store and records an install baseline (config.installed_from: catalog source + per-schema fingerprint of the installed schemas) used for installed-vs-catalog drift detection; the baseline covers schemas only and a re-install on an unchanged substrate is idempotent. (Bringing the installed schema model current is/context update, below.)/context check-status(CLI:pi-context context-check-status) — read-only: reports drift between the installed schemas and the catalog (in-sync/catalog-ahead/locally-modified/both-diverged/no-baseline/missing-*), and for each schema behind the catalog (catalog-ahead/both-diverged) which schema is behind and by what version gap — the baseline → catalog version pair, or a content-only basis when the version string is unchanged; writes nothing beyond the idempotent ceremony seeds of the catalog'sconfigmigration declarations and the block-schema migration chains implied by each installed schema's catalog starter+schema version pair intomigrations.json(every substrate-lifecycle ceremony seeds before its first config read, so a substrate missing a catalog-implied chain self-heals on the ceremony)read-catalog-schema(CLI:pi-context read-catalog-schema --kind <canonical_id>) — read-only: fetches and prints the verbatim bundled catalog*.schema.jsonbody for a named block kind (the raw JSON Schema —properties/definitions/$id, not theread-samples-catalogprojection), diffable locally against the installed<substrate>/schemas/<name>.schema.jsonwithout hunting throughnode_modules. Package-intrinsic; mutates nothing/context update [--dryRun](CLI:pi-context update [--dryRun]) — bring the installed schema model current with the catalog, routing each schema by drift state:in-syncno-op;catalog-aheadresync (migration-aware);locally-modified/both-divergedreconciled by a deterministic 3-way merge of base (the as-installed body in the object store, keyed by the baselinecontent_hash) × ours (installed schema) × theirs (catalog schema) — disjoint edits auto-merge (required/enum/ array-typenodes merge as sets), and a schema with irreconcilable per-path conflicts is left unmodified — the conflict set is returned in the op output (underconflicts) alongside a readable report, and the calling agent reconciles it then commits via/context resolve-conflict(below; no subordinate resolver is spawned). Update also additively propagates catalog-new config-registry entries (relation_types/invariants/block_kinds/lenses) absent from the config, preserving user-authored entries and any locally-diverged body of an existing entry (reported underregistryAdditions). A version-bumpcatalog-aheadresync, or a 3-way merge/resolve-conflict that advances a schema's version, registers the shipped catalog migration chain's declarations intomigrations.json, reported undermigrationsRegistered(each{ schema, from, to }). Acatalog-aheadschema whose resync is refused (blocked) carries its diagnostic underblockedDetail(one entry per blocked schema): the refusal reason —no-migration-chain(no shipped chain reaches the catalog version) vsvalidation-failed(the forward-migrated items fail the catalog schema) vswrite-failed(a non-validation throw at the write boundary, e.g. the block writer's duplicate-item-id guard; the failures entry carries the thrown message, the items were NOT flagged invalid, and no markers or pending-blocked record are produced) — the installed→catalog version pair, and for a validation failure the per-item failures naming the failing item id, field, and constraint; on the CLI's text surface this renders as a readable report below the op output. A livevalidation-failedblock also persists a pending-blocked record (pending-blocked.json, pinning the target catalog schema + the chain reaching it) consumable by/context resolve-blocked(below). Avalidation-failedblock additionally gets git-style failure markers written INTO the block file at the offending items (full-line<<<<<<< BLOCKED …/>>>>>>> target: …sentinels), pinning the pre-marker bytes; the schema andmigrations.jsonstay byte-unchanged, and/context resolve-blockedstrips the markers and re-validates.--dryRunpredicts the precise per-schema outcome (resync / migrate / block / merge / conflict) by running the forward-migration and re-validation in memory, alongside the per-blocked-schema diagnostic detail, the config-registry entries that would be added, and the migration declarations that would be registered, and writes nothing beyond the idempotent ceremony seeds (the catalog'sconfigmigration declarations + the catalog-implied block-schema chains; no markers). A run that refuses any schema while applying registry additions or other-schema resyncs/migrations/merges additionally reports the partiality underpartialApplication(applied/notAppliedmirrors of the result channels + a one-line summary naming what was applied alongside what was refused and why), so a blocked run never reads as a no-op;--dryRunreports the predicted partiality in the same shape. On a substrate whose config carries nosubstrate_id, a LIVE update establishes the identity at entry (mint + persist + register, before the first identity-stamping write) so a pre-identity substrate heals on the ceremony instead of refusing, reported undersubstrateIdEstablished; the same entry establishment runs ininstallandresolve-blocked, an established identity is never re-minted, and--dryRunestablishes nothing/context resolve-conflict --schemaName <name> [--schema <reconciled>](CLI:pi-context resolve-conflict --schemaName <name> [--schema <reconciled>]) — commit the reconciliation of a merge conflictupdatesurfaced: writes the reconciled schema body (meta-validated, atomic), advances the merge base for that schema to the catalog, and registers a known catalog migration chain when the reconciliation advances the schema's version, so the nextupdatesees the schema aslocally-modifiedand its deterministic merge takes the reconciled body (base === theirs → ours), converging with zero conflicts and preserving the resolution (a barewrite-schemadoes not advance the base, soupdatewould re-report the same conflict). Omit--schemato treat the current on-disk body as already reconciled and only advance the base/context resolve-blocked --schemaName <name>(CLI:pi-context resolve-blocked --schemaName <name>) — commit the resolution of a schemaupdateblocked. Run after fixing the block's failing items (or widening the local schema): when the block file carries git-style failure markers (written byupdate), it strips the full-line marker sentinels first, then re-validates the corrected block against the pinned target schema from the pending-blocked record, and on pass registers the chain declarations, writes the target schema, advances the block envelope and merge base to the target (so a subsequentupdateconverges in-sync instead of re-blocking), and clears the pending entry; on fail it returns the remaining per-item failures and writes nothing (the marker file is left untouched). The commit itself is all-or-nothing: a throw partway through it restores every touched file byte-exact and reports the failure, never a partial commit/context reconcile [--dryRun](CLI:pi-context context-reconcile [--dryRun], auth-gated) — converge stored rollup-kind statuses with their derivation (the repair half of thederived-statusinvariant class):--dryRunpreviews the exact delta set; a live run applies it through the validated write path (identity-stamped, envelope-stamped, writer-attested). Never touches authored statuses or prose — authored statuses surface for review throughcontext-validate's signals (among them declared invariant violations, the status-vocabulary warning, the staleness sweep forstale_conditions-bearing items); prose is not validated, its review is human/context switch <dir> | -c <new-dir> | -— substrate-management primitive: flip pointer to existing dir, bootstrap-and-flip in one op, or round-trip toprevious_contextDir/context list— enumerate substrate dirs withconfig.json(active marked)/context archive <dir>— move a (non-active) substrate dir toarchive//context view <lensId>— render a configured lens (groupByLens projection) into the conversation/context lens-curate <lensId>— surface bin-assignment suggestions for uncategorized items as a follow-up turn/context status— derived project state (source metrics, test counts, block summaries, git state)/context add-work— extract structured items from conversation into typed blocks/context validate— cross-block referential integrity checks/context help— show available subcommands
Key concept: Users define block types by adding JSON Schemas to <substrate-dir>/schemas/. Any <substrate-dir>/*.json file with a matching schema gets automatic write-time validation. No code changes needed to add new block types.
pi-workflows
Tools: workflow, workflow-list, workflow-agents, workflow-validate, workflow-status, workflow-init
Commands:
/workflow init— scaffold.workflows/directory/workflow list— discover and select a workflow to run/workflow run <name>— execute a workflow (tab-completes with discovered workflow names)/workflow resume <name>— resume from checkpoint/workflow validate [name]— validate workflow specs/workflow status— show workflow vocabulary and discovery/workflow help— show available subcommands
Keybindings: Ctrl+H pause, Ctrl+J resume
Key concept: Workflows are .workflow.yaml specs with typed data flow between steps. Each step runs as a subprocess with its own context window. The DAG planner infers parallelism from ${{ steps.X }} expression references and context declarations. Agent steps support context: [stepName] to inline prior step narrative text into the dispatch prompt, complementing expression-based structured data flow. The monitor step type integrates behavior classification as a verification gate. Bundled agent specs (with their output schemas) ship in the pi-context samples catalog and materialize into a substrate's agents/ via /context install; agent templates ship with pi-jit-agents. A substrate's own agents/ specs win over the bundled tier, and project .pi/templates/ override the bundled templates.
pi-behavior-monitors
Tools: monitors-status, monitors-inspect, monitors-control, monitors-rules, monitors-patterns
Commands:
/monitors on|off— enable/disable all monitoring/monitors <name>— inspect a monitor/monitors <name> rules|patterns|dismiss|reset— manage monitor state/monitors help— show available commands
Programmatic API: invokeMonitor(name, context?) — exported function for synchronous classification without event-handler side effects. Returns ClassifyResult directly.
Key concept: Monitors are .monitor.json specs with Nunjucks classify templates. They observe agent activity via Pi event handlers (message_end, turn_end, agent_end), classify against JSON pattern libraries using side-channel LLM calls, and steer corrections or write structured findings. Verdicts: CLEAN (no issue), FLAG (known pattern), NEW (unknown pattern, optionally learned).
pi-agent-dispatch
Tools (static): call-agent, author-agent-spec, author-tool-grant, run-real-checks, commit-attested, run-work-order-loop, write-schema-migration
Tools (dynamic): composite Pi tools registered per config.tool_operations[] entries (kind-typed: read-files / git-log / grep-paths / command-allowlist). Each closure-binds its instance_params and is granted via --tools <canonical_id> like any built-in.
Dispatch-layer event handlers:
- auth-gate (
tool_callhandler) — intercepts canonical write-class tools (author-agent-spec/author-tool-grant/commit-attested/write-schema/write-schema-migration/amend-config/write-block/rename-canonical-id/context-init/context-accept-all/context-install/context-switch/context-archive/workflow-execute/workflow-resume/workflow-init/monitors-control/monitors-rules); refuses non-interactive contexts unconditionally; callsctx.ui.confirminteractively; on confirm, stamps verified operator identity (git config user.email → process.env.USER cascade) ontoevent.input.writer. Substrate attestation reflects actually-confirming-user, not agent-supplied claim. - read-truncation-gate (
tool_resulthandler) — hard-refuses pi built-inreadtruncation by replacingevent.contentwith single-text-item carrying canonical directive (paginate / grep / sed byte-range as appropriate perTruncationResultshape).
Key concept: an in-pi orchestrator authors agent specs + composes operation-granular capability grants, dispatches a privileged sub-agent under a bounded grant clamped to the parent's grant at dispatch, runs deterministic real-checks (build / typecheck / test / runtime demo / adversarial probe) as the terminal verdict (no LLM in the gate loop), and commits the agent's per-file changes with an Attested-by: agent/<id> footer. run-work-order-loop provides bounded iteration with a human-OK gate at iteration boundaries; on a passing real-check its commit routes through the same commit-attested auth-gate every other caller of that tool goes through (interactive confirm, or completed-pending-commit with no commit attempted in a non-interactive context); a work-order may declare on_fail: "retry" so a non-interactive real-check failure retries the bounded loop instead of aborting.
Development
# Install dependencies
npm install
# Build all packages (tsc compiles to dist/)
npm run build
# Run all tests
npm test
# Run per-package
npm test -w packages/pi-context
npm test -w packages/pi-workflows
npm test -w packages/pi-behavior-monitors
# Run integration tests (requires pi on PATH, spawns LLM subprocesses)
RUN_INTEGRATION=1 npm test -w packages/pi-workflows
# Lint and format (Biome v2.4.9, scoped to packages/ + scripts/)
npm run lint # check for lint issues
npm run format # auto-fix formatting
npm run check # lint + typecheck
# Clean build artifacts
npm run clean
# Derive project state
npx tsx -e "
import { contextState } from './packages/pi-context/src/context-sdk.js';
console.log(JSON.stringify(contextState('.'), null, 2));
"
Architecture
- Main conversation is the control plane; workflows are subordinate. Each workflow step runs as a subprocess (
pi --mode json) with its own context window. The main LLM orchestrates; step agents execute. - Agent specs are
.agent.yamlonly (no.mdfallback). Compiled to prompts via Nunjucks at dispatch time. Agents declareinputSchemafor typed input validation at dispatch,contextBlocksto inject project block data into templates, andoutput.format/output.schemafor output validation. contextBlocks— an agent YAML field. Each entry is either a bare block-name string (whole-block injection) or an object{ name, item?, focus?, depth? }(per-item or scoped injection). At dispatch time, string entries inject the whole block under_<name>(hyphens become underscores). Object entries withitemresolve the ID via the cross-block resolver and inject under_<name>_item(single-entry case) or_<name>_itemsarray (multi-entry case for the same name — e.g., three decisions in one contextBlocks array).depthcontrols cross-reference recursion;focuscarries kind-specific scope hints. Templates access block data via{{ _conventions.rules }}or render via the per-item macros undertemplates/items/(e.g.,{% from "items/conventions.md" import render_convention %}{{ render_convention(rule) }}). Whole-block delegators intemplates/shared/macros.mdmap over items. Missing blocks arenull; templates guard with{% if _conventions %}. No substrate directory means injection is skipped entirely. This is how project state flows into agent prompts declaratively.inputSchema— an agent YAML field defining a JSON Schema for the agent's input. Validated at dispatch time before the agent subprocess is spawned. If validation fails, the step fails immediately — no LLM call is made.- Per-item macros (
templates/items/<kind>.md) — one per block kind, each rendering a single item. Macro names follow canonical singular convention (e.g.render_decision,render_feature,render_framework_gap,render_convention); the renderer registry maps each kind to its canonical macro viaCANONICAL_MACRO_NAMES. Macros are depth-aware: cross-block ID references inline via theresolveandrender_recursiveNunjucks globals whendepth > 0, fall back to bare-ID emission atdepth = 0, and produce the namedcycleMarker/unrenderedMarker/notFoundMarkersentinels on cycles, missing macros, or unresolved IDs. Budget-annotated fields render through theenforceBudgetNunjucks global; warnings surface onCompiledAgent.budgetWarnings. - Whole-block delegators (
templates/shared/macros.md) — thinfor x in data.<key> { render_<kind>_item(x) }wrappers over the per-item macros, for callers that want to dump a whole block. - Shared render-helpers (
templates/shared/render-helpers.md) — helper macros for the recursion / optional-array / optional-scalar patterns. Per-item macros import what they need; new block kinds added later get the same recursion behavior without copy-pasting the pattern. - All resolved via three-tier template search — users override by placing alternate macro files in
.pi/templates/items/<kind>.mdor.pi/templates/shared/.... - DAG planner infers parallelism from
${{ steps.X }}expression references andcontext: [stepName]declarations. Steps without explicit dependencies run sequentially by declaration order. - Step context injection — agent steps with
context: [step1, step2]get prior steptextOutputinlined into their dispatch prompt as labeled markdown sections. Complements expression-based structured data flow with narrative text inlining. - Monitor step type — workflows can invoke monitors as verification gates via
monitor: <name>. CLEAN → completed, FLAG/NEW → failed. - Atomic writes — all block and state persistence uses tmp file + rename for crash safety. State write failure is fatal.
- Checkpoint/resume — incomplete runs can be resumed from last completed step.
completionfield controls post-workflow message to main LLM. - Three-tier resource search — project
.pi/> user~/.pi/agent/> package builtin (agents, templates, workflows) - Workflow SDK (
packages/pi-workflows/src/workflow-sdk.ts) — single queryable surface for the extension's capabilities. All functions derive dynamically from code registries and filesystem. Vocabulary:stepTypes(),filterNames(),validationChecks(). Discovery:availableAgents(),availableWorkflows(). Contracts:agentContracts(cwd)projects each agent's inputSchema, contextBlocks, and output format;agentsByBlock(cwd, blockName)finds agents consuming a given block. Validation:validateWorkflow()checks 11 dimensions including inputSchema required-key matching, contextBlocks existence, StepType metadata enforcement, and template-input alignment with contextBlocks-injected variables. - ESM, TypeScript compiled via
tsctodist/. Pi loads compiled JS from each package'sdist/index.js. Cross-package imports use.jsextensions for Node16 module resolution. - Skill self-install — each extension copies its
skills/directory to~/.pi/agent/skills/on activation, ensuring skills are discoverable regardless of install method. - Pre-commit hook (husky,
.husky/pre-commit) runs, in order:npm run check,npm test,scripts/check-changelog.ts(published-surface commits must grow[Unreleased]),scripts/parity-check.ts(op↔CLI parity),scripts/check-config-schema.ts(expand-contract discipline on the bundled config schema), andscripts/check-comment-citations.ts(delta-scoped: flags only a canonical_id tracker citation newly introduced in a.ts/.tsxcomment under a package'ssrc/tree, never a pre-existing one). CI (GitHub Actions) runs check + build + test on Node 22/23. - All packages use direct dependencies. pi-jit-agents depends on pi-context for block-api reads during contextBlocks injection. pi-workflows and pi-behavior-monitors depend on pi-context for block state; consumer migration to adopt pi-jit-agents as their agent runtime is tracked in the jit-agents v2 spec at
analysis/2026-05-30-jit-agents-spec-v2.md(markdown source-of-content) +.context-jit-spec-v2/(substrate source-of-structure: 3 axioms + 10 decisions + 5 concepts + 2 dispatch-modes + 6 v1-supersessions + open-question FGAPs + inter-entity edges +full-spec-rendercomposition lens). pi-agent-dispatch depends on pi-context + pi-jit-agents. pi-context has no knowledge of workflows, monitors, jit-agents, or agent-dispatch.
For LLMs
When working in this repository:
- Read package READMEs for detailed API docs: pi-context, pi-context-cli, pi-jit-agents, pi-workflows, pi-behavior-monitors, pi-agent-dispatch, pi-project-workflows
packages/pi-context/src/context-sdk.ts— derived state, block discovery, thecontextState()functionpackages/pi-context/src/block-api.ts— block CRUD with schema validationpackages/pi-workflows/src/workflow-sdk.ts— vocabulary, discovery, introspection for workflowspackages/pi-workflows/src/workflow-spec.ts— YAML parsing andSTEP_TYPESregistrypackages/pi-workflows/src/expression.ts— expression evaluator and filter registrypackages/pi-behavior-monitors/index.ts— single-file extension: monitors, classification, steering,invokeMonitor()export.project/contains this project's own block data (issues, decisions, architecture, inventory) — useful for understanding the extension's development state- Use
/context statusto see derived metrics. Use/workflow listto see available workflows.
Release
All packages use lockstep versioning — every release bumps all packages to the same version. Run from the repo root:
npm run release:patch # bump all packages patch, update CHANGELOGs, commit, tag
npm run release:minor # bump all packages minor
npm run release:major # bump all packages major
This invokes scripts/release.mjs, which: checks for uncommitted changes, bumps versions across all workspaces (via scripts/bump-versions.js), stamps [Unreleased] CHANGELOG sections with the new version and date, commits, and tags vX.Y.Z. The script does not publish or push — after it completes, the human must run npm publish --workspaces --access public (requires npm login + OTP) and git push origin main && git push origin v<version>.
License
MIT.