@matthewfl/pi-contemplator 0.1.8 → 0.1.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matthewfl/pi-contemplator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "A Pi extension that keeps long-running agentic sessions on track with background memory, contemplation, and structural review.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,7 @@ import { estimateStringTokens } from "../../tokens.js";
|
|
|
27
27
|
import { createRecallAgentTool } from "../../tools/recall-observation.js";
|
|
28
28
|
import { createSearchMemoriesAgentTool } from "../../tools/search-memories.js";
|
|
29
29
|
import { logAgentStreamError } from "../stream-errors.js";
|
|
30
|
-
import {
|
|
30
|
+
import { summarizerContinue, SUMMARIZER_SYSTEM } from "./prompts.js";
|
|
31
31
|
import {
|
|
32
32
|
renderSummarizerMemory,
|
|
33
33
|
sampleSummarizerMemories,
|
|
@@ -568,7 +568,7 @@ export async function runSummarizer(args: RunSummarizerArgs): Promise<Summarizer
|
|
|
568
568
|
|
|
569
569
|
try {
|
|
570
570
|
await runOnce("The preceding summarize call and receipt are an illustrative example only. Its placeholder ids are not real and it did not create a summary. Now inspect the actual records and use tools to register safe compression, or call done if none is warranted.", false);
|
|
571
|
-
for (let invocation = 1; !completedWithDone && invocation < SUMMARIZER_MAX_INVOCATIONS; invocation++) await runOnce(
|
|
571
|
+
for (let invocation = 1; !completedWithDone && invocation < SUMMARIZER_MAX_INVOCATIONS; invocation++) await runOnce(summarizerContinue(drafts.size, invocation), true);
|
|
572
572
|
} catch (error) {
|
|
573
573
|
debugLog("summarizer.error", { error: error instanceof Error ? error.message : String(error), acceptedSummaries: drafts.size });
|
|
574
574
|
if (drafts.size === 0) return { completed: false, reviewedUpToId: coversUpToId, sample };
|
|
@@ -43,4 +43,8 @@ Tools:
|
|
|
43
43
|
|
|
44
44
|
Prefer faithful useful compression over both distortion and indefinite accumulation. Under pressure, make progress on old low-value clusters first; treat durable valuable records and user intent as the last things to compress.`;
|
|
45
45
|
|
|
46
|
-
export
|
|
46
|
+
export function summarizerContinue(recordedSummaries: number, reminderNumber: number): string {
|
|
47
|
+
const count = Math.max(0, Math.floor(recordedSummaries));
|
|
48
|
+
const thinkingMinutes = Math.max(1, Math.floor(reminderNumber)) * 20;
|
|
49
|
+
return `IMPORTANT!!!! YOU HAVE BEEN THINKING FOR ${thinkingMinutes} MINUTES. CALL A TOOL NOW. DO NOT WRITE SUMMARIES IN THE MAIN TEXT. THERE ${count === 1 ? "IS" : "ARE"} CURRENTLY ${count} RECORDED ${count === 1 ? "SUMMARY" : "SUMMARIES"}${count === 0 ? "; NOTHING HAS BEEN SUMMARIZED YET" : ""}. IF YOU WROTE SUMMARIES IN THE MAIN TEXT, RECORD THEM USING THE summarize TOOL NOW. IF NO SAFE SUMMARY IS WARRANTED, CALL done.`;
|
|
50
|
+
}
|
|
@@ -242,7 +242,6 @@ export async function runConsolidationPipeline(
|
|
|
242
242
|
const pipelineEntries = options.observerEntries ?? (ctx.sessionManager.getBranch() as Entry[]);
|
|
243
243
|
const initialCoverage = latestCoverageIndex(pipelineEntries, OM_OBSERVATIONS_RECORDED);
|
|
244
244
|
const catchUpThroughId = sourceEntriesAfter(pipelineEntries, initialCoverage).at(-1)?.id;
|
|
245
|
-
const beforeFold = foldLedger(ctx.sessionManager.getBranch() as Entry[]);
|
|
246
245
|
runtime.consolidationPhase = "observer";
|
|
247
246
|
runtime.observerBacklogBlocking = catchUpThroughId !== undefined;
|
|
248
247
|
try {
|
|
@@ -254,6 +253,7 @@ export async function runConsolidationPipeline(
|
|
|
254
253
|
while (true) {
|
|
255
254
|
const beforeEntries = ctx.sessionManager.getBranch() as Entry[];
|
|
256
255
|
const beforeCoverage = latestCoverageIndex(beforeEntries, OM_OBSERVATIONS_RECORDED);
|
|
256
|
+
const beforeObservationIds = new Set(foldLedger(beforeEntries).observations.map((memory) => memory.id));
|
|
257
257
|
const observerOutcome = await runObserverStage(pi, runtime, ctx, resolveModel, {
|
|
258
258
|
force: options.forceObserver === true,
|
|
259
259
|
entries: options.observerEntries,
|
|
@@ -261,9 +261,18 @@ export async function runConsolidationPipeline(
|
|
|
261
261
|
contextGeneration,
|
|
262
262
|
});
|
|
263
263
|
if (observerOutcome === "abort") return;
|
|
264
|
-
if (options.observerEntries) break;
|
|
265
264
|
|
|
266
265
|
const afterEntries = ctx.sessionManager.getBranch() as Entry[];
|
|
266
|
+
if (shouldScheduleSummarizerFromObserver(options)) {
|
|
267
|
+
const passAddedObservations = foldLedger(afterEntries).observations.some((memory) => !beforeObservationIds.has(memory.id));
|
|
268
|
+
// A finite observer backlog can take hours to drain while the primary
|
|
269
|
+
// agent is idle. Let each completed chunk feed the independent
|
|
270
|
+
// summarizer; waiting for the entire observer pipeline can otherwise
|
|
271
|
+
// leave an oversized old pool untouched indefinitely.
|
|
272
|
+
if (passAddedObservations) scheduleSummarizer(pi, runtime, ctx);
|
|
273
|
+
}
|
|
274
|
+
if (options.observerEntries) break;
|
|
275
|
+
|
|
267
276
|
const afterCoverage = latestCoverageIndex(afterEntries, OM_OBSERVATIONS_RECORDED);
|
|
268
277
|
const remainingTokens = catchUpThroughId === undefined
|
|
269
278
|
? 0
|
|
@@ -279,10 +288,6 @@ export async function runConsolidationPipeline(
|
|
|
279
288
|
// pipeline's blocking backlog. Clear before notifying the contemplator.
|
|
280
289
|
if (contextGeneration === runtime.getContextGeneration()) runtime.observerBacklogBlocking = false;
|
|
281
290
|
}
|
|
282
|
-
const afterFold = foldLedger(ctx.sessionManager.getBranch() as Entry[]);
|
|
283
|
-
const beforeIds = new Set(beforeFold.observations.map((item) => item.id));
|
|
284
|
-
const added = afterFold.observations.filter((item) => !beforeIds.has(item.id));
|
|
285
|
-
if (added.length > 0 && shouldScheduleSummarizerFromObserver(options)) scheduleSummarizer(pi, runtime, ctx);
|
|
286
291
|
if (contextGeneration === runtime.getContextGeneration()) runtime.notifyMemoryUpdate?.(ctx);
|
|
287
292
|
}
|
|
288
293
|
|
|
@@ -312,10 +317,14 @@ export function summarizerTriggerAfterRun(
|
|
|
312
317
|
targetTokens: number,
|
|
313
318
|
postRunOldTokens: number,
|
|
314
319
|
retriggerTokens: number,
|
|
320
|
+
failedAttemptStartOldTokens?: number,
|
|
315
321
|
): number | undefined {
|
|
316
|
-
return
|
|
317
|
-
|
|
318
|
-
|
|
322
|
+
if (successfullyCompleted) return nextSummarizerTriggerTokens(targetTokens, postRunOldTokens, retriggerTokens);
|
|
323
|
+
if (failedAttemptStartOldTokens === undefined) return currentTriggerTokens;
|
|
324
|
+
// Back off a model pass from the pool it actually received. Growth that
|
|
325
|
+
// arrived while it was running must count toward the retry rather than being
|
|
326
|
+
// swallowed by a threshold based on the larger post-run pool.
|
|
327
|
+
return Math.max(currentTriggerTokens ?? targetTokens, failedAttemptStartOldTokens + retriggerTokens);
|
|
319
328
|
}
|
|
320
329
|
|
|
321
330
|
function syncAndScheduleSummarizer(pi: ExtensionAPI, runtime: Runtime, ctx: ConsolidationCtx): void {
|
|
@@ -324,14 +333,27 @@ function syncAndScheduleSummarizer(pi: ExtensionAPI, runtime: Runtime, ctx: Cons
|
|
|
324
333
|
}
|
|
325
334
|
|
|
326
335
|
export function scheduleSummarizer(pi: ExtensionAPI, runtime: Runtime, ctx: ConsolidationCtx): void {
|
|
327
|
-
if (runtime.config.passive || !runtime.config.summarizerEnabled
|
|
336
|
+
if (runtime.config.passive || !runtime.config.summarizerEnabled) return;
|
|
337
|
+
if (runtime.summarizerInFlight) {
|
|
338
|
+
// Do not lose observer/activity checkpoints that arrive during a long run.
|
|
339
|
+
// The tracked task rechecks once its single-flight lock has been released.
|
|
340
|
+
runtime.summarizerRecheckPending = true;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
runtime.summarizerRecheckPending = false;
|
|
328
344
|
const entries = ctx.sessionManager.getBranch() as Entry[];
|
|
329
345
|
const pools = currentMemoryPools(runtime, entries);
|
|
330
|
-
|
|
346
|
+
const targetTokens = runtime.config.oldMemoryPoolTargetTokens;
|
|
347
|
+
const nextTriggerTokens = summarizerTriggerTokens(runtime);
|
|
348
|
+
// Initial/healthy eligibility strictly exceeds the advisory target. Once a
|
|
349
|
+
// growth backoff is installed, reaching that +N threshold is sufficient; it
|
|
350
|
+
// must not require an accidental extra token beyond the configured amount.
|
|
351
|
+
if (pools.oldTokens <= targetTokens || nextTriggerTokens > targetTokens && pools.oldTokens < nextTriggerTokens) return;
|
|
352
|
+
const runStartOldTokens = pools.oldTokens;
|
|
331
353
|
const generation = runtime.getContextGeneration();
|
|
332
354
|
const runId = `summarizer-${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`;
|
|
333
355
|
const sessionMetadata = debugSessionMetadata(ctx);
|
|
334
|
-
|
|
356
|
+
const task = runtime.launchSummarizerTask(ctx, async () => withDebugLogContext({
|
|
335
357
|
enabled: runtime.config.debugLog === true,
|
|
336
358
|
cwd: ctx.cwd,
|
|
337
359
|
...sessionMetadata,
|
|
@@ -415,19 +437,28 @@ export function scheduleSummarizer(pi: ExtensionAPI, runtime: Runtime, ctx: Cons
|
|
|
415
437
|
const postRunPools = currentMemoryPools(runtime, ctx.sessionManager.getBranch() as Entry[]);
|
|
416
438
|
const target = runtime.config.oldMemoryPoolTargetTokens;
|
|
417
439
|
runtime.summarizerNextTriggerTokens = summarizerTriggerAfterRun(
|
|
418
|
-
|
|
419
|
-
// identical pool at every primary-agent checkpoint. Require fresh
|
|
420
|
-
// old-memory growth before trying another (potentially different)
|
|
421
|
-
// sample. Model-resolution failures spend no tokens and stay eligible.
|
|
422
|
-
successfullyCompleted || modelRunAttempted,
|
|
440
|
+
successfullyCompleted,
|
|
423
441
|
runtime.summarizerNextTriggerTokens,
|
|
424
442
|
target,
|
|
425
443
|
postRunPools.oldTokens,
|
|
426
444
|
runtime.config.summarizerRetriggerTokens,
|
|
445
|
+
// A failed/no-progress model pass should not retry an identical
|
|
446
|
+
// prompt at every checkpoint. Anchor the growth backoff to the pool
|
|
447
|
+
// seen at launch so concurrent growth is not accidentally erased.
|
|
448
|
+
modelRunAttempted ? runStartOldTokens : undefined,
|
|
427
449
|
);
|
|
428
450
|
}
|
|
429
451
|
}
|
|
430
452
|
}));
|
|
453
|
+
void task?.then(() => {
|
|
454
|
+
if (generation !== runtime.getContextGeneration()) return;
|
|
455
|
+
if (!runtime.summarizerRecheckPending) return;
|
|
456
|
+
// launchTrackedTask has released summarizerInFlight before resolving.
|
|
457
|
+
// Coalesce every checkpoint received during the old run into one fresh
|
|
458
|
+
// ledger/threshold evaluation.
|
|
459
|
+
runtime.summarizerRecheckPending = false;
|
|
460
|
+
scheduleSummarizer(pi, runtime, ctx);
|
|
461
|
+
});
|
|
431
462
|
}
|
|
432
463
|
|
|
433
464
|
async function runObserverStage(
|
package/src/runtime.ts
CHANGED
|
@@ -176,6 +176,8 @@ export class Runtime {
|
|
|
176
176
|
summarizerPromise: Promise<void> | null = null;
|
|
177
177
|
/** Old-pool token threshold for the next pass; undefined means configured target. */
|
|
178
178
|
summarizerNextTriggerTokens: number | undefined;
|
|
179
|
+
/** A scheduling checkpoint arrived while the single-flight summarizer lock was held. */
|
|
180
|
+
summarizerRecheckPending = false;
|
|
179
181
|
private memoryUpdateListener: ((ctx: MemoryUpdateCtx) => void) | undefined;
|
|
180
182
|
private agentActivityListener: ((ctx: MemoryUpdateCtx) => void) | undefined;
|
|
181
183
|
private settingsUpdateListener: ((ctx: MemoryUpdateCtx, settings: SettingsUpdate) => void) | undefined;
|
|
@@ -277,6 +279,7 @@ export class Runtime {
|
|
|
277
279
|
this.observerBacklogBlocking = false;
|
|
278
280
|
this.summarizerInFlight = false;
|
|
279
281
|
this.summarizerPromise = null;
|
|
282
|
+
this.summarizerRecheckPending = false;
|
|
280
283
|
this.reviewInFlight = false;
|
|
281
284
|
this.reviewPromise = null;
|
|
282
285
|
this.compactInFlight = false;
|