pi-blackhole 0.5.9 → 0.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +18 -8
- package/dist/index.js +156 -156
- package/package.json +8 -8
- package/src/commands/pi-vcc.ts +52 -3
- package/src/core/unified-config.ts +2 -1
- package/src/om/agents/dropper/agent.ts +57 -15
- package/src/om/agents/observer/agent.ts +81 -17
- package/src/om/agents/reflector/agent.ts +98 -17
- package/src/om/agents/turn-cap.ts +35 -2
- package/src/om/compaction-trigger.ts +3 -3
- package/src/om/consolidation.ts +170 -6
- package/src/om/inline-compaction.ts +51 -9
- package/src/om/retryable-error.ts +97 -2
- package/src/om/worker-attempt.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## [0.5.10] - 2026-09-29
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **`/blackhole` on a branch Pi considers ineligible now reports nothing-to-compact instead of failing.** Pi's `AgentSession.compact()` runs `prepareCompaction()` _before_ it emits `session_before_compact`, so a branch whose entries after the last boundary fit inside `keepRecentTokens` is refused by the host before any blackhole hook can run. The refusal surfaced twice — as Pi's error card (`reason: "manual"` always renders `Compaction failed: …`) and again as `Compaction failed: Nothing to compact (session too small)` from the command's `onError`, whose benign set covered only `Compaction cancelled` and `Already compacted`. The 0.5.4 eligibility fix gated the auto-compaction trigger but not this call site. The command now asks the host's own question via the new `getCompactionIneligibility()` before it mutates anything: an ineligible branch reports `blackhole: nothing to compact yet — Pi's keep-recent budget still covers this branch` (or `already compacted — …`) as information and returns, and the check sits above the pending-observational-memory flush so a refusal no longer appends entries, clears the pending buffer, and announces a flush that never compacted. `onError` keeps the same messages as a backstop for the case where our probe and the host still disagree.
|
|
10
|
+
- **The eligibility probe now resolves compaction settings with the session model.** `isCompactionEligible` called `settingsManager.getCompactionSettings()` with no argument, while Pi calls `getCompactionSettings(model)`; Pi resolves `compaction.modelOverrides["<provider>/<id>"]` for `keepRecentTokens` and `reserveTokens` only when handed the model, so the probe was asking a different question than the host — with a per-model override configured it could report a branch eligible that `compact()` then refuses (or the reverse). All four call sites now pass `ctx.model`.
|
|
11
|
+
- **A cancelled `/blackhole` no longer adds a second, vaguer toast.** The own-cut guard already names the specific reason (`blackhole: Nothing to compact (no live messages)`, or the `tailBehavior` hint) before returning `{ cancel: true }`, and Pi renders `Compaction cancelled` itself for manual aborts; the command's `onError` was adding a third message on top of those.
|
|
12
|
+
- **`/blackhole` loads the config before reading it.** The handler read `runtime.config.compaction` to decide whether to flush pending observational memory, but never called `runtime.ensureConfig`, which only `agent_start` did. When `/blackhole` was the first command of a session, a configured `compaction: "manual"` install read `DEFAULTS` (`"auto"`) and silently skipped the flush, so memory accumulated in the pending file and the summary missed it.
|
|
13
|
+
- **An observer stream error no longer counts as a completed chunk.** A provider failure after a recorded batch used to return partial work as success, advancing `coversUpToId` past content that was never observed. `runObserver` now throws a `WorkerStreamError` (message unchanged); the stage keeps the cursor and retries through the fallback chain, logging `discardedCount`. A valid `complete: true` close from a run that recorded something is still kept when a later turn fails: the failure is warned with the error class only (per [#80](https://github.com/k0valik/pi-blackhole/issues/80)), logged as `observer.error_after_close`, and cools the model when deterministic. A run the turn cap cuts off before it closes is treated the same way when it recorded something; a cap firing before anything was recorded is still an empty success. ([#137](https://github.com/k0valik/pi-blackhole/pull/137))
|
|
14
|
+
- **A failed reflector or dropper run no longer counts as finished work.** Both stages advance their cursor on success, so a partial result marked that work covered: unreflected observations were never crystallized again, and half-evaluated drop candidates were written under an `OM_OBSERVATIONS_DROPPED` marker. `runReflector` now throws a `WorkerStreamError` unless a valid `complete: true` batch that recorded reflections closed the review (a kept close returns `errorAfterClose`, which the stage cools and warns about exactly like the observer's); `runDropper` always throws, since `drop_observations` has no complete flag to prove the evaluation finished. The turn cap cuts a run that recorded something the same way an error does (a cap before anything was recorded is still an empty success), both stages log `discardedCount`, and neither spends another `agentMaxTurns` budget retrying a session model the cap already exhausted. The cap only reports exhaustion for a turn that did tool work, so a run that stopped naturally on exactly its last allowed turn keeps its result. ([#137](https://github.com/k0valik/pi-blackhole/pull/137))
|
|
15
|
+
- **A stream failure that never reaches `agent_end` still reports what it discarded.** When the stream itself breaks, or `result()` rejects, the observer, reflector and dropper rethrow that error with the count of records already written attached, so the stage error logs no longer show `discardedCount: undefined` for the most common way a run dies. The error keeps its own identity — wrapping it would break the stage's stale-context and timeout checks — and a thrown value that cannot carry a property simply loses the count rather than the failure. A run cut off by the hard `workerAttemptTimeoutMs` deadline is the deliberate exception: the deadline rejects immediately with its own error instead of waiting to learn what the worker had recorded, so that path still logs `discardedCount: undefined`. ([#137](https://github.com/k0valik/pi-blackhole/pull/137))
|
|
16
|
+
|
|
17
|
+
- **A provider body no longer cools a model because the worker framed it as an error.** `isDeterministicError` only accepts a bare 4xx code alongside an error signal word, but the `<worker> API error: ` framing supplied that word on every message the observer, reflector and dropper emit — so a status-shaped token anywhere in a provider body (`processed 401 rows`) counted as a real status and cooled the session model for the default hour. The framing is stripped before the signal-word check, the three workers build their messages from one prefix shared by the throw path and the kept-close path so the two cannot drift, and the reflector's kept-close debug entry now classifies the same framed message it would throw. ([#137](https://github.com/k0valik/pi-blackhole/pull/137))
|
|
18
|
+
- **The kept-close toast no longer claims a cooldown that was not recorded.** A session model whose resolved config carries no provider/id never reaches the cooldown file (`recordDeterministicError` keys it on both), yet the warning still said `model cooled down; details in cooldown log`. It now reports only what was written (`deterministic error; no cooldown recorded`), alongside the existing `cooldownHours: 0` branch, which the observer and reflector stages share through one helper. ([#137](https://github.com/k0valik/pi-blackhole/pull/137))
|
|
19
|
+
- **Token counts no longer read as retryable provider statuses.** The numeric alternatives in `RETRYABLE_ERROR_RE` matched as substrings, so `1500 tokens`, `5000 rows`, or `14290` classified retryable on the `retryable:`/`cooldownWorthy:` debug axis. The codes (`429`, `500`, `502`–`504`) are now word-boundary anchored; standalone codes and real phrasing (`429 Too Many Requests`, `HTTP 503`) still match. Nothing branches on this axis today (candidate cooldowns are recorded unconditionally), so the change is log-precision only.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
5
23
|
## [0.5.9] - 2026-09-26
|
|
6
24
|
|
|
7
25
|
### Added
|
package/README.md
CHANGED
|
@@ -2,7 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
**Deterministic compaction + session-aware observational memory for [Pi](https://github.com/earendil-works/pi) — in one unified extension.**
|
|
4
4
|
|
|
5
|
-
`/blackhole` replaces Pi's LLM-based `/compact` with an algorithmic structural summary — fast, zero-cost. Three background workers (Observer, Reflector, Dropper) capture durable facts and decisions that survive across compactions. Per-worker model fallback chains with persisted cooldowns. Manual flush mode. One JSON file to configure it all.
|
|
5
|
+
`/blackhole` replaces Pi's LLM-based `/compact` with an algorithmic structural summary — fast, and the compaction step itself is zero-cost. Three background workers (Observer, Reflector, Dropper) run as separate billed model calls to capture durable facts and decisions that survive across compactions. Per-worker model fallback chains with persisted cooldowns. Manual flush mode. One JSON file to configure it all.
|
|
6
|
+
|
|
7
|
+
> **A note from the maintainer: this is experimental.**
|
|
8
|
+
>
|
|
9
|
+
> It won't save you tokens by itself. Compacting is free — no model call, just structure. Remembering is not: the background workers wake up as your session grows, read what happened, and write down what seems worth keeping. Those are real billed calls. Untuned, on your main coding model, this costs more than doing nothing. It only earns its keep with the workers on something cheap or free.
|
|
10
|
+
>
|
|
11
|
+
> It also doesn't solve compaction — nobody has, as far as I know. It takes a different approach ([observational memory](https://mastra.ai/blog/observational-memory), via [pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) and merges it with [pi-vcc](https://github.com/sting8k/pi-vcc), because the two conflicted when installed together. I merged them and vibeslopped a lot on top: robustness work, new behavior, and bugfixes ported from both upstreams. The recap is pattern-matching, not understanding: it guesses at goals and preferences from their shape. The memory is model-written, so it has the opposite weakness: it can keep what sounds right but isn't. Assume both need the search tool as a backstop.
|
|
12
|
+
>
|
|
13
|
+
> Want only one half? Use the project that does just that half. No hard feelings.
|
|
14
|
+
>
|
|
15
|
+
> Scope stays tight on purpose: forks welcome, bug fixes welcome and reviewed, new features only if they serve conversation compaction itself — not the week's AI fad bolted on. Almost everything is a toggle, which I'll admit is tiring; I use a small slice myself. Memory on most days, off some days. Workers on free models, waking rarely, size-based auto-compaction as a backstop. Most days I don't compact at all — fresh sessions beat resumed ones.
|
|
16
|
+
>
|
|
17
|
+
> Best fit is someone who doesn't count every token: code locally and push the workers to free hosted models, or code on a top model and run the workers on a local card — knowing each worker's different instructions wipe the local prompt cache for the next one. No measurements, no comparisons against other methods. Fast and nearly free the way I run it, good enough for what I need.
|
|
6
18
|
|
|
7
19
|
---
|
|
8
20
|
|
|
@@ -34,14 +46,12 @@ Then `/reload` or restart Pi. The config file at `~/.pi/agent/pi-blackhole/pi-bl
|
|
|
34
46
|
|
|
35
47
|
## ✨ What's new
|
|
36
48
|
|
|
37
|
-
> **Latest release: [0.5.
|
|
49
|
+
> **Latest release: [0.5.10](CHANGELOG.md)**
|
|
38
50
|
>
|
|
39
|
-
> - **
|
|
40
|
-
> - **
|
|
41
|
-
> -
|
|
42
|
-
> - **A
|
|
43
|
-
> - **Finished worker runs stop early** — `record_observations` / `record_reflections` accept a `complete` flag, so a fully-processed run ends instead of spending its `agentMaxTurns` budget.
|
|
44
|
-
> - **Dropper pool pressure now works end to end** — gated on `observationsPoolMaxTokens` (the same pool the footer P gauge shows), it bypasses new-data gates and `1.0` disables it.
|
|
51
|
+
> - **Memory workers that fail now fall back instead of silently skipping** — observer/reflector/dropper stream errors and turn-cap cutoffs throw into the model fallback chain, so a failed run no longer advances the cursor over work it never finished.
|
|
52
|
+
> - **The turn cap only ends runs that did tool work** — a run that stops naturally on exactly its last allowed turn keeps its result instead of failing as cap-cut, and a capped session model is not retried on an identical budget.
|
|
53
|
+
> - **`/blackhole` on an ineligible branch reports nothing-to-compact** — instead of Pi's error card plus a second failure toast when the branch already fits inside the keep-recent budget.
|
|
54
|
+
> - **A cancelled `/blackhole` stays quiet, and the command loads the config first** — no second vaguer toast on cancel, and a `manual`-mode session flushes pending memory instead of reading defaults.
|
|
45
55
|
>
|
|
46
56
|
> See [`CHANGELOG.md`](CHANGELOG.md) for the full history.
|
|
47
57
|
|