# How pi-compact-jev 0.5.1 works

This is the prune path that runs on Pi’s `session_before_compact`. It replaces Pi’s LLM summary of the dropped prefix. Kept text is verbatim. Landing page: [README.md](README.md).

## Order of operations

1. Keep-windows on user/assistant text in the dropped region.
2. Pin invoked skills, unresolved errors, git mutations, edit/write, images.
3. Pair each `toolCall` with its `toolResult`. Unpaired calls are not scored.
4. Ask Jev about **unpinned** pairs only.
5. Apply useful → need-full.
6. Render surviving messages as one markdown string. Flatten the previous compact under it (not Jev-scored).

Pi still cuts at `firstKeptEntryId` (about 20k live tokens). This package only rewrites the dropped prefix.

## State (JSON, shared by every question batch)

Not the live transcript. Caps are characters.

| Field | What |
|---|---|
| `workspace` | Folder name, depth-2 dirs (skips `node_modules` / `.git` / …), README ≤ **25,000** |
| `context` | Fixed paragraph: this is a compaction |
| `first` | First user + first assistant, pair ≤ **25,000** |
| `recent` | Last **5** user/assistant pairs, each ≤ **5,000** |
| `carried` | Previous compact, dumps stripped, ≤ **8,000**. Mid-session commitments only. **Not scored.** |

Tool dumps are **not** in state. `fitState` shrinks those caps until estimated tokens ≤ **28,000**. One state for every batch. Whole POST (state + questions) ≤ **64,000** estimated tokens.

## Questions (float noul, 0–1)

Two questions per unpinned call, same dump view on both (head / middle / tail, 2,500c each). Both go in one POST; the threshold is applied after.

**`useful_tN`**

> Give confidence the following context useful to the current project and/or session context or the latest prompt(s)?
> `[id, tool, args, dump windows]`

**`full_tN`**

> Do I need it in full?
> `[same dump windows]`

Criteria for `full`: after compact this dump is gone and will not be re-run. False means keep first 25% + last 25%, each capped at 2,500.

`keepThreshold` **0.5**, inclusive.

| useful | full | Action |
|---|---|---|
| &lt; 0.5 | ignored | Drop call + result |
| ≥ 0.5 | ≥ 0.5 | Keep dump **verbatim** |
| ≥ 0.5 | &lt; 0.5 | Stub: 25% head + 25% tail, cap 2,500 each |

Pinned calls skip Jev and stay.

## Pins (Jev is not asked)

| Pin | Dump |
|---|---|
| Unresolved `isError` (last fail per tool+path/command with no later **same-key** success) | **Verbatim** |
| Git mutations (`commit`/`push`/…, not status/diff/log) | Cap 600c head |
| `edit`/`write` | Path; edit old/new heads 300c; write body kept if ≤ **2,000c**, else a length note |
| Images | Untouched |
| Last invoked `$skill` load | Full body |

A unique-command error (`cat /tmp/old.log`) never gets a same-key success, so it stays pinned even if the user later says ignore it. Unpinning on *any* later success would drop a real test fail after a following Read. That tradeoff is intentional.

## What we tried that did not ship

- `now`/`later` on full history — large, expensive, worse continuation.
- Blind `full` (“would the stub drop info?” with no dump) — **zero** unpinned full-keeps; broker needle died in the middle.
- 600c cap on unresolved errors — buried failures vanished.
- Stripping every write body — HMAC in short Write args disappeared.

Restoring “Do I need it in full?” **and** putting dump windows on `full` is what made useful dumps stay whole (broker 0.89/0.68).

## Known leftovers (not bugs to chase)

- Tiny listings can score useful ~0.65 and stub instead of drop. Noise.
- One-line facts may score `full` just under 0.5; if the dump is short, the stub keeps the whole line anyway.
- Carried previous compact is shown in state, not re-scored. Re-pruning S1 with Jev stacks loss.
