omp packages

@jocax/compact-advisor0.1.0

Tells the agent when its context is worth compacting and where the live work starts, from one decision-model call

jocax Published 7 days ago MIT ~219 downloads/mo

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.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

@jocax/compact-advisor

Tells the agent when its context is worth compacting and where the live work starts, from one decision-model call.

What

One extension, four hooks, two tools:

  • turn_end — after a token notice line, asks a decision model four typed questions about the work and turns the answers into one of four tiers: silent, notice, recommend, request. Above notice it steers a message into the running loop telling the agent to call compact_now.
  • session_before_compact — before pi compacts, asks a second question of its own: which recent turn starts the work that is still live. A confident answer moves pi's planned cut point earlier, onto that turn, so the live work survives compaction verbatim.
  • should_i_compact — the same four questions and tiers, on demand.
  • compact_now — schedules compaction for the end of the current turn, with a note handed to pi's summarizer as its focus.

Why

pi's own auto-compaction fires on a token threshold alone and cuts by token budget alone, so it can summarize away a refactor that is half applied while keeping one that is already finished. This package adds a typed judgment of the work itself, in front of both decisions: whether now is a good time, and where the still-live work begins.

Install

{
  "packages": [
    {
      "source": "npm:@jocax/compact-advisor"
    }
  ]
}
# Try it from a local checkout without installing
pi -e ./src/index.ts

Usage

Nothing to call for the turn-end advice — it arrives on its own once context passes the notice line. The agent can also ask directly:

// should_i_compact — illustrative result, shapes only, no provider is named
{
  "should_compact": true,
  "tier": "recommend",
  "reason": "The task changed from \"...\" to \"...\".",
  "context_tokens": 104312,
  "context_percent": 52.2,
  "lines": { "notice": 60000, "recommend": 100000, "request": 140000 },
  "signals": { "switched_gears": 0.91, "at_boundary": 0.74, "needs_history": 0.3, "mid_operation": 0.08 },
  "next_step": "Call compact_now with a short note to yourself, then continue the current request."
}
// compact_now — illustrative result
{
  "scheduled": true,
  "when": "at the end of this turn",
  "note": "mid-refactor of the auth module; keep the file list and the open questions",
  "next_step": "Finish your reply. Compaction runs when the turn ends, then continue."
}

The four questions

One decision call, four answers, asked together: whether the current request is a different task from what came before (switched_gears), whether the last turn finished a unit of work (at_boundary), how much of the earlier conversation the next step needs (needs_history), and whether the agent is mid a multi-step edit whose partial state exists only in the conversation (mid_operation). The model answers questions about the work; it is never asked what to do — the tier is decided in code against the configured lines, so the same answers always produce the same tier.

Tiers

silent, notice, recommend, request, from quietest to loudest. silent whenever there is nothing to compact yet, the agent is mid operation, or the work has not moved on — each checked before the token count, so a full context is never by itself a reason to drop half-finished work. Below the notice line, no decision call is made at all: the tier would be silent regardless of the answers, so asking would be pure spend. The same tier is never announced twice in a row; an escalation to a louder tier is.

Tools

Tool Parameters Returns
should_i_compact none A verdict: should_compact, tier, reason, the context tokens and percent, the configured lines, the four raw signals, and next_step
compact_now note — what you are doing and what the summary must keep { scheduled, when, note, next_step }

compact_now only records the request; it never compacts inside the tool. Compaction runs through pi's own ctx.compact(), which aborts the running agent as its first act — calling it from inside a tool's execute would abort the loop that is running the tool and discard its result. Instead, compact_now schedules the request and the extension triggers ctx.compact() once the turn's agent_end hook fires, after the agent has finished replying.

The cut point

The pick only ever moves the cut earlier, onto a turn start at or before pi's own planned cut — it only ever moves the cut, never the summary. It never changes how the summary is written: pi keeps ownership of the summarizer, the cut algorithm and the file lists. A consequence of that is a few kept turns also appear inside the summary pi writes, which is a deliberate duplication, not a bug — the alternative would risk dropping conversation that is neither kept nor summarized.

This was verified against pi 0.79.6, which hands the compaction preparation to its summarizer by reference, so mutating the two fields this package touches is enough to widen what pi keeps. AE_PI_COMPACT_ADVISOR_CUT_POINT_ENABLED=false turns only this part off; the turn-end advice and the two tools keep working.

Like @jocax/decision-maker's ask_decision, this package sends conversation text to the decision model to make both picks. security-guard's path rules govern the read and bash tools, not this one, so they are not consulted here.

Configuration

Variable Default Absent
AE_PI_COMPACT_ADVISOR_ENABLED on "false" registers nothing at all: no hook, no tool, no model call
AE_PI_COMPACT_ADVISOR_NOTICE_TOKENS 60000 see below
AE_PI_COMPACT_ADVISOR_RECOMMEND_TOKENS 100000 see below
AE_PI_COMPACT_ADVISOR_REQUEST_TOKENS 140000 see below
AE_PI_COMPACT_ADVISOR_TIMEOUT_MS 5000 this package's own budget for one decision call
AE_PI_COMPACT_ADVISOR_CUT_POINT_ENABLED on "false" leaves pi's own cut point alone
AE_PI_COMPACT_ADVISOR_MAX_TURNS the model's choice-option limit minus one turns one cut-point choice may offer

The three token lines are read as a set: if any of the three is absent, or the set is present but not strictly increasing (notice < recommend < request), all three fall back to their defaults together — a partial override is never mixed with the defaults. The notice line must sit above pi's own keepRecentTokens, which defaults to 20000; below it, compaction has nothing to drop and advice at that point would be noise.

Provider, model, endpoint and key are not settings of this package. They belong to @jocax/decision-core's own AE_PI_DECISION_CORE_* variables — see software/packages/decision-core/README.md.

Errors

A failure at turn_end is swallowed; the first one in a session warns once through ctx.ui.notify, later ones are silent — a hook that throws must never cost the user a turn. A failure in the cut-point hook is also swallowed and leaves pi's own planned cut in place — a compaction must never fail because the advisor could not be asked. A failure in either tool is thrown, which is what marks a pi tool result as an error.

Testing with the mock

import { createDecisionClient, MOCK_CONFIG } from "@jocax/decision-core";
import { advise } from "@jocax/compact-advisor/src/advise";

const client = createDecisionClient(MOCK_CONFIG, {
  mockAnswers: {
    switched_gears: { type: "noul", noul: 0.9 },
    at_boundary: { type: "noul", noul: 0.8 },
    needs_history: { type: "score", score: 0, normalized: 0 },
    mid_operation: { type: "noul", noul: 0.1 },
  },
});

const result = await advise({
  client,
  branch: [],
  usage: { tokens: 105_000, pct: 52.5 },
  lines: { notice: 60_000, recommend: 100_000, request: 140_000 },
});
// result.decision.tier === "recommend"

mockAnswers forces each of the four question ids independently, and the cut-point question's own id (live_from) can be forced the same way without touching the four.

Examples

examples/compact-questions.ts — shouldCompact(client, branch, usage, lines?) and pickLiveFrom(client, turns), both thin wrappers over the package's own modules, taking an injected client so they run against the offline mock.

Live tests

Skipped unless AE_PI_COMPACT_ADVISOR_LIVE names the provider to test:

AE_PI_COMPACT_ADVISOR_LIVE=ollama npx vitest run software/packages/compact-advisor/tests/live.test.ts
AE_PI_COMPACT_ADVISOR_LIVE=typesafe npx vitest run software/packages/compact-advisor/tests/live.test.ts

On 2026-10-01, the Ollama suite passed 3 of 3; the TypeSafe suite was not run, because no API key was configured on the build machine. Both suites assert only the direction of each answer — nothing in this package is calibrated.

Run the demo

just demo-web compact-advisor   # browser, the default surface
just demo compact-advisor       # herdr — optional surface, same extension

The decision provider is a local Ollama serving nimble — the demo needs it running beside whatever session model the kit picks. The offline mock cannot drive this demo: it answers by word overlap, and a single word the conversation shares with the question's own wording pushes mid_operation above the 0.6 threshold at which decideTier returns silent regardless of anything else, so the mock would silence the advisor on most turns.

The three token lines are lowered to 22000 / 28000 / 34000 so a demo session can reach them, but all three still sit above pi's own 20,000 keepRecentTokens floor — below that a scheduled compaction has nothing to drop. demo/demo.config.ts's header has the full reasoning.

Scenario "Advice as the context fills"

The scenario's own two turns (prompt and then) read enough of auth-service to reach only about 10k tokens — still below the notice line. One live run needed four further prompts to reach it; the table below gives them so the run can be reproduced.

Step Do this Watch
1 Send the first prompt ~6.6k tokens — silent
2 Send the then prompt ~10.1k tokens — still silent
3 Send Now read every file under test/ and docs/ and tell me what the tests cover. ~13.6k tokens — still silent
4 Send Read src/index.js, src/server.js, src/types.js, README.md, package.json and every file under src/store and src/util, and quote the first eight lines of each one verbatim. ~20.2k tokens — still silent, below the 22k notice line
5 Send Re-read every file under src/auth and quote each one in full, then list every exported function with its signature. ~23k tokens — one notice row: "Context is at 23k tokens, 17.9% of the window."
6 Send Re-read every file under src/routes and src/middleware and quote each one in full, then write out the full request path for a login call. ~30k tokens — one recommend row: "Recommended: compact now. Context is at 30k tokens, 23.6% of the window."

Which turn first shows a row depends on how much the model actually reads on each turn — these prompts are what one live run used, not a guarantee. Never more than one row per turn, and each quotes its own live count.

In that same run the agent called compact_now on its own after the recommend row, with no operator instruction to do so. pi answered "Compacting the context (manual)" then "Context compacted" — no 🗜 Live work starts at "…" notification fired, and no "Nothing to compact" either. Treat compaction as something the agent may choose to do, not a scripted step of the demo.

Scenario "Ask for the verdict"

Step Do this Watch
1 Send the first prompt {"should_compact": false, "tier": "silent", "reason": "nothing to compact yet", "context_tokens": 1405, ...}
2 Send the then prompt Still tier: "silent", now reason: "below the notice line", with context_tokens, lines: {"notice":22000,"recommend":28000,"request":34000} and signals all filled in (one live run: switched_gears 0.841, at_boundary 0.510, needs_history 0.867, mid_operation 0.377, context_tokens 5409)

Two calls, two reasons for silence — this scenario proves the verdict's shape, not a particular tier. A real model's judgment is its own.

herdr (optional surface): starts, shows the 🗜 Compaction: ollama/nimble status line, and runs its first prompt to a reply.

Needs herdr 0.9.3 or newer and one reachable model provider — see demo-kit.

Development

npx vitest run software/packages/compact-advisor
npx tsc --noEmit -p software/packages/compact-advisor/tsconfig.json

The package suite is 81 tests offline, plus 3 live cases per provider.

Attribution

src/questions.ts, src/tiers.ts, src/state.ts, src/cut-point.ts and examples/compact-questions.ts are adapted from a MIT-licensed project; see THIRD-PARTY-NOTICES.md.