pi-prior

Pi extension for project-local learned context priors.

Packages

Package details

extension

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:

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.jsonl when autoCapture is 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, or failure.
  • 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 proposed status 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:

  1. Only active lessons are injected. Proposed, inactive, and rejected lessons are not injected.
  2. Model proposals require human review. The pi_prior tool only allows the model to list, retrieve, propose, or propose a revise; it cannot activate, directly edit, delete, or change config.
  3. Injected lessons are advisory. The system prompt explicitly tells the agent that user instructions, AGENTS.md, tool evidence, and current files override the prior.
  4. 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.
  5. 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

  1. Install or register the plugin using one of the methods above.

  2. In the project where you use Pi, ignore runtime state:

    echo '.pi/' >> .gitignore
    

    If you intentionally commit Pi shims/settings, use the exception pattern in the privacy warning above.

  3. Start or reload Pi.

  4. Check that the extension is active:

    /prior status
    
  5. Add 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.
    
  6. Work normally in Pi. Relevant active lessons will be injected automatically.

  7. After a run, score the outcome:

    /prior score success tests passed and the final diff was accepted
    
  8. After several scored runs, ask the model to propose new lessons:

    /prior learn 8
    
  9. Review 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:

  1. loads .pi/prior/prior.json,
  2. filters to active lessons,
  3. ranks active lessons using simple lexical overlap with the user prompt plus the lesson score,
  4. selects at most maxItems lessons within maxChars,
  5. appends them to the system prompt in a clearly marked Learned Context Prior block,
  6. increments each selected lesson's uses count.

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:

  • list
  • retrieve
  • propose
  • revise

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 /reload in 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.
  • autoCapture is 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.