# Engram on Pi Engram is an **omni-repo**: one codebase that runs on Claude Code, OpenAI Codex, OpenCode, Hermes Agent, Google Antigravity, OpenClaw — and [Pi](https://pi.dev), Earendil's minimal, extensible coding agent. The core is the same everywhere: `skills/` (Agent Skills `SKILL.md`) plus the dependency-free `scripts/engram.py`. This file covers the Pi-specific glue. > Verified against **pi 0.83.0** (current) and **pi 0.74.2** (the `legacy-node20` line) on Linux — 20/20 harness checks on both. What was and wasn't proven is itemised in [honest status](#honest-status-of-the-pi-glue) — including the two things that need a live model and therefore aren't ticked. Pi is the friendliest port so far. It reads the Agent Skills standard natively, its package manager understands a plain git repo, and its extension API has exactly the two hooks the nudge needs. There is no self-extract, no bundle-format trap, no manifest precedence puzzle — the whole port is one manifest key, one extension file, and three prompt templates. ## What ships for Pi ``` skills/ # SHARED — the same three skills every platform uses scripts/engram.py # SHARED — the same engine, same state, same schedule pi/engram.ts # Pi extension: exports ENGRAM_ROOT + the due-review nudge pi/prompts/ # /learn, /review, /coach as Pi prompt templates skills/_shared/subagents.md # how to spawn the blind assessor where no agent registry exists agents/*.md # prompt sources the spawned children read ``` Wired up by the `pi` key in `package.json` — Pi reads `skills`, `extensions`, and `prompts` from it and ignores everything else in the repo. ## Requirements - **pi ≥ 0.74** (earlier versions not tested; the extension uses `before_agent_start`'s injected-message return, which is present in 0.74.x). - **Node ≥ 22.19** for current pi. On Node 20 (≥ 20.6), npm serves pi's `legacy-node20` line (0.74.2) instead — Engram is verified on both. - **`python3`** on PATH (stock macOS/Linux is fine; the engine is stdlib-only). ## Install ```bash pi install git:github.com/nagisanzenin/engram ``` That's the install. Pi clones the repo to `~/.pi/agent/git/github.com/nagisanzenin/engram`, runs `npm install` there (engram's extension has zero runtime dependencies — the install exists for the OpenCode half of the omni-repo), and registers the package in `~/.pi/agent/settings.json`. `pi update` tracks the default branch; pin with `@` if you want to stay put. A local clone works too — `pi install /path/to/engram` adds the path without copying, which is the route for hacking on engram itself. `pi remove ` undoes either. ## Invoking the skills | You want | Type | |---|---| | to learn a topic | `/learn ` — or just say "teach me X" | | a review session | `/review` | | stats & coaching | `/coach` | `/learn`, `/review`, and `/coach` are prompt templates that point the model at the corresponding skill. The skills are also first-class Agent Skills in Pi, which buys two more doors: `/skill:learn ` invokes one explicitly, and the descriptions sit in the system prompt so natural language ("teach me Kalman filters") activates them without any command at all. ## The nudge Engram's ambient re-anchor — "[engram] 7 reviews due · ~4 min" — is `pi/engram.ts`, auto-loaded from the package manifest: - On **session start** (launch, `/new`, `--resume`) it runs `engram.py session-start`. If nothing is due, total silence. - If reviews are due, you see one TUI notice immediately (the nudge's first line), and the full text is injected as a visible custom message alongside your **first prompt** (or the next one, if the engine was slow to answer — the probe never blocks startup), so the model knows what you know and can offer to `/review` (Constitution art. 8: ambient, never nagging — at most one nudge per session, and any failure degrades to silence, never repetition). - It also exports `ENGRAM_ROOT` into Pi's process environment, which is how the skills' engine-resolution block finds `scripts/engram.py` from any bash call, wherever the package landed. **If your shell already exports `ENGRAM_ROOT`** (the dev-override convention on every platform), the extension respects it and the skills will use *that* checkout's engine — while the nudge always runs the installed package's own copy. Two checkouts, one store, is a versions-split you chose; unset the variable if you didn't mean to choose it. - In non-interactive runs (`-p`, `--mode json`) and in spawned children (`ENGRAM_CHILD=1`) the extension is deliberately inert — see the next section for why that matters. ## The assessor (blind grading) on Pi **This is the part that needs your attention**, because it is the part a careless port would quietly drop. Pi ships no subagent mechanism — deliberately. So `engram-assessor`, `engram-curriculum-architect`, and `engram-artifact-smith` are **not registered** as agents here. What Pi does have is the one primitive isolation actually needs: **a fresh process is a fresh context.** The skills spawn the blind grader as a non-interactive pi run through the bash tool: ```bash ENGRAM_CHILD=1 pi --no-session --no-skills --no-context-files -p \ "Read /agents/engram-assessor.md and follow it exactly. Grade the items in . Write the receipt JSON to ." ``` The child sees the agent file, the items file, and nothing else — no tutoring dialogue, no project context files, no skills, no nudge (`ENGRAM_CHILD=1` plus print mode keep engram's own extension silent in the child). The receipt comes back through a file, not stdout, because models garnish stdout with prose. The full contract, including why each flag is load-bearing and why extensions stay **on** (custom model providers arrive as extensions), is in [`skills/_shared/subagents.md`](skills/_shared/subagents.md). Each spawned child bills as a normal `pi -p` run against your configured default model. ## Where state lives `engram.py` keeps state in `~/.claude/learning` (override with `ENGRAM_HOME`). Learn in Claude Code at your desk, clear the reviews in pi after lunch — same schedule, one memory. This is the point of the omni-repo. ## Verify the install ```bash python3 ~/.pi/agent/git/github.com/nagisanzenin/engram/scripts/engram.py selftest # 302/302 — same engine everywhere pi config # engram listed, with skills/extension/prompts enabled pi # then type / — learn, review, coach in the picker ``` ## Honest status of the Pi glue **Verified on pi 0.83.0 and pi 0.74.2** (Linux; 20/20 harness checks on each). The method: a mock OpenAI-compatible provider (`models.json` + a local server), so every payload pi was about to send a model could be captured and inspected, plus RPC mode for the interactive-equivalent paths. Every claim below is either one of the 20 checks or is labeled as a separate observation. *Packaging* — `pi install` of this repo; manifest-driven discovery of exactly three skills (`learn`, `review`, `coach` — `_shared/` correctly ignored), the extension, and the three prompt templates. (`engram.py selftest` — 302/302, unchanged by this release — is run alongside the harness, not one of its checks.) *Skills* — all three present in the captured system prompt in Agent Skills XML form (`` asserted), with correct absolute paths into the installed package. *Nudge* — on a seeded store, the extension ran `session-start`, emitted the RPC notify request (asserted), and the captured first-prompt payload contained the injected `engram-nudge` message as a **user-role** message (asserted — that is how pi converts custom messages for the LLM); on an empty store, no notify and no nudge text anywhere in the payload — asserted **over RPC with the UI live**, the one mode where the nudge machinery actually runs (a print-mode-only version of this check would be vacuous; the extension is inert there regardless of the store). Separately — a one-off manual observation, not a harness check — a run against a real store reproduced the production nudge text with live due counts. `ENGRAM_ROOT` was observed propagating through pi's **actual bash execution path** (driven directly via RPC's `bash` command, no model in the loop). *Templates* — `/learn`, `/review`, `/coach` each expanded to the pointing-prompt with arguments in place, in print mode's initial prompt. *Blind grading* — the spawn shape (`ENGRAM_CHILD=1 pi --no-session --no-skills --no-context-files -p …`) was exercised against the mock provider **with a deliberately seeded store**: the child booted, loaded no skills, injected no nudge, read no project context files, and its payload carried nothing of engram's beyond the task text. The context-file exclusion is proved, not assumed: a canary `AGENTS.md` sits in the cwd, one check asserts it **present** in the parent-shaped run and another asserts it **absent** in the child — the flag, not luck, is what excluded it. **The blindness is structural** — a fresh process cannot see a transcript it was never given. **Not verified:** a complete live `/learn` tutoring session and a real-model assessor round-trip (both need a live LLM doing multi-turn tool work; the transport under them is what the harness proved). Reports welcome — open an issue with what you see. ### A note for maintainers: the frontmatter trap Pi parses prompt-template and skill frontmatter as **real YAML**, and a template whose frontmatter fails to parse is **silently skipped** — `/learn` then reaches the model as literal text and nothing tells you why. An unquoted `description:` containing a second colon (`Engram — learn any topic properly: first-principles …`) is exactly that failure. Keep every frontmatter value in `pi/prompts/*.md` double-quoted. (Engram's own skills' frontmatter is colon-free after the name, so the skills load fine as written — with one nuance worth knowing: the shared skills' unquoted `argument-hint: [quick | ]` parses as a YAML *flow sequence*, not a string. Pi ignores that key on skills, so it is benign here — but the same rule applies if a skill description ever grows a colon, and any platform that reads `argument-hint` as a string would want it quoted.)