al-jaffee0.1.0
Fold-in compaction for pi — mark a point, run a self-contained tangent, fold it into a summary receipt, unfold on demand.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
al-jaffee
Fold-in compaction for pi, named after MAD magazine's Al Jaffee and his fold-ins: the page folds to a marker, only the summary shows, and you can fold it back out when you need the whole picture.
The agent marks a point in the conversation (set_fold_marker), runs a
self-contained tangent, then calls fold(summary). At the end of that turn the
session tree branches back to the marker and the summary is written as the
marker call's new result — the tangent leaves the live context entirely, and
the agent continues the main line as if the tangent had been one tool call.
The folded transcript is not deleted — sessions are append-only trees, so the
tangent stays on a branch. return_to_fold(prompt, fold?) navigates back to it
with an agent-provided prompt; /tree shows the folds as labeled branches.
Model-facing semantics
The mechanics are the easy half. The hard half is what the folded context looks like to the model resuming from it:
The receipt is second-person with excision metadata. A model reading
set_fold_marker → FOLD COMPLETEhas no in-context evidence it ever ran the tangent or calledfold— "I did this work and it was spliced out" and "this never happened" must not be indistinguishable. The receipt states how many entries were folded away, whether user messages were swept up, and says explicitly: you did that work before the fold — you won't remember doing it, and that is expected, not an error — plus trust-but-verify guidance (re-check files/output before asserting them) and "do not re-do the work."… main conversation … assistant: set_fold_marker({ task: "archive inventory" }) toolResult: [al-jaffee] FOLD COMPLETE — tangent folded into this marker. Task: archive inventory Folded away: 9 conversation entries … you won't remember doing it, and that is expected, not an error. Treat the summary below as your own notes… Summary (yours, written at fold time): read a.md (kickoff, plan B), b.md (budget $12.4k), c.md (roadmap)… assistant: (continues the main line from here — tangent tokens gone from context)The plugin never impersonates the user. All model-directed traffic (fold cues, corrections, unfold prompts) goes through
pi.sendMessagecustom messages — labeledal-jaffee, neverpi.sendUserMessage, which appends a message that appears as if typed by the user. The unfold prompt is re-sent as a visible labeled plugin message at the tangent tip.Refusals reach the model, not just the TUI. A queued
fold/unfold promised a commit; if the commit cannot happen (conversation moved past the fold, navigation cancelled, dispatch failed), the model receives a visible correction message (withtriggerTurn) saying NOTHING was folded and it may retry — instead of silently believing its summary is safely at the marker.User words are flagged before they leave. If user messages sit between the marker and the fold,
foldwarns at queue time ("N user message(s) will be folded too — make sure the summary covers them") and the receipt records the count; the words stay restorable on the branch.Machine notes: the summary's ground-truth counterpart. The summary is self-reported, and after the fold it is the only part of the tangent left in context — a single point of failure, since sloppy notes get relayed with full confidence and cannot be detected from inside. So the commit appends an auto-generated footer:
set_fold_markersnapshots the working directory (walk + sha1 hashes + line counts), the commit snapshots it again, and the diff plus every bash call seen between marker and fold (via atool_resultlistener) land in the receipt:Summary (yours, written at fold time): Inventoried archive/: a.md (meeting notes)… Machine notes (auto-generated — ground truth for files/commands, not my notes): cwd: /Users/nick/proj Files vs marker snapshot (2 changed): + notes.md (created, 1 line) ~ draft.md (modified, 3 → 5 lines) Bash: 1 call — all exit 0; last: python3 -m py_compile draft.md Worktree: 1 dirty (git) — 0 already dirty at marker (cwd snapshot: 34 files hashed)The receipt's trust-but-verify line points at the notes ("the machine notes at the end are auto-generated ground truth for files and commands — re-verify anything else critical"). Paths and counts only — never file contents or command output; the whole footer is priced at a few lines.
One surgery per turn. Queuing a fold while an unfold is pending (or vice versa) fails loudly instead of silently dropping the first intent.
Why it's cache-friendly
- Fold preserves the transcript byte-for-byte up to the marker tool call,
so the provider prompt-cache prefix survives; only the marker result forward
is new tokens. Compare
/compact, which rewrites the head and forfeits nearly all cached tokens. - Unfold is the mirror win: the tangent tail was the live context minutes ago, so returning usually hits the still-warm provider cache. In the e2e test, the unfold request read 3,840 cached tokens and sent only 278 uncached.
Measured on the e2e run (synthetic / GLM-5.3-Flash, ~7 KB of tangent file content folded):
| request | input | cacheRead |
|---|---|---|
| tangent turns | 1597→80 | 704→2496 |
| post-fold continuation | 1327 | 2560 |
| next turn (folded) | 96 | 3584 |
| unfold (back to full) | 278 | 3840 |
Tools
| tool | when |
|---|---|
set_fold_marker(task) |
right before starting a self-contained tangent |
fold(summary) |
right when the tangent is done; summary is written for future-you |
return_to_fold(prompt, fold?) |
when a completed fold's details are needed again; fold targets a specific fold by task substring (default: latest) |
All three are always active (stable tool list = stable cache prefix). Nested
markers work; fold applies to the most recent. /al-jaffee-status lists the
folds on record with their excision counts.
Commands
/al-jaffee-status— active marker, folds on record, pending surgery, machine-notes state for the active marker/al-jaffee-commit,/al-jaffee-return— internal; also usable manually to retry a failed dispatch
Tests
node test/mechanics.test.mjs # SessionManager-level surgery — no LLM
node test/snapshot.test.mjs # machine-notes pipeline — no LLM
node test/e2e.test.mjs # full RPC fold/unfold cycle — real provider
The harnesses resolve pi's global install via npm root -g (homebrew
fallback in test/paths.mjs). The e2e run uses the real Synthetic.new
provider through ~/.pi/agent/npm/.../pi-synthetic-provider — point
AL_JAFFEE_SYNTHETIC_PROVIDER at the extensions dir if it lives elsewhere.
Install
ln -s /path/to/al-jaffee ~/.pi/agent/extensions/al-jaffee
# or project-local:
ln -s /path/to/al-jaffee .pi/extensions/al-jaffee
Notes & limits
- The commit runs at turn end (tree navigation is unsafe mid-stream by
design). Tools queue intent with
terminate: true; a command dispatched fromagent_settledperforms the surgery. The dispatch channel is a self-rescheduling retry chain (exponential backoff, ~12s budget) that never depends onagent_settledrefires — a swallowed dispatch with no further session activity retries anyway, and on give-up notifies both the user (TUI) and the model (correction message). The model experiences a successful fold as one seamless continuation. - Folding folds everything after the marker, including any conversation that
happened past the tangent — flagged at queue time and in the receipt;
/treecan always walk back (the branch is intact). fold/unfold need an interactive session (TUI or RPC). In print mode the tools fail gracefully.- pi API trap:
getBranch()returns ROOT→LEAF (oldest first — it walks entry→root then reverses), so "first match found" is the oldest entry. Nested-marker resolution and fold-record listing compensate by iterating to the match closest to the leaf. The region scan skips until the marker's assistant entry, then counts to the leaf. - Machine notes budgets (snapshot.ts): the cwd walk caps at 5,000 files and
a 5s budget; files over 8MB aren't hashed (size-only comparison, documented
best-effort) and over 1MB get no line count. Common noise dirs are excluded
(
.git,node_modules,__pycache__,dist,build, caches — seeEXCLUDED_DIRS). A truncated snapshot never reports a confident "none": the footer flags it ("snapshots truncated — some changes may be untracked"). Degrades honestly everywhere else too: no marker snapshot (taken before a/reloadthat lost the in-memory store — though the store persists to a temp file for exactly that), not a git repo, failed walk — each becomes an "n/a" line, never a fabrication. Per-marker state survives/reloadviaos.tmpdir()/al-jaffee-snapshots/(pruned after 7 days) and is cleaned up at commit. - One line of non-public API:
appendMessage()via a type cast onctx.sessionManager(no sanctioned API appends arbitrary toolResults). Everything else —navigateTreebranching + agent-state resync, custom messages, labels, command dispatch — is public API. test/mechanics.test.mjs— SessionManager-level surgery tests (no LLM), including the second-person receipt, excision metadata, the machine-notes footer, and the labeled plugin-message unfold prompt.test/snapshot.test.mjs— the machine-notes pipeline (no LLM): walk/excludes/line-counts, diff, bash status parsing, footer formatting, and honest degradation. Both load TS directly (jiti).test/e2e.test.mjs— full RPC-driven fold/unfold cycle with token accounting (node e2e.test.mjs, uses the real Synthetic.new provider — allow for inference latency variance); asserts the receipt semantics (including the auto machine notes) and that the unfold prompt is NOT a fake user message. All three resolve pi viatest/paths.mjs.