pi-esr
ESR (Engineering State Runtime) - constrained semantic graph state machine plugin for Pi agent
Package details
$ pi install npm:pi-esr- Package
pi-esr- Version
0.6.3- Published
- Jul 6, 2026
- Downloads
- 441/mo · 33/wk
- Author
- skepsun
- License
- unknown
- Types
- extension, skill
- Size
- 443.9 KB
- Dependencies
- 0 dependencies · 4 peers
Pi manifest JSON
{
"extensions": [
"./dist/pi-extension.js"
],
"skills": [
"./dist/skills"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-esr
Engineering State Runtime — structured state machine for LLM agents.
A constrained semantic graph state machine for engineering, documentation, and decision intelligence tasks. Designed for LLM prefix-cache stability — every byte is deterministic.
NOT a memory system. NOT a chat history system. NOT a retrieval-only system.
Quick Start
Pi Agent (one command)
pi install npm:pi-esr
That's it — /esr command is available immediately. ESR graph tools, closure tools, pack tools, and optional memory tools are ready.
For global install (all projects):
pi install npm:pi-esr
For project-local only:
npm install -g pi-esr
pi-esr setup
Pi Agent (CLI-based)
npm install -g pi-esr
pi-esr setup # project-local
pi-esr setup --global # all projects
Claude Code
npm install -g pi-esr
pi-esr setup --claude
Codex (OpenAI)
npm install -g pi-esr
pi-esr setup --codex
From Source
git clone https://github.com/skepsun/pi-esr.git && cd pi-esr && npm install
npm test # 156 tests
npm run typecheck # Zero errors
Overview
pi-esr transforms user requests into structured entities, typed relations, explicit state transitions, and validated actions. It supports:
- Coding tasks — entities = modules/classes/functions, relations = depends_on/implements
- Document processing — entities = sections/artifacts/requirements, relations = supports/refines/contradicts
- Expert evaluation — entities = experts/evaluations/tasks, relations = evaluates/scores/validates
- Scoring / decision support — numeric metrics attached to entities
- Cross-session continuity — graph state persists across sessions and projects
The architecture has four intentionally separate layers:
ESR Core— the single source of truth for entities, relations, tasks, artifacts, constraints, and closureMemory Bridge— detects host memory capabilities and selects a compatible provider (pi-loom, SQLite, null)Domain Pack— compiles domain semantics into ESR structures without owning statePack Registry— lightweight built-in pack market for discovery and selection
ESR Tools
Core (16 tools)
| Tool | Description |
|---|---|
esr_create_entity |
Create entity (Task, Constraint, Concept, Actor, Artifact) |
esr_update_state |
Update entity state, confidence, or metrics |
esr_link_relation |
Create a typed relation between entities |
esr_evaluate |
Record an evaluation with confidence and metrics |
esr_update_artifact |
Create/update structured artifact with versioned sections |
esr_get_context |
Query the current ESR graph state |
esr_get_closure_status |
Check if a task has enough evidence for stable promotion |
esr_list_closure_gaps |
List tasks with missing closure evidence |
esr_list_tasks |
View task state with closure-oriented summaries |
esr_remove_entity |
Remove an entity and cascade-delete all its relations |
esr_remove_relation |
Remove a specific relation between two entities |
esr_attach_memory_ref |
Attach external memory reference without duplicating content |
esr_list_packs |
List available domain packs |
esr_detect_pack |
Detect the best-matching domain pack for a goal |
esr_expand_with_pack |
Expand a goal through a domain pack into ESR structure |
esr_complete_task |
Primary completion path — artifacts + evaluation + closure → stable |
Memory Tools (built-in, requires better-sqlite3)
| Tool | Description |
|---|---|
esr_mem_store |
Store observation anchored to an ESR entity |
esr_mem_recall |
Recall memories by entity_id, text search, or both |
esr_mem_timeline |
Chronological timeline of observations about an entity |
esr_mem_journal |
View state transition journal or record manual entry |
Memory Compatibility
pi-esr does not assume the host runtime has no memory, and it does not try to replace a mature memory system.
- If the host already exposes memory capabilities (e.g., pi-loom),
memory-bridgedetects them and ESR cooperates throughmemory_ref - If the host has no usable memory layer, ESR can fall back to a SQLite provider or a null provider
- ESR itself stores only decision-relevant structured state rather than duplicating full external memory content
This keeps ESR compatible with:
- agent runtimes with built-in state or memory
- retrieval-oriented memory plugins
- summary-oriented memory plugins
- lightweight hosts with no memory support
Domain Packs
Built-in Packs (shipped with distribution)
Three packs are bundled and auto-loaded from ~/.pi-esr/packs/ or $ESR_PACKS_PATH:
| Pack | Version | Scope | Auto-expands |
|---|---|---|---|
software |
0.6.3 | Code tasks, refactoring, builds | 1 Task + typecheck/test constraints |
agent-tool |
0.6.3 | MCP tool/server/plugin development | 6 Tasks (contract, schema, error, timeout, idempotency, tests) + 4 Artifacts + baseline review |
refactor |
0.6.3 | Extract, migrate, verify, document | 7 Tasks with depends_on chain + 2 Artifacts + safety baseline |
Pack Locations
Packs are loaded at runtime from these paths (checked in order):
ESR_PACKS_PATHenvironment variable (colon-separated, likePATH)~/.pi-esr/packs/(default, created automatically on first run)
Each subdirectory under these paths must contain an index.js that exports an ESRDomainPack object.
After npm install -g pi-esr, run pi-esr setup to ensure ~/.pi-esr/packs/ is initialized.
Creating a New Domain Pack
Every domain pack is a plain object implementing the ESRDomainPack interface:
interface ESRDomainPack {
name: string;
version: string;
description?: string;
detect(input: { prompt: string; cwd: string }): Promise<number>; // 0.0–1.0 confidence
expand(input: { goal: string; cwd: string }): Promise<ESRPackExpansion>;
validate(input: { context: string; cwd: string }): Promise<ESRPackValidationResult>;
}
Step-by-step:
- Create a directory under
~/.pi-esr/packs/my-pack/ - Add
index.jsexporting your pack object - Implement
detect()— return a confidence score (0.0–1.0) based on keywords, patterns, or context - Implement
expand()— return entities, relations, artifacts, constraints, checks, and baselines - Implement
validate()— return evaluations, gaps, baseline diffs, review findings, and remediation items - The pack is auto-discovered on next
esr_list_packsoresr_expand_with_packcall
Minimal example (~/.pi-esr/packs/my-pack/index.js):
export const myPack = {
name: "my-pack",
version: "0.6.3",
description: "A minimal domain pack example.",
async detect(input) {
return input.prompt.toLowerCase().includes("my-topic") ? 0.85 : 0.1;
},
async expand(input) {
return {
entities: [
{ entity_id: "task-main", role: "Task", state: "draft", label: input.goal, confidence: 0.5 },
],
relations: [],
artifacts: [],
constraints: [{ entity_id: "task-main", description: "must_pass_quality_check" }],
summary: "My pack initialized.",
};
},
async validate(_input) {
return { evaluations: [], constraints: [], memoryRefs: [], gaps: [], summary: "Validated." };
},
};
Multiple packs in one directory — use ESR_PACKS_PATH to point to additional directories. Packs are only discovered if a subdirectory has index.js exporting a valid ESRDomainPack.
Advanced packs can include:
checks— structured check definitions for quality gatesreferenceBaselines— requirement baselines with sections and expected signalsbaselineDiffs— diff between actual vs expected in validationreviewFindings— structured findings with severity, category, evidence, recommendationsremediationItems— suggested actions with priority, owner hints, traceability
See packages/domain-pack-agent-tool/src/index.ts and packages/domain-pack-refactor/src/index.ts for full-featured examples.
Design Boundaries
ESRdoes not understand domain semanticsPackdoes not persist stateAdapteronly performs structural mappingRegistryonly handles discovery and selection
Commands
| Command | Description |
|---|---|
/esr |
Display the current ESR graph |
/esr-clear |
Clear all ESR state |
Core Ontology
LLM-Exposed Roles (subset)
| Role | Purpose |
|---|---|
Task |
A unit of work: draft → active → stable (or blocked / deprecated) |
Constraint |
A quality gate or rule that validates a Task |
Concept |
An abstract idea, pattern, or domain term |
Full type system also includes Actor and Artifact (see packages/core/src/types.ts).
LLM-Exposed Relation Types (subset)
| Type | Meaning |
|---|---|
depends_on |
Task A must complete before Task B |
produces |
Task produces an Artifact |
validates |
Constraint validates a Task |
blocks |
Entity A blocks Entity B |
refines |
Entity A is a sub-task or detail of Entity B |
evaluates |
An evaluator judges an entity |
Full type system also includes part_of, implements, supports, contradicts, scores, triggers, updates.
Guardrails
- State machine enforcement — valid transitions only;
stable → draftrejected - Cycle detection — DFS on structural edges (depends_on, part_of, implements, triggers)
- Confidence clamping — all values validated to [0, 1]
- Duplicate prevention — identical relations and repeated evaluations rejected
- Immutability —
getEntity()returns defensive copy - Cryptographic IDs — constraint entities use
crypto.randomUUID() - Timestamps — every entity carries
updated_at, excluded from context for cache - Context fingerprint —
buildGraphFingerprint(DJB2 hash) for cache-hit diagnosis - Query helpers —
getRelationsFor(entityId),getRelationsByType(type)
Cache Stability
ESR is designed for DeepSeek-style prefix caching. Three invariants:
- System prompt is never mutated at runtime —
prompts/esr.mdis static - Context injection wrapper is always identical — no branching based on empty/non-empty
- All context output is deterministically sorted — entities by id, relations by (from, type, to)
Persistence Model
- Session branch entries — per-session audit trail inside the host
- Project-level file —
.pi-esr-memory/esr-state.jsonfor cross-session continuity - Bootstrap recovery — scans recent session files if neither exists
Architecture
packages/
├── core/ @pi-esr/core — framework-agnostic engine
│ └── src/
│ ├── types.ts Entity/Relation/State type definitions
│ ├── validation.ts Ontology validators + state transition matrix
│ ├── graph.ts ESRGraph class (core state machine)
│ ├── context.ts ESR context builder + fingerprint
│ ├── closure.ts Closure protocol + evaluation engine
│ ├── store.ts MemoryStore — SQLite-backed observation storage
│ ├── recall.ts Entity-anchored memory context builder
│ ├── journal.ts State transition journal + summaries
│ ├── session.ts Shared session state
│ ├── host.ts Host interface
│ ├── driver.ts Graph execution driver
│ ├── planner.ts Task planning engine
│ ├── scheduler.ts Task scheduling engine
│ ├── runtime.ts Runtime types & lifecycle
│ ├── runtime-types.ts Runtime type guards
│ ├── repository.ts Repository interface
│ ├── repository-sqlite.ts SQLite-backed versioned entity storage
│ ├── state.ts State machine core
│ ├── cache.ts Cache utilities
│ ├── executor.ts Task executor
│ └── index.ts Package entry point
├── adapter-mcp/ @pi-esr/adapter-mcp — MCP server adapter
├── adapter-opencode/ @pi-esr/adapter-opencode — OpenCode adapter
├── cli/ pi-esr CLI — setup, plugin install, MCP registration
├── domain-pack/ @pi-esr/domain-pack — Pack protocol + adapter types
├── domain-pack-agent-tool/ Agent tool development domain pack
├── domain-pack-refactor/ Refactoring domain pack
├── domain-pack-software/ Software engineering domain pack
└── memory-bridge/ @pi-esr/memory-bridge — Host capability detection + provider selection
extensions/
├── integration/
│ ├── tools.ts 16 ESR tool registrations
│ └── commands.ts /esr /esr-clear
├── persistence/
│ ├── graph-persist.ts Unified persistence (session + file)
│ ├── snapshot.ts Graph state persistence adapter
│ └── reconstruct.ts Graph state reconstruction
├── memory/
│ └── tools.ts 4 esr_mem_* tool registrations
├── overlay/
│ ├── widget.ts TUI overlay widgets
│ ├── format.ts Output formatting
│ └── selectors.ts Entity selectors
├── memory-bridge.ts Memory bridge extension
├── prompt.ts Prompt context builder
├── core.ts Core extension
└── index.ts Extension entry point
Validation
Correctness (156 tests, 11 test files)
npm test # 156 tests, <1s
npm run typecheck # tsc --noEmit, zero errors
| Layer | Tests | What's covered |
|---|---|---|
| Graph core | 54 | Entity CRUD, state transitions, cycle detection, serialization roundtrips, fingerprint stability, immutability, context builder, artifact auto-proxy, neighborhood queries |
| Closure | 10 | Evaluation engine, constraint validation, closure promotion, policy-driven gating, memory-ref requirements |
| Tool integration | 25 | All 16 ESR tools, pack detection/expansion, closure workflow, domain pack scenarios |
| Memory | 24 | Store CRUD, recall/search/timeline, journal, context builder, format helpers, session tag filtering |
| Session | 3 | Current session ID get/set/reset |
| Efficiency | 15 | Token compression benchmarks, prefix-cache stability, context growth, cost projection, DAG parallelism, real-world scenario |
| Persistence | 3 | Reconstruct validation, malformed data rejection, session branch state loading, mirror locking |
| Repository | 5 | SQLite-backed versioned entity storage, conflict detection, concurrent client safety |
| MCP adapter | 11 | MCP tool registration, parameter validation, hook context injection, pack tools, snapshot mirroring |
| E2E multi-session | 5 | 3-session refactor scenario, state continuity, closure across sessions, artifact auto-proxy |
Efficiency Benchmarks
Token Compression vs Chat History
| Entities | ESR context | Chat equivalent | Ratio | Savings |
|---|---|---|---|---|
| 5 | 124t | 210t | 1.7x | 41.0% |
| 10 | 240t | 435t | 1.8x | 44.8% |
| 20 | 479t | 897t | 1.9x | 46.6% |
| 50 | 1,199t | 2,285t | 1.9x | 47.5% |
| 100 | 2,400t | 4,597t | 1.9x | 47.8% |
ESR context is ~1.9x more compact than equivalent chat history at scale.
Prefix-Cache Stability
- Identical state → identical fingerprint → 100% cache hit
- Adding/removing entities → fingerprint changes (correct cache miss)
- Context output is byte-for-byte deterministic — DeepSeek/Claude prefix cache compatible
- Per-entity overhead: ~9.5 tokens (linear O(n), no quadratic blowup)
- Per-relation overhead: ~16.7 tokens (entity + relation, linear O(n))
Cost Projection (DeepSeek pricing)
For a 100-entity session with 50 turns:
- Chat history tokens: 4,597
- ESR context tokens: 2,102
- Tokens saved per turn: 2,495
- Chat history cost (no cache): ~$0.032
- ESR with prefix-cache hits: ~$0.0015
- Estimated savings per session: $0.03+ (compounds across many sessions)
DAG Parallelism
- 3 independent nodes: ESR executes in 1 turn vs 3 sequential chat turns → 67% turn reduction
- Cache invalidation: only changed + downstream nodes re-executed → 40% work saved vs full re-run
Real-world Scenario
5-module refactor (auth, db, api, ui, cli) with 4 depends_on relations and 5 evaluations:
- ESR context: 287 tokens vs ~1,000 chat equivalent → 3.5x compression
State Transition Matrix
| From ↓ / To → | draft | active | stable | blocked | deprecated |
|---|---|---|---|---|---|
| draft | — | ✓ | ✓ | ✓ | ✓ |
| active | ✗ | — | ✓ | ✓ | ✓ |
| stable | ✗ | ✓ | — | ✓ | ✓ |
| blocked | ✓ | ✓ | ✗ | — | ✓ |
| deprecated | ✓ | ✗ | ✗ | ✗ | — |
Golden Rules
- Every meaningful task → Entity
- Every constraint → Entity + validates relation
- Every completed task →
esr_complete_task(artifacts + evaluation → stable) - State is the single source of truth — when in doubt, call
esr_get_context - If it cannot be represented in ontology → DO NOT STORE
- If it does not affect future decisions → DO NOT STORE