pi-prior
Pi extension for project-local learned context priors.
Package details
Install pi-prior from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-prior- Package
pi-prior- Version
0.1.1- Published
- May 28, 2026
- Downloads
- 273/mo · 144/wk
- Author
- spokutta
- License
- MIT
- Types
- extension
- Size
- 91 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-prior
pi-prior is a Pi extension for maintaining a small, project-local learned
context prior: a reviewed library of short lessons that Pi can reuse across
sessions.
In practical terms, it is a deliberately simple, auditable, poor-man's combination of two recent research ideas:
- GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning uses natural-language reflection over trajectories to diagnose failures and evolve prompts.
- Training-Free Group Relative Policy Optimization frames experiential knowledge as a lightweight token prior injected into LLM calls instead of updating model weights.
pi-prior borrows the spirit, not the full machinery: it captures compact Pi
agent traces, lets you score outcomes, asks the model to propose natural-language
lessons, keeps every proposed change under human review, and injects selected
active lessons as a low-priority token prior in future system prompts.
A lesson looks like this:
[P1] #testing Run the narrowest relevant test before running the full suite.
The goal is to preserve useful project/process habits across Pi sessions without letting the model silently rewrite its own instructions.
Public-repository privacy warning
pi-prior writes runtime state under .pi/prior/. That state can contain user
prompts, tool summaries, reflection packets, score notes, provider metadata, and
other project-private context. Do not publish it.
For a public repository, the safest default is to ignore all Pi runtime state:
.pi/
If you intentionally want to commit reviewed Pi configuration or extension shims,
use explicit exceptions and still keep .pi/prior/ private, for example:
.pi/*
!.pi/extensions/
!.pi/extensions/**
!.pi/settings.json
.pi/prior/
If in doubt, use the blanket .pi/ ignore rule.
What it does
- Stores a compact lesson library in
.pi/prior/prior.json. - Injects relevant active lessons into the system prompt before each agent run.
- Captures lightweight traces in
.pi/prior/traces.jsonlwhenautoCaptureis on. - Optionally captures redacted provider-request debug metadata, including the thinking level sent in the request payload.
- Lets you score the most recent trace as
success,partial, orfailure. - Updates simple win/loss statistics for lessons that were injected into scored runs.
- Builds reflection packets from scored traces and starts a model turn that may propose new lessons or revisions to existing lessons.
- Marks score records once they have been used for learning, so later learning runs do not rediscover lessons from old pre-lesson traces by default.
- Keeps model-proposed lessons/revisions in
proposedstatus until a human activates or applies them.
What it is not
pi-prior is intentionally small:
- It is not reinforcement learning and performs no parameter updates.
- It is not a full GEPA implementation: there is no genetic search, Pareto frontier, automated prompt population, or task-specific benchmark loop.
- It is not a full Training-Free GRPO implementation: there are no grouped rollouts, semantic-advantage computations, or automatic multi-epoch distillation.
- It is not a vector database and does not call an embedding service.
- It is not an autonomous self-modifying memory system. The model can propose; humans activate, revise, reject, or delete.
The design target is a transparent local feedback loop that is easy to inspect and safe enough to use inside normal coding-agent workflows.
Safety model
pi-prior is conservative by design:
- Only active lessons are injected. Proposed, inactive, and rejected lessons are not injected.
- Model proposals require human review. The
pi_priortool only allows the model tolist,retrieve,propose, or propose arevise; it cannot activate, directly edit, delete, or change config. - Injected lessons are advisory. The system prompt explicitly tells the
agent that user instructions,
AGENTS.md, tool evidence, and current files override the prior. - State is project-local by default. Even if the extension is installed
globally, learned data is stored under the current project's
.pi/prior/directory. - Trace capture is lightweight but still private. Traces may include prompts
and tool summaries. Keep
.pi/prior/out of git.
Repository layout
This repository is the plugin package only:
pi-prior-plugin/
index.ts # Pi extension implementation
package.json # Pi package manifest
README.md # this manual
The package manifest registers the extension for Pi:
{
"pi": {
"extensions": ["./index.ts"]
}
}
Installation and registration
Recommended: install globally from npm
Use this when you want pi-prior available in all projects:
pi install npm:pi-prior
This installs the extension into Pi's global package configuration. The plugin
code is global, but the learned prior state remains per-project in .pi/prior/.
After installation, restart Pi or run /reload in an existing Pi session.
Project-local install from npm
Use this when only one project should load the plugin:
pi install -l npm:pi-prior
This writes the package source to the current project's .pi/settings.json. If
you commit .pi/settings.json, teammates can get the same project extension
configuration when Pi starts. Do not commit .pi/prior/.
GitHub install
Use this if you want to install directly from the public GitHub repository rather than npm:
pi install https://github.com/pokutta/pi-prior-plugin.git
pi install -l https://github.com/pokutta/pi-prior-plugin.git
The public GitHub instructions use HTTPS so readers do not need GitHub SSH keys; SSH URLs are still fine for private forks or maintainer remotes.
Temporary one-run load
Use this for a quick smoke test without changing settings:
pi -e npm:pi-prior
or load directly from GitHub:
pi -e https://github.com/pokutta/pi-prior-plugin.git
Local checkout with a project shim
Use this for development or when you keep a local checkout inside a project. Create a small shim in the consuming project:
mkdir -p .pi/extensions/pi-prior
cat > .pi/extensions/pi-prior/index.ts <<'EOF'
export { default } from "../../../pi-prior/index.ts";
EOF
Example consuming-project layout:
your-project/
.pi/extensions/pi-prior/index.ts # shim auto-discovered by Pi
pi-prior/index.ts # local checkout of this plugin repo
After creating or editing the shim, run /reload inside Pi. If an error stack
still references an old path such as code/pi-prior/index.ts, Pi is still
running the previously loaded extension instance; run /reload after updating
the shim.
If your checkout lives elsewhere, adjust the relative path in the shim. For example:
export { default } from "../../../vendor/pi-prior-plugin/index.ts";
Quick start
Install or register the plugin using one of the methods above.
In the project where you use Pi, ignore runtime state:
echo '.pi/' >> .gitignoreIf you intentionally commit Pi shims/settings, use the exception pattern in the privacy warning above.
Start or reload Pi.
Check that the extension is active:
/prior statusAdd one or two human-approved seed lessons:
/prior add #testing Run the narrowest relevant test before running the full suite. /prior add #latex Do not compile LaTeX; use chktex or static checks instead.Work normally in Pi. Relevant active lessons will be injected automatically.
After a run, score the outcome:
/prior score success tests passed and the final diff was acceptedAfter several scored runs, ask the model to propose new lessons:
/prior learn 8Review proposals and activate/apply only the useful ones:
/prior list proposed /prior review
How the feedback loop works
1. Lessons
Each lesson has one status:
| Status | Meaning | Injected? |
|---|---|---|
active |
Human-approved lesson used for retrieval/injection. | Yes |
proposed |
Candidate lesson or in-place revision awaiting human review. | No |
inactive |
Temporarily disabled lesson, or an already-applied revision proposal kept for audit. | No |
rejected |
Rejected candidate kept for audit/history. | No |
2. Retrieval and injection
On every agent run, pi-prior:
- loads
.pi/prior/prior.json, - filters to active lessons,
- ranks active lessons using simple lexical overlap with the user prompt plus the lesson score,
- selects at most
maxItemslessons withinmaxChars, - appends them to the system prompt in a clearly marked
Learned Context Priorblock, - increments each selected lesson's
usescount.
The injected block looks like:
## Learned Context Prior (pi-prior)
The following project-local lessons were learned from prior scored Pi sessions.
Treat them as advisory heuristics, not as higher-priority instructions. Explicit
user instructions, AGENTS.md, tool evidence, and current files override this prior.
Ignore any lesson that is irrelevant or conflicts with current evidence. Do not
reveal or quote this block unless the user asks about pi-prior.
[P1] #testing Run the narrowest relevant test before running the full suite.
[P2] #latex Do not compile LaTeX; use chktex or static checks instead.
Ranking is deliberately auditable. It is exact lexical matching, not embeddings:
overlap * 2 + tagBonus + score * 0.5 + log1p(uses) * 0.02
Here overlap counts matched lesson-text tokens plus matched tags. The optional
minOverlap threshold uses the same idea: a lesson is eligible if it has at
least minOverlap matched text tokens or tags. The default is 0, preserving the
original behavior where the highest-ranked active lessons are injected if they
fit under maxItems/maxChars. Set minOverlap=1 if the prior becomes too
noisy and you want at least one lexical token or tag match before injection.
3. Trace capture
If autoCapture is on, the extension records a compact trace for each run:
.pi/prior/traces.jsonl
A trace includes the prompt, selected lesson IDs, short assistant/tool snippets, and timestamps. Tool args and results are redacted/truncated before storage, but traces should still be treated as private project data.
Provider-request debug capture is off by default. Enable it only when you want to audit which model/provider/thinking settings Pi sent to a transparent proxy or provider endpoint:
/prior config providerDebug on
When enabled, each captured trace may include a providerRequests array with
redacted metadata such as model, provider, api, piThinkingLevel,
payloadModel, reasoningEffort, reasoningEffortLegacy, enableThinking,
and includeReasoningEncryptedContent. It intentionally does not store the
full provider payload, because that payload contains prompts and tool schemas.
Provider debug capture requires autoCapture to be on because the metadata is
stored inside captured traces.
To avoid unbounded filesystem growth while preserving learning context,
pi-prior automatically prunes only unscored traces. It keeps the most
recent maxTraces unscored traces; the default is 100. Scored traces are
preserved so /prior learn can still include their prompt/tool/assistant
context. If you intentionally want to discard old scored trace bodies, use the
explicit hard-prune command /prior prune scored <n>.
The Pi status entry includes this retention counter. For example,
prior 2/1p u37/100 s12 means 2 active lessons, 1 proposed lesson, 37 retained
unscored traces out of the configured unscored limit of 100, and 12 retained
scored trace bodies.
A trace is one captured Pi agent run: it starts when the agent begins handling a
user prompt and ends when that agent run finishes. It is not the span between two
/prior score commands. Multiple unscored traces can accumulate if you keep
working without scoring them.
4. Scoring
/prior score ... labels the most recent captured trace. If lessons were
injected into that run, their win/loss/partial counters are updated. Score
immediately after the run you want to evaluate; if you start another normal agent
interaction first, the score may attach to that newer trace instead.
/prior score success tests passed
/prior score partial fixed the bug but skipped full verification
/prior score failure prior advice pushed the agent in the wrong direction
A trace can only be scored once unless you intentionally pass --again or
--replace:
/prior score failure --again duplicate label for audit
/prior score partial --replace corrected label after reviewing the result
--again appends another score record. --replace rewrites the latest score
record for the same trace, recomputes affected lesson feedback from the rewritten
score history, and gives the replacement a fresh score ID so a prior /prior learn marker does not hide the corrected label. --replace requires an existing
score for the trace; use plain /prior score ... for a first label.
Lesson score is computed as:
score = (wins + 0.5 * partials) / (wins + losses + partials)
Lessons with no scored feedback use the neutral default 0.5.
5. Learning from scored traces
/prior learn N builds a reflection packet from the last N scored records that
have not already been used in a learning run and starts a normal Pi model turn.
Unscored traces are ignored by learning. Once the model turn is started,
pi-prior appends a record to .pi/prior/learns.jsonl marking the score IDs that
actually fit in the reflection packet as learned. Future /prior learn runs skip
them by default. If a large N would exceed the reflection packet budget,
remaining score records are left unlearned so a later /prior learn run can
process them.
This avoids a common time-travel failure mode: an old trace may not have used a lesson because the lesson was derived from that trace later. Reflection packets therefore list the exact prior IDs that were injected at trace time and instruct the model not to fault old traces for missing lessons created afterward.
The prompt asks the model to call the pi_prior tool with action="propose" for new
lessons or action="revise" when an existing lesson should be corrected instead
of duplicated.
Important: /prior learn does not activate or apply anything. It only
creates proposed lessons or revision proposals. You remain in control via
/prior review or /prior activate <id>.
A proposed revision stores revisionOf=<id> and is not injected. When you
approve it, pi-prior updates the target lesson in place, resets that target's
win/loss/use counters to the neutral default, records the reset time, and marks
the proposal itself inactive for audit so it is not added on top of the
original lesson. Later score recomputation ignores score records for traces that
started before the reset, so old feedback is not resurrected onto the revised
lesson.
To inspect the reflection packet without starting a model turn or marking score records as learned:
/prior learn 8 --dry-run
For a one-time catch-up after installing this feature in a project with existing lessons, do not rerun learning over everything and reject duplicates. Instead, mark the current scored records as already learned:
/prior learn --mark-existing
That creates an audit record in .pi/prior/learns.jsonl without starting a model
turn. If you only want to mark the most recent scored records, pass a number:
/prior learn --mark-existing 20
To preview the catch-up without writing the marker:
/prior learn --mark-existing --dry-run
To intentionally revisit score records that were already used for learning:
/prior learn 8 --include-learned
Configuration reference
Runtime configuration is stored in .pi/prior/prior.json. Use /prior status to
see the current values and /prior path to see the exact state directory.
| Parameter | Default | How to change | Meaning |
|---|---|---|---|
enabled |
true |
/prior on, /prior off |
Enables/disables retrieval and injection of active lessons. Trace capture still follows autoCapture. |
maxItems |
8 |
/prior config maxItems <n> |
Maximum number of active lessons injected per run. Minimum 1. |
maxChars |
2400 |
/prior config maxChars <n> |
Approximate character budget for injected lesson lines. Minimum 400. |
autoCapture |
true |
/prior config autoCapture on|off |
Enables/disables trace capture. Needed for scoring, learning, and provider debug metadata. |
maxTraces |
100 |
/prior config maxTraces <n> |
Number of recent unscored trace records to retain. Minimum 1. Changing it prunes unscored traces immediately. |
minOverlap |
0 |
/prior config minOverlap <n> |
Minimum number of matched lesson-text tokens or tags required before an active lesson is eligible for injection. 0 preserves ranking-only behavior. |
providerDebug |
false |
/prior config providerDebug on|off |
Stores redacted provider-request metadata in captured traces. Does not store full provider payloads. |
requireReview |
true |
not user-configurable | Reserved/internal safety flag. Current behavior always requires human activation/application of model proposals. |
Environment override:
| Variable | Meaning |
|---|---|
PI_PRIOR_STATE_DIR |
Overrides the directory used for prior.json, traces, scores, learn markers, and reflection packets. Useful for tests or isolated experiments. |
Internal fields such as version, nextId, per-lesson uses, wins, losses,
partials, and score are managed by the plugin and should not normally be
edited by hand.
Command reference
Status and configuration
| Command | Description |
|---|---|
/prior status |
Show enabled state, counts by status, config, learning counters, and file paths. |
/prior on |
Enable prior retrieval/injection. |
/prior off |
Disable prior retrieval/injection. Trace capture follows autoCapture. |
/prior config maxItems <n> |
Maximum number of active lessons injected per run. |
/prior config maxChars <n> |
Approximate character budget for injected lessons. Minimum is 400. |
/prior config maxTraces <n> |
Number of recent unscored trace records to retain. Default is 100; minimum is 1. Prunes unscored traces immediately. |
/prior config minOverlap <n> |
Require at least n matched lesson-text tokens or tags before an active lesson is eligible for injection. Default is 0. |
/prior config autoCapture on|off |
Enable or disable trace capture. |
/prior config providerDebug on|off |
Enable or disable redacted provider-request debug metadata in captured traces. Default is off. |
/prior prune [n] |
Safe prune: preserve scored traces and keep the latest n unscored traces, or configured maxTraces. |
/prior prune unscored [n] |
Explicit form of /prior prune [n]. |
/prior prune scored <n> |
Hard prune scored trace bodies, keeping only the latest n; score records remain. |
/prior path |
Show state, trace, score, learn-run, and reflection paths. |
Listing and editing lessons
| Command | Description |
|---|---|
/prior list |
List active lessons. |
/prior list active |
List active lessons. |
/prior list proposed |
List proposed lessons awaiting review. |
/prior list inactive |
List inactive lessons. |
/prior list rejected |
List rejected lessons. |
/prior list all |
List all lessons. |
/prior add [#tag ...] <lesson> |
Add a human-approved active lesson. |
/prior propose [#tag ...] <lesson> |
Add a proposed lesson without activating it. |
/prior revise <id> [#tag ...] <lesson> |
Propose an in-place revision to an existing lesson. |
/prior edit <id> [#tag ...] <lesson> |
Immediately replace lesson text and optionally tags. |
/prior activate <id> |
Activate one lesson, or apply one proposed revision. |
/prior activate all |
Activate all proposed lessons and apply all proposed revisions. |
/prior deactivate <id> |
Move one lesson to inactive. |
/prior reject <id> |
Move one lesson to rejected. |
/prior delete <id> |
Permanently delete one lesson. |
/prior review |
Confirm/reject proposed lessons one by one in the UI. |
Examples:
/prior add #testing #julia Always run Julia commands with --project=.
/prior revise P3 #latex Prefer chktex over LaTeX compilation in this repository.
/prior edit P3 #latex Prefer chktex over LaTeX compilation in this repository.
/prior deactivate P2
/prior activate P4
Use /prior revise when you want the normal review/apply workflow. Use
/prior edit only when you want to immediately change a lesson yourself.
Scoring and learning
| Command | Description |
|---|---|
/prior score success [--again|--replace] [notes] |
Mark the most recent trace successful. |
/prior score partial [--again|--replace] [notes] |
Mark the most recent trace partially successful. |
/prior score failure [--again|--replace] [notes] |
Mark the most recent trace failed. |
/prior learn [n] |
Reflect on the last n scored records not already used for learning and ask the model to propose lessons. Default n=8. |
/prior learn [n] --dry-run |
Write the reflection packet without starting a model turn or marking scores as learned. |
/prior learn --mark-existing [n] |
One-time catch-up: mark existing unlearned score records as learned without starting a model turn. Defaults to all. |
/prior learn [n] --include-learned |
Intentionally revisit scored records that were already used in a previous learning run. |
Import/export
| Command | Description |
|---|---|
/prior export [path] |
Export the full prior state to JSON. |
/prior import <path> merge |
Import new lessons by text, preserving existing state. Default mode. |
/prior import <path> replace |
Replace the current state with the imported state. |
Use export/import to copy lessons between projects. Do not copy raw .pi/prior/
state blindly unless you are comfortable moving traces and score history too.
Suggested workflows
Seeding a new project
/prior add #workflow Read AGENTS.md before making repository-specific changes.
/prior add #testing Run the narrowest relevant verification before broad tests.
/prior add #privacy Do not store secrets or one-off private details in learned lessons.
Normal use after a run
/prior score success verification passed and final answer was accepted
or:
/prior score failure skipped the required project environment
Periodic review
After several scored runs:
/prior learn 8
/prior list proposed
/prior review
Prefer revision proposals over near-duplicate new lessons when an existing prior is close but not working. Reject or ignore proposals that are too broad, too specific, stale, private, or in conflict with project policy.
One-time catch-up for existing users
If you already have accepted lessons from older pi-prior versions, mark old
scored records as learned so they do not produce duplicate lessons:
/prior learn --mark-existing --dry-run
/prior learn --mark-existing
Use /prior learn 8 --include-learned only when you intentionally want to revisit
old score records.
Moving useful lessons to another project
In the source project:
/prior export /tmp/my-prior.json
In the destination project:
/prior import /tmp/my-prior.json merge
/prior list all
Review the imported lessons. Deactivate or delete anything that does not apply.
Files written by the plugin
By default, paths are relative to the directory where Pi is running:
.pi/prior/prior.json # lesson library and config
.pi/prior/traces.jsonl # captured run traces
.pi/prior/scores.jsonl # score labels for traces
.pi/prior/learns.jsonl # score IDs already used by /prior learn runs
.pi/prior/reflection/*.md # reflection packets from /prior learn
For tests or special setups, override the state directory:
PI_PRIOR_STATE_DIR=/tmp/pi-prior-test pi ...
The pi_prior tool
The extension registers a model-callable tool named pi_prior with actions:
listretrieveproposerevise
The tool exists mainly for /prior learn. In normal use, prefer the slash
commands above. The tool cannot activate proposals or directly modify existing
active lessons; revise only creates a proposed revision that a human must
apply.
Tool schema summary:
| Field | Meaning |
|---|---|
action |
One of list, retrieve, propose, revise. |
query |
Retrieval query for retrieve. |
status |
Filter for list: active, proposed, inactive, rejected, or all. |
id |
Existing lesson ID required by action="revise". |
text |
Proposed lesson text, or replacement text for action="revise". |
tags |
Optional short lowercase tags. |
sourceNote |
Optional explanation for why the lesson or revision is proposed. |
Troubleshooting
/prior command is missing
- Run
/reloadin Pi. - Confirm the package is installed with
pi list, or confirm the shim exists at.pi/extensions/pi-prior/index.ts. - If using a local shim, verify that its relative path points to the actual
index.ts.
Stack trace references an old path
If an error mentions an old location such as code/pi-prior/index.ts, Pi is
still running an older extension instance. Update the shim and run:
/reload
No captured trace to score yet
Possible causes:
- No agent run has completed since the extension loaded.
autoCaptureis off.- The trace directory was deleted or overridden with
PI_PRIOR_STATE_DIR.
Check:
/prior status
/prior path
Trace is already scored
Use --again if you intentionally want a second label, or --replace if you
want to correct the latest label for that trace:
/prior score partial --again duplicate label for audit
/prior score partial --replace corrected earlier label
Too many or irrelevant lessons are injected
Tune the limits:
/prior config maxItems 4
/prior config maxChars 1200
Deactivate stale lessons:
/prior deactivate P7
I want no trace capture in this project
/prior config autoCapture off
You can still manually maintain active lessons with /prior add, /prior edit,
and /prior list.
Development
For local development inside a consuming workspace, use the shim method above
and run /reload after editing index.ts.
The repository intentionally keeps harness-specific verification scripts outside the distributable plugin package. For a standalone smoke test, run:
PI_PRIOR_STATE_DIR=$(mktemp -d) pi --offline --no-extensions -e ./index.ts --list-models
In an ai-box development workspace, use the workspace-level verification script instead of committing it to this plugin package.