@matthewfl/pi-contemplator 0.1.6 → 0.1.8
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 +6 -6
- package/src/agents/contemplator/agent.ts +199 -49
- package/src/agents/contemplator/prompts.ts +1 -1
- package/src/commands/contemplator-view.ts +1 -1
- package/src/commands/settings.ts +1 -3
- package/src/commands/status.ts +1 -1
- package/src/config.ts +0 -3
- package/src/hooks/consolidation-trigger.ts +2 -1
- package/src/runtime.ts +2 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matthewfl/pi-contemplator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"test": "npm run test:unit && npm run test:e2e",
|
|
43
43
|
"test:unit": "vitest run",
|
|
44
|
-
"test:e2e": "node tests/e2e/rpc-contemplator.mjs && node tests/e2e/rpc-summarizer.mjs && node tests/e2e/rpc-delivery.mjs && node tests/e2e/rpc-restore-review.mjs && node tests/e2e/rpc-compaction.mjs && node tests/e2e/rpc-compaction-resilience.mjs && node tests/e2e/rpc-memory-edges.mjs && node tests/e2e/rpc-observer-length.mjs && node tests/e2e/rpc-routing-isolation.mjs"
|
|
44
|
+
"test:e2e": "node tests/e2e/rpc-contemplator.mjs && node tests/e2e/rpc-contemplator-history.mjs && node tests/e2e/rpc-summarizer.mjs && node tests/e2e/rpc-delivery.mjs && node tests/e2e/rpc-restore-review.mjs && node tests/e2e/rpc-compaction.mjs && node tests/e2e/rpc-compaction-resilience.mjs && node tests/e2e/rpc-memory-edges.mjs && node tests/e2e/rpc-observer-length.mjs && node tests/e2e/rpc-routing-isolation.mjs"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@earendil-works/pi-agent-core": "*",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"@earendil-works/pi-tui": "*"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@earendil-works/pi-agent-core": "^0.84.
|
|
54
|
-
"@earendil-works/pi-ai": "^0.84.
|
|
55
|
-
"@earendil-works/pi-coding-agent": "^0.84.
|
|
56
|
-
"@earendil-works/pi-tui": "^0.84.
|
|
53
|
+
"@earendil-works/pi-agent-core": "^0.84.4",
|
|
54
|
+
"@earendil-works/pi-ai": "^0.84.4",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "^0.84.4",
|
|
56
|
+
"@earendil-works/pi-tui": "^0.84.4",
|
|
57
57
|
"@types/node": "^22.0.0",
|
|
58
58
|
"typebox": "^1.1.38",
|
|
59
59
|
"typescript": "^5.6.0",
|
|
@@ -2,7 +2,7 @@ import { agentLoop, type AgentContext, type AgentLoopConfig, type AgentMessage,
|
|
|
2
2
|
import { Type, type Message, type Model } from "@earendil-works/pi-ai";
|
|
3
3
|
import type { Static } from "typebox";
|
|
4
4
|
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
5
|
-
import { generateSummaryWithUsage } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { estimateTokens as estimateAgentMessageTokens, generateSummaryWithUsage } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { Box, Text } from "@earendil-works/pi-tui";
|
|
8
8
|
import { agentActiveTimeMs, assistantOutputTokens, assistantToolCallCount, fullProjection, isReviewRequestEntry, isReviewResultEntry, OM_AGENT_ACTIVITY, OM_REVIEWER_MESSAGE, OM_REVIEWER_NOTICE, OM_REVIEWER_STATE, OM_REVIEW_REQUEST, OM_REVIEW_RESULT, rawTokensSinceObservationCoverage, recallMemorySources, type Entry, type ReviewResult, type StructuralReviewRequest } from "../../session-ledger/index.js";
|
|
@@ -19,9 +19,68 @@ import { buildContemplatorSystemPrompt } from "./prompts.js";
|
|
|
19
19
|
import { runStructuralReview } from "../reviewer/agent.js";
|
|
20
20
|
import { createWorkerStallWatchdog } from "../../worker-watchdog.js";
|
|
21
21
|
|
|
22
|
+
const CONTEMPLATOR_HISTORY_FALLBACK_TRIGGER_TOKENS = 20_000;
|
|
23
|
+
const CONTEMPLATOR_HISTORY_MAX_TRIGGER_TOKENS = 40_000;
|
|
24
|
+
const CONTEMPLATOR_HISTORY_CONTEXT_FRACTION = 0.25;
|
|
25
|
+
const CONTEMPLATOR_HISTORY_KEEP_RECENT_TOKENS = 12_000;
|
|
26
|
+
const CONTEMPLATOR_HISTORY_SUMMARY_RESERVE_TOKENS = 16_000;
|
|
27
|
+
const CONTEMPLATOR_HISTORY_SUMMARY_INSTRUCTIONS = "This is an older prefix of a private contemplator transcript. Be concise. Preserve durable user intent, decisions, evidence, unresolved reasoning gaps, and review/probe outcomes. Newer transcript messages are retained verbatim after this checkpoint.";
|
|
28
|
+
|
|
29
|
+
function contemplatorMessageTokens(message: AgentMessage): number {
|
|
30
|
+
try {
|
|
31
|
+
return Math.max(1, estimateAgentMessageTokens(message));
|
|
32
|
+
} catch {
|
|
33
|
+
return Math.max(1, Math.ceil(JSON.stringify(message).length / 4));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function contemplatorHistoryTokens(history: readonly AgentMessage[]): number {
|
|
38
|
+
return history.reduce((total, message) => total + contemplatorMessageTokens(message), 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function contemplatorHistoryTriggerTokens(model: Model<any>): number {
|
|
42
|
+
const contextWindow = Number(model.contextWindow);
|
|
43
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0) return CONTEMPLATOR_HISTORY_FALLBACK_TRIGGER_TOKENS;
|
|
44
|
+
return Math.max(1, Math.min(CONTEMPLATOR_HISTORY_MAX_TRIGGER_TOKENS, Math.floor(contextWindow * CONTEMPLATOR_HISTORY_CONTEXT_FRACTION)));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Return a user-message boundary that leaves approximately targetTokens recent. */
|
|
48
|
+
function recentHistoryStart(history: readonly AgentMessage[], entryIds: readonly (string | undefined)[], targetTokens: number): number | undefined {
|
|
49
|
+
let tokens = 0;
|
|
50
|
+
let start = history.length;
|
|
51
|
+
while (start > 0 && tokens < targetTokens) {
|
|
52
|
+
start--;
|
|
53
|
+
tokens += contemplatorMessageTokens(history[start]);
|
|
54
|
+
}
|
|
55
|
+
while (start > 0 && history[start].role !== "user") start--;
|
|
56
|
+
// Retained messages are durable pointers, not copied payloads. If a legacy
|
|
57
|
+
// snapshot supplied messages without entry ids, retain only the newer complete
|
|
58
|
+
// updates whose original om.contemplator.message entries can be referenced.
|
|
59
|
+
let lastMissingId = -1;
|
|
60
|
+
for (let index = start; index < entryIds.length; index++) {
|
|
61
|
+
if (entryIds[index] === undefined) lastMissingId = index;
|
|
62
|
+
}
|
|
63
|
+
if (lastMissingId >= 0) {
|
|
64
|
+
start = lastMissingId + 1;
|
|
65
|
+
while (start < history.length && history[start].role !== "user") start++;
|
|
66
|
+
}
|
|
67
|
+
return start > 0 && start < history.length ? start : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Pick a smaller complete oldest prefix after a length-truncated first attempt. */
|
|
71
|
+
function smallerPrefixEnd(history: readonly AgentMessage[], initialEnd: number): number | undefined {
|
|
72
|
+
const target = Math.max(1, Math.floor(contemplatorHistoryTokens(history.slice(0, initialEnd)) / 2));
|
|
73
|
+
let tokens = 0;
|
|
74
|
+
for (let index = 0; index < initialEnd; index++) {
|
|
75
|
+
tokens += contemplatorMessageTokens(history[index]);
|
|
76
|
+
const next = index + 1;
|
|
77
|
+
if (tokens >= target && next < initialEnd && history[next].role === "user") return next;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
22
82
|
interface PendingUpdate {
|
|
23
83
|
observations: string[];
|
|
24
|
-
summaries: string[];
|
|
25
84
|
reviews: string[];
|
|
26
85
|
mainAgentOutputTokens: number;
|
|
27
86
|
mainAgentToolCalls: number;
|
|
@@ -212,6 +271,8 @@ export function createRequestReviewTool(
|
|
|
212
271
|
|
|
213
272
|
export class Contemplator {
|
|
214
273
|
private history: AgentMessage[] = [];
|
|
274
|
+
/** Ledger entry id for each private-history message; used by compact checkpoints to retain a suffix without copying it. */
|
|
275
|
+
private historyEntryIds: Array<string | undefined> = [];
|
|
215
276
|
private pending: PendingUpdate | undefined;
|
|
216
277
|
private running = false;
|
|
217
278
|
/** Invalidates stale/hard-timed-out flush finalizers across session changes. */
|
|
@@ -219,7 +280,6 @@ export class Contemplator {
|
|
|
219
280
|
/** Bounds retries of one poisoned memory update so future updates can run. */
|
|
220
281
|
private consecutiveFlushFailures = 0;
|
|
221
282
|
private seenObservationIds = new Set<string>();
|
|
222
|
-
private seenSummaryIds = new Set<string>();
|
|
223
283
|
private seenReviewIds = new Set<string>();
|
|
224
284
|
private inFlightReviewKeys = new Set<string>();
|
|
225
285
|
private inFlightReviewIds = new Set<string>();
|
|
@@ -302,9 +362,9 @@ export class Contemplator {
|
|
|
302
362
|
this.consecutiveFlushFailures = 0;
|
|
303
363
|
this.agentActiveSince = undefined;
|
|
304
364
|
this.history = [];
|
|
365
|
+
this.historyEntryIds = [];
|
|
305
366
|
this.pending = undefined;
|
|
306
367
|
this.seenObservationIds.clear();
|
|
307
|
-
this.seenSummaryIds.clear();
|
|
308
368
|
this.seenReviewIds.clear();
|
|
309
369
|
this.inFlightReviewKeys.clear();
|
|
310
370
|
this.inFlightReviewIds.clear();
|
|
@@ -321,7 +381,6 @@ export class Contemplator {
|
|
|
321
381
|
this.runtime.contemplatorState = {
|
|
322
382
|
running: false,
|
|
323
383
|
pendingObservations: 0,
|
|
324
|
-
pendingSummaries: 0,
|
|
325
384
|
pendingReviews: 0,
|
|
326
385
|
responsesSinceRun: 0,
|
|
327
386
|
waitingFor: "idle",
|
|
@@ -431,7 +490,6 @@ export class Contemplator {
|
|
|
431
490
|
...this.runtime.contemplatorState,
|
|
432
491
|
running: this.running,
|
|
433
492
|
pendingObservations: this.pending?.observations.length ?? 0,
|
|
434
|
-
pendingSummaries: this.pending?.summaries.length ?? 0,
|
|
435
493
|
pendingReviews: this.pending?.reviews.length ?? 0,
|
|
436
494
|
responsesSinceRun: this.turnsSinceRun,
|
|
437
495
|
waitingFor,
|
|
@@ -446,6 +504,8 @@ export class Contemplator {
|
|
|
446
504
|
if (this.running && !resetTracking) return;
|
|
447
505
|
if (tipId === this.restoredTipId && !resetTracking) return;
|
|
448
506
|
this.history = [];
|
|
507
|
+
this.historyEntryIds = [];
|
|
508
|
+
const historyMessagesByEntryId = new Map<string, AgentMessage>();
|
|
449
509
|
let resetProjection: ReturnType<typeof fullProjection> | undefined;
|
|
450
510
|
if (resetTracking) {
|
|
451
511
|
this.deliveredProbeIds.clear();
|
|
@@ -457,7 +517,6 @@ export class Contemplator {
|
|
|
457
517
|
this.reviewerSessions.clear();
|
|
458
518
|
resetProjection = fullProjection(entries);
|
|
459
519
|
this.seenObservationIds.clear();
|
|
460
|
-
this.seenSummaryIds.clear();
|
|
461
520
|
this.seenReviewIds.clear();
|
|
462
521
|
this.pending = undefined;
|
|
463
522
|
this.turnsSinceRun = 0;
|
|
@@ -465,7 +524,6 @@ export class Contemplator {
|
|
|
465
524
|
this.runtime.contemplatorState = {
|
|
466
525
|
running: false,
|
|
467
526
|
pendingObservations: 0,
|
|
468
|
-
pendingSummaries: 0,
|
|
469
527
|
pendingReviews: 0,
|
|
470
528
|
responsesSinceRun: 0,
|
|
471
529
|
waitingFor: "idle",
|
|
@@ -475,14 +533,27 @@ export class Contemplator {
|
|
|
475
533
|
for (const entry of entries) {
|
|
476
534
|
if (entry.customType === CONTEMPLATOR_STATE && entry.data && typeof entry.data === "object") {
|
|
477
535
|
const state = entry.data as { history?: unknown };
|
|
478
|
-
if (Array.isArray(state.history))
|
|
536
|
+
if (Array.isArray(state.history)) {
|
|
537
|
+
this.history = state.history.filter((message): message is AgentMessage => !!message && typeof message === "object");
|
|
538
|
+
this.historyEntryIds = this.history.map(() => undefined);
|
|
539
|
+
}
|
|
479
540
|
}
|
|
480
541
|
if (entry.customType === CONTEMPLATOR_MESSAGE && entry.data && typeof entry.data === "object") {
|
|
481
|
-
const data = entry.data as { message?: unknown; compacted?: unknown };
|
|
542
|
+
const data = entry.data as { message?: unknown; compacted?: unknown; retainedMessageEntryIds?: unknown };
|
|
482
543
|
const message = data.message;
|
|
483
544
|
if (message && typeof message === "object") {
|
|
484
|
-
|
|
485
|
-
|
|
545
|
+
const typedMessage = message as AgentMessage;
|
|
546
|
+
historyMessagesByEntryId.set(entry.id, typedMessage);
|
|
547
|
+
if (data.compacted === true) {
|
|
548
|
+
const retainedIds = Array.isArray(data.retainedMessageEntryIds)
|
|
549
|
+
? data.retainedMessageEntryIds.filter((id): id is string => typeof id === "string" && historyMessagesByEntryId.has(id))
|
|
550
|
+
: [];
|
|
551
|
+
this.history = [typedMessage, ...retainedIds.map((id) => historyMessagesByEntryId.get(id)!)];
|
|
552
|
+
this.historyEntryIds = [entry.id, ...retainedIds];
|
|
553
|
+
} else {
|
|
554
|
+
this.history.push(typedMessage);
|
|
555
|
+
this.historyEntryIds.push(entry.id);
|
|
556
|
+
}
|
|
486
557
|
}
|
|
487
558
|
}
|
|
488
559
|
if (entry.customType === OM_REVIEWER_STATE && entry.data && typeof entry.data === "object") {
|
|
@@ -547,12 +618,10 @@ export class Contemplator {
|
|
|
547
618
|
for (const id of memoryReferenceIds(text)) coveredIds.add(id);
|
|
548
619
|
}
|
|
549
620
|
this.seenObservationIds = new Set(resetProjection.observations.filter((item) => coveredIds.has(item.id)).map((item) => item.id));
|
|
550
|
-
this.seenSummaryIds = new Set(resetProjection.summaries.filter((item) => coveredIds.has(item.id)).map((item) => item.id));
|
|
551
621
|
this.seenReviewIds = new Set((resetProjection.reviews ?? []).filter((item) => coveredIds.has(item.id)).map((item) => item.id));
|
|
552
622
|
const unprocessedObservations = resetProjection.observations.length - this.seenObservationIds.size;
|
|
553
|
-
const unprocessedSummaries = resetProjection.summaries.length - this.seenSummaryIds.size;
|
|
554
623
|
const unprocessedReviews = (resetProjection.reviews?.length ?? 0) - this.seenReviewIds.size;
|
|
555
|
-
if (unprocessedReviews > 0 || unprocessedObservations >= this.runtime.config.contemplatorMinNewObservations
|
|
624
|
+
if (unprocessedReviews > 0 || unprocessedObservations >= this.runtime.config.contemplatorMinNewObservations) {
|
|
556
625
|
this.turnsSinceRun = this.runtime.config.contemplatorMinTurns;
|
|
557
626
|
}
|
|
558
627
|
}
|
|
@@ -607,31 +676,24 @@ export class Contemplator {
|
|
|
607
676
|
}
|
|
608
677
|
const projection = fullProjection(branchEntries);
|
|
609
678
|
const observations = projection.observations.map((item) => `[${item.id}] ${item.content}`);
|
|
610
|
-
const summaries = projection.summaries.map((item) => `[${item.id}] ${item.content}`);
|
|
611
679
|
const reviews = projection.reviews ?? [];
|
|
612
680
|
const newObservationItems = projection.observations.filter((item) => !this.seenObservationIds.has(item.id));
|
|
613
|
-
const newSummaryItems = projection.summaries.filter((item) => !this.seenSummaryIds.has(item.id));
|
|
614
681
|
const newReviewItems = reviews.filter((item) => !this.seenReviewIds.has(item.id));
|
|
615
682
|
const newObservations = newObservationItems.map((item) => `[${item.id}] ${item.content}`);
|
|
616
|
-
const newSummaries = newSummaryItems.map((item) => `[${item.id}] ${item.content}`);
|
|
617
683
|
const newReviews = newReviewItems.map(reviewSummaryLine);
|
|
618
684
|
for (const item of newObservationItems) this.seenObservationIds.add(item.id);
|
|
619
|
-
for (const item of newSummaryItems) this.seenSummaryIds.add(item.id);
|
|
620
685
|
for (const item of newReviewItems) this.seenReviewIds.add(item.id);
|
|
621
686
|
debugLog("contemplator.update", {
|
|
622
687
|
observationCount: observations.length,
|
|
623
|
-
summaryCount: summaries.length,
|
|
624
688
|
newObservationCount: newObservations.length,
|
|
625
|
-
newSummaryCount: newSummaries.length,
|
|
626
689
|
newReviewCount: newReviews.length,
|
|
627
690
|
turnsSinceRun: this.turnsSinceRun,
|
|
628
691
|
pending: this.pending !== undefined,
|
|
629
692
|
running: this.running,
|
|
630
693
|
});
|
|
631
|
-
if (newObservations.length > 0 ||
|
|
694
|
+
if (newObservations.length > 0 || newReviews.length > 0) {
|
|
632
695
|
this.pending = {
|
|
633
696
|
observations: mergeMemoryLines(this.pending?.observations ?? [], newObservations),
|
|
634
|
-
summaries: mergeMemoryLines(this.pending?.summaries ?? [], newSummaries),
|
|
635
697
|
reviews: mergeMemoryLines(this.pending?.reviews ?? [], newReviews),
|
|
636
698
|
mainAgentOutputTokens: assistantOutputTokens(branchEntries),
|
|
637
699
|
mainAgentToolCalls: assistantToolCallCount(branchEntries),
|
|
@@ -648,7 +710,7 @@ export class Contemplator {
|
|
|
648
710
|
this.pending.mainAgentOutputTokens = assistantOutputTokens(branchEntries);
|
|
649
711
|
this.pending.mainAgentToolCalls = assistantToolCallCount(branchEntries);
|
|
650
712
|
this.pending.mainAgentActiveTimeMs = agentActiveTimeMs(branchEntries);
|
|
651
|
-
const enoughMemories = this.pending.reviews.length > 0 || this.pending.observations.length >= this.runtime.config.contemplatorMinNewObservations
|
|
713
|
+
const enoughMemories = this.pending.reviews.length > 0 || this.pending.observations.length >= this.runtime.config.contemplatorMinNewObservations;
|
|
652
714
|
if (this.probeCooldownPendingIds.size > 0) {
|
|
653
715
|
this.publishState("probe");
|
|
654
716
|
debugLog("contemplator.waiting", { reason: "probe_delivery", pendingProbeCount: this.probeCooldownPendingIds.size });
|
|
@@ -661,14 +723,12 @@ export class Contemplator {
|
|
|
661
723
|
turnsSinceRun: this.turnsSinceRun,
|
|
662
724
|
minTurns: this.runtime.config.contemplatorMinTurns,
|
|
663
725
|
minNewObservations: this.runtime.config.contemplatorMinNewObservations,
|
|
664
|
-
minNewSummaries: this.runtime.config.contemplatorMinNewSummaries,
|
|
665
726
|
});
|
|
666
727
|
return;
|
|
667
728
|
}
|
|
668
729
|
this.publishState(this.running ? "running" : "ready");
|
|
669
730
|
debugLog("contemplator.triggered", {
|
|
670
731
|
pendingObservationCount: this.pending.observations.length,
|
|
671
|
-
pendingSummaryCount: this.pending.summaries.length,
|
|
672
732
|
pendingReviewCount: this.pending.reviews.length,
|
|
673
733
|
turnsSinceRun: this.turnsSinceRun,
|
|
674
734
|
});
|
|
@@ -705,7 +765,6 @@ export class Contemplator {
|
|
|
705
765
|
this.publishState("running", { lastStartedAt: startedAt, lastError: undefined });
|
|
706
766
|
debugLog("contemplator.start", {
|
|
707
767
|
newObservationCount: update.observations.length,
|
|
708
|
-
newSummaryCount: update.summaries.length,
|
|
709
768
|
newReviewCount: update.reviews.length,
|
|
710
769
|
historyMessageCount: this.history.length,
|
|
711
770
|
});
|
|
@@ -727,14 +786,13 @@ export class Contemplator {
|
|
|
727
786
|
const pending = this.pending as PendingUpdate | undefined;
|
|
728
787
|
this.pending = {
|
|
729
788
|
observations: mergeMemoryLines(pending?.observations ?? [], update.observations),
|
|
730
|
-
summaries: mergeMemoryLines(pending?.summaries ?? [], update.summaries),
|
|
731
789
|
reviews: mergeMemoryLines(pending?.reviews ?? [], update.reviews),
|
|
732
790
|
mainAgentOutputTokens: update.mainAgentOutputTokens,
|
|
733
791
|
mainAgentToolCalls: update.mainAgentToolCalls,
|
|
734
792
|
mainAgentActiveTimeMs: update.mainAgentActiveTimeMs,
|
|
735
793
|
};
|
|
736
794
|
} else {
|
|
737
|
-
debugLog("contemplator.poisoned_update_released", { reason: resolved.reason, observationCount: update.observations.length,
|
|
795
|
+
debugLog("contemplator.poisoned_update_released", { reason: resolved.reason, observationCount: update.observations.length, reviewCount: update.reviews.length });
|
|
738
796
|
this.consecutiveFlushFailures = 0;
|
|
739
797
|
}
|
|
740
798
|
this.turnsSinceRun = 0; // Back off until fresh primary responses arrive; never retry every checkpoint.
|
|
@@ -759,7 +817,6 @@ export class Contemplator {
|
|
|
759
817
|
const reviewerEnabled = this.runtime.config.reviewerEnabled;
|
|
760
818
|
const updateSections: string[] = [];
|
|
761
819
|
if (update.observations.length > 0) updateSections.push(`OBSERVATIONS:\n${update.observations.join("\n")}`);
|
|
762
|
-
if (update.summaries.length > 0) updateSections.push(`SUMMARIES:\n${update.summaries.join("\n")}`);
|
|
763
820
|
if (update.reviews.length > 0) updateSections.push(`REVIEWS:\n${update.reviews.join("\n")}`);
|
|
764
821
|
const updateBody = updateSections.length > 0 ? updateSections.join("\n\n") : "(no new memories)";
|
|
765
822
|
const finalActionNames = reviewerEnabled
|
|
@@ -883,7 +940,7 @@ export class Contemplator {
|
|
|
883
940
|
for (const message of runMessages) {
|
|
884
941
|
if (message.role !== "user" && message.role !== "assistant") continue;
|
|
885
942
|
this.history.push(message);
|
|
886
|
-
this.
|
|
943
|
+
this.historyEntryIds.push(this.appendContemplatorHistoryEntry(ctx, { version: 1, message }));
|
|
887
944
|
promptPersisted = true;
|
|
888
945
|
this.markTipPersisted(ctx);
|
|
889
946
|
}
|
|
@@ -913,7 +970,13 @@ export class Contemplator {
|
|
|
913
970
|
}
|
|
914
971
|
if (sessionGeneration === this.sessionGeneration) {
|
|
915
972
|
workerWatchdog.progress();
|
|
916
|
-
|
|
973
|
+
try {
|
|
974
|
+
await workerWatchdog.race(this.compactHistory(ctx, resolved.model as Model<any>, resolved.apiKey, resolved.headers, sessionGeneration, flushEpoch, workerWatchdog.signal));
|
|
975
|
+
} catch (compactionError) {
|
|
976
|
+
// The intervention and its durable messages are already complete. Private
|
|
977
|
+
// history maintenance must never relabel that successful work as failed.
|
|
978
|
+
debugLog("contemplator.compaction_postponed", { reason: compactionError instanceof Error ? compactionError.message : String(compactionError) });
|
|
979
|
+
}
|
|
917
980
|
}
|
|
918
981
|
} catch (error) {
|
|
919
982
|
failed = true;
|
|
@@ -925,14 +988,13 @@ export class Contemplator {
|
|
|
925
988
|
const pending = this.pending as PendingUpdate | undefined;
|
|
926
989
|
this.pending = {
|
|
927
990
|
observations: mergeMemoryLines(pending?.observations ?? [], update.observations),
|
|
928
|
-
summaries: mergeMemoryLines(pending?.summaries ?? [], update.summaries),
|
|
929
991
|
reviews: mergeMemoryLines(pending?.reviews ?? [], update.reviews),
|
|
930
992
|
mainAgentOutputTokens: update.mainAgentOutputTokens,
|
|
931
993
|
mainAgentToolCalls: update.mainAgentToolCalls,
|
|
932
994
|
mainAgentActiveTimeMs: update.mainAgentActiveTimeMs,
|
|
933
995
|
};
|
|
934
996
|
} else {
|
|
935
|
-
debugLog("contemplator.poisoned_update_released", { reason: failureMessage, observationCount: update.observations.length,
|
|
997
|
+
debugLog("contemplator.poisoned_update_released", { reason: failureMessage, observationCount: update.observations.length, reviewCount: update.reviews.length });
|
|
936
998
|
this.consecutiveFlushFailures = 0;
|
|
937
999
|
}
|
|
938
1000
|
this.turnsSinceRun = 0; // Back off until fresh primary responses arrive; never retry every checkpoint.
|
|
@@ -950,8 +1012,7 @@ export class Contemplator {
|
|
|
950
1012
|
const waitingForProbe = this.probeCooldownPendingIds.size > 0;
|
|
951
1013
|
const pendingHasEnoughMemories = this.pending !== undefined && (
|
|
952
1014
|
this.pending.reviews.length > 0 ||
|
|
953
|
-
this.pending.observations.length >= this.runtime.config.contemplatorMinNewObservations
|
|
954
|
-
this.pending.summaries.length >= this.runtime.config.contemplatorMinNewSummaries
|
|
1015
|
+
this.pending.observations.length >= this.runtime.config.contemplatorMinNewObservations
|
|
955
1016
|
);
|
|
956
1017
|
const waitingFor = waitingForProbe
|
|
957
1018
|
? "probe"
|
|
@@ -1157,6 +1218,24 @@ export class Contemplator {
|
|
|
1157
1218
|
}
|
|
1158
1219
|
}
|
|
1159
1220
|
|
|
1221
|
+
/**
|
|
1222
|
+
* Append one private-history entry and attribute its synchronous ledger id.
|
|
1223
|
+
* Pi does not return the id from appendEntry(), so prefer the exact data
|
|
1224
|
+
* object and accept a cloned payload only when exactly one matching entry was
|
|
1225
|
+
* appended. Ambiguity returns undefined rather than pointing at another
|
|
1226
|
+
* writer's message.
|
|
1227
|
+
*/
|
|
1228
|
+
private appendContemplatorHistoryEntry(ctx: MemoryUpdateCtx, data: Record<string, unknown>): string | undefined {
|
|
1229
|
+
const branch = ctx.sessionManager.getBranch() as readonly Entry[];
|
|
1230
|
+
const before = branch.length;
|
|
1231
|
+
this.pi.appendEntry(CONTEMPLATOR_MESSAGE, data);
|
|
1232
|
+
const candidates = (ctx.sessionManager.getBranch() as readonly Entry[])
|
|
1233
|
+
.slice(before)
|
|
1234
|
+
.filter((entry) => entry.customType === CONTEMPLATOR_MESSAGE);
|
|
1235
|
+
return candidates.find((entry) => entry.data === data)?.id
|
|
1236
|
+
?? (candidates.length === 1 ? candidates[0].id : undefined);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1160
1239
|
private markTipPersisted(ctx: MemoryUpdateCtx): string | undefined {
|
|
1161
1240
|
this.restoredTipId = (ctx.sessionManager.getBranch() as Entry[]).at(-1)?.id;
|
|
1162
1241
|
return this.restoredTipId;
|
|
@@ -1180,27 +1259,73 @@ export class Contemplator {
|
|
|
1180
1259
|
return own?.id;
|
|
1181
1260
|
}
|
|
1182
1261
|
|
|
1183
|
-
private async compactHistory(model: Model<any>, apiKey: string, headers: Record<string, string> | undefined, sessionGeneration: number, flushEpoch: number): Promise<void> {
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
const
|
|
1262
|
+
private async compactHistory(ctx: MemoryUpdateCtx, model: Model<any>, apiKey: string, headers: Record<string, string> | undefined, sessionGeneration: number, flushEpoch: number, signal?: AbortSignal): Promise<void> {
|
|
1263
|
+
const history = this.history.slice();
|
|
1264
|
+
const historyEntryIds = this.historyEntryIds.slice();
|
|
1265
|
+
const historyTokens = contemplatorHistoryTokens(history);
|
|
1266
|
+
const triggerTokens = contemplatorHistoryTriggerTokens(model);
|
|
1267
|
+
if (history.length < 12 || historyTokens < triggerTokens) return;
|
|
1268
|
+
|
|
1269
|
+
const keepRecentTokens = Math.min(CONTEMPLATOR_HISTORY_KEEP_RECENT_TOKENS, Math.max(1, Math.floor(triggerTokens * 0.5)));
|
|
1270
|
+
const initialPrefixEnd = recentHistoryStart(history, historyEntryIds, keepRecentTokens);
|
|
1271
|
+
if (initialPrefixEnd === undefined) return;
|
|
1272
|
+
const previousMessageCount = history.length;
|
|
1187
1273
|
debugLog("contemplator.compaction_start", {
|
|
1188
1274
|
historyMessageCount: previousMessageCount,
|
|
1189
|
-
|
|
1275
|
+
historyTokens,
|
|
1276
|
+
triggerTokens,
|
|
1277
|
+
keepRecentTokens,
|
|
1278
|
+
prefixMessageCount: initialPrefixEnd,
|
|
1279
|
+
retainedMessageCount: history.length - initialPrefixEnd,
|
|
1190
1280
|
});
|
|
1191
|
-
|
|
1192
|
-
const
|
|
1281
|
+
|
|
1282
|
+
const summarizePrefix = async (prefixEnd: number) => generateSummaryWithUsage(
|
|
1283
|
+
history.slice(0, prefixEnd),
|
|
1284
|
+
model,
|
|
1285
|
+
CONTEMPLATOR_HISTORY_SUMMARY_RESERVE_TOKENS,
|
|
1286
|
+
apiKey,
|
|
1287
|
+
headers,
|
|
1288
|
+
signal,
|
|
1289
|
+
CONTEMPLATOR_HISTORY_SUMMARY_INSTRUCTIONS,
|
|
1290
|
+
);
|
|
1291
|
+
|
|
1292
|
+
let prefixEnd = initialPrefixEnd;
|
|
1293
|
+
let summaryWithUsage: Awaited<ReturnType<typeof generateSummaryWithUsage>>;
|
|
1294
|
+
try {
|
|
1295
|
+
summaryWithUsage = await summarizePrefix(prefixEnd);
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
const failure = error instanceof Error ? error.message : String(error);
|
|
1298
|
+
const fallbackEnd = /generation hit the token cap/i.test(failure)
|
|
1299
|
+
? smallerPrefixEnd(history, initialPrefixEnd)
|
|
1300
|
+
: undefined;
|
|
1301
|
+
if (fallbackEnd === undefined) {
|
|
1302
|
+
debugLog("contemplator.compaction_postponed", { reason: failure, historyTokens, prefixMessageCount: prefixEnd });
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
prefixEnd = fallbackEnd;
|
|
1306
|
+
debugLog("contemplator.compaction_retry_smaller_prefix", { reason: failure, prefixMessageCount: prefixEnd, retainedMessageCount: history.length - prefixEnd });
|
|
1307
|
+
try {
|
|
1308
|
+
summaryWithUsage = await summarizePrefix(prefixEnd);
|
|
1309
|
+
} catch (fallbackError) {
|
|
1310
|
+
debugLog("contemplator.compaction_postponed", {
|
|
1311
|
+
reason: fallbackError instanceof Error ? fallbackError.message : String(fallbackError),
|
|
1312
|
+
historyTokens,
|
|
1313
|
+
prefixMessageCount: prefixEnd,
|
|
1314
|
+
});
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1193
1319
|
this.runtime.recordAgentUsage(summaryWithUsage.usage);
|
|
1194
1320
|
if (sessionGeneration !== this.sessionGeneration || flushEpoch !== this.flushEpoch) {
|
|
1195
1321
|
debugLog("contemplator.compaction_stale", { reason: "session_or_branch_changed" });
|
|
1196
1322
|
return;
|
|
1197
1323
|
}
|
|
1198
|
-
const summary = summaryWithUsage.text;
|
|
1199
1324
|
const summaryModel = model as Model<any> & { api?: unknown; provider?: string; id?: string };
|
|
1200
1325
|
const summaryUsage = summaryWithUsage.usage;
|
|
1201
|
-
|
|
1326
|
+
const summaryMessage = {
|
|
1202
1327
|
role: "assistant",
|
|
1203
|
-
content: [{ type: "text", text: `Previous contemplator context summary:\n${
|
|
1328
|
+
content: [{ type: "text", text: `Previous contemplator context summary:\n${summaryWithUsage.text}` }],
|
|
1204
1329
|
api: summaryModel.api,
|
|
1205
1330
|
provider: summaryModel.provider ?? "unknown",
|
|
1206
1331
|
model: summaryModel.id ?? "contemplator",
|
|
@@ -1214,12 +1339,37 @@ export class Contemplator {
|
|
|
1214
1339
|
},
|
|
1215
1340
|
stopReason: "stop",
|
|
1216
1341
|
timestamp: Date.now(),
|
|
1217
|
-
} as AgentMessage
|
|
1218
|
-
|
|
1342
|
+
} as AgentMessage;
|
|
1343
|
+
// A flush currently owns private-history mutation while this awaits the
|
|
1344
|
+
// model, and session/tree movement invalidates the generation above. Still,
|
|
1345
|
+
// preserve any future append-only writer rather than replacing a stale
|
|
1346
|
+
// snapshot and silently dropping messages. Non-append mutation is unsafe to
|
|
1347
|
+
// merge, so postpone and let a later pass compact the live transcript.
|
|
1348
|
+
const liveHistory = this.history;
|
|
1349
|
+
const liveHistoryEntryIds = this.historyEntryIds;
|
|
1350
|
+
const snapshotIsLivePrefix = liveHistory.length >= history.length
|
|
1351
|
+
&& history.every((message, index) => liveHistory[index] === message && liveHistoryEntryIds[index] === historyEntryIds[index]);
|
|
1352
|
+
if (!snapshotIsLivePrefix) {
|
|
1353
|
+
debugLog("contemplator.compaction_postponed", { reason: "private history changed during compaction" });
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
const retainedMessages = liveHistory.slice(prefixEnd);
|
|
1357
|
+
const retainedMessageEntryIds = liveHistoryEntryIds.slice(prefixEnd).filter((id): id is string => typeof id === "string");
|
|
1358
|
+
if (retainedMessageEntryIds.length !== retainedMessages.length) {
|
|
1359
|
+
debugLog("contemplator.compaction_postponed", { reason: "retained history lacked durable entry ids", retainedMessageCount: retainedMessages.length, retainedReferenceCount: retainedMessageEntryIds.length });
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const checkpoint = { version: 2, compacted: true, message: summaryMessage, retainedMessageEntryIds };
|
|
1363
|
+
const checkpointEntryId = this.appendContemplatorHistoryEntry(ctx, checkpoint);
|
|
1364
|
+
this.history = [summaryMessage, ...retainedMessages];
|
|
1365
|
+
this.historyEntryIds = [checkpointEntryId, ...retainedMessageEntryIds];
|
|
1366
|
+
this.markTipPersisted(ctx);
|
|
1219
1367
|
debugLog("contemplator.compaction_complete", {
|
|
1220
1368
|
previousMessageCount,
|
|
1221
1369
|
newMessageCount: this.history.length,
|
|
1222
|
-
|
|
1370
|
+
prefixMessageCount: prefixEnd,
|
|
1371
|
+
retainedMessageCount: retainedMessages.length,
|
|
1372
|
+
summaryLength: summaryWithUsage.text.length,
|
|
1223
1373
|
});
|
|
1224
1374
|
}
|
|
1225
1375
|
}
|
|
@@ -6,7 +6,7 @@ export function buildContemplatorSystemPrompt(
|
|
|
6
6
|
|
|
7
7
|
Neither you nor the primary agent should be assumed to know the correct solution. You are jointly exploring a problem space from different perspectives. The primary agent interacts with the actual environment and carries out the work. You maintain a longer-term view of the reasoning, evidence, assumptions, alternatives, unresolved questions${reviewerEnabled ? ", and recurring structural patterns" : ""} that emerge over time.
|
|
8
8
|
|
|
9
|
-
You receive incremental observations
|
|
9
|
+
You receive incremental observations about primary-agent activity. Pay extra attention to memories about the user’s intent, priorities, constraints, corrections, and desired outcome. Older summarized memories remain available through search_memories and recall when you need historical context, but summary maintenance is not itself a new event.
|
|
10
10
|
|
|
11
11
|
You see only the memory ledger, not the primary agent’s live activity. Your understanding may be incomplete or slightly stale. Do not infer inactivity, failure, or lack of progress from missing recent results. A result may simply not have reached memory yet.
|
|
12
12
|
|
|
@@ -59,7 +59,7 @@ function renderMessage(message: StoredMessage, compacted: boolean): string {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function liveStateLine(state: ContemplatorRunState): string {
|
|
62
|
-
const pending = `${state.pendingObservations} observations / ${state.
|
|
62
|
+
const pending = `${state.pendingObservations} observations / ${state.pendingReviews} reviews pending`;
|
|
63
63
|
const timing = `Last start: ${state.lastStartedAt === undefined ? "not run this launch" : new Date(state.lastStartedAt).toISOString()} · Last end: ${state.lastCompletedAt === undefined ? "not completed this launch" : new Date(state.lastCompletedAt).toISOString()}`;
|
|
64
64
|
const error = state.lastError ? `\nLast error: ${state.lastError}` : "";
|
|
65
65
|
if (state.running) return `LIVE · running for ${Math.max(0, Math.floor((Date.now() - (state.lastStartedAt ?? Date.now())) / 60_000))}m · ${pending}\n${timing}${error}`;
|
package/src/commands/settings.ts
CHANGED
|
@@ -10,7 +10,7 @@ type ModelRegistryLike = {
|
|
|
10
10
|
getAll(): Array<{ provider: string; id: string }>;
|
|
11
11
|
find?(provider: string, id: string): { contextWindow?: number } | undefined;
|
|
12
12
|
};
|
|
13
|
-
type NumberSetting = "observeAfterTokens" | "compactAfterTokens" | "observerChunkMaxTokens" | "newMemoryPoolMaxTokens" | "oldMemoryPoolTargetTokens" | "agentMaxTurns" | "contemplatorMinNewObservations" | "
|
|
13
|
+
type NumberSetting = "observeAfterTokens" | "compactAfterTokens" | "observerChunkMaxTokens" | "newMemoryPoolMaxTokens" | "oldMemoryPoolTargetTokens" | "agentMaxTurns" | "contemplatorMinNewObservations" | "contemplatorMinTurns" | "summarizerRetriggerTokens" | "summarizerSamplingThresholdTokens";
|
|
14
14
|
type BooleanSetting = "contemplatorEnabled" | "showContemplatorMessages" | "reviewerEnabled" | "summarizerEnabled" | "compactionObserverEnabled" | "showWorkerNotifications" | "passive" | "debugLog";
|
|
15
15
|
|
|
16
16
|
function modelLabel(model: ConfiguredModel | undefined): string {
|
|
@@ -216,7 +216,6 @@ export function registerSettingsCommand(pi: ExtensionAPI, runtime: Runtime): voi
|
|
|
216
216
|
`Contemplator model: ${hasOverride(settings, "contemplatorModel") ? modelLabel(runtime.config.contemplatorModel) : `${modelLabel(runtime.getDefaultConfig().contemplatorModel)} (default)`}`,
|
|
217
217
|
`Show contemplator messages: ${scalarLabel(runtime, "showContemplatorMessages")}`,
|
|
218
218
|
`Contemplator new-observation trigger (count): ${scalarLabel(runtime, "contemplatorMinNewObservations")}`,
|
|
219
|
-
`Contemplator new-summary trigger (count): ${scalarLabel(runtime, "contemplatorMinNewSummaries")}`,
|
|
220
219
|
`Contemplator response spacing (count): ${scalarLabel(runtime, "contemplatorMinTurns")}`,
|
|
221
220
|
`Summarizer enabled: ${scalarLabel(runtime, "summarizerEnabled")}`,
|
|
222
221
|
`New memory pool protection budget (tokens): ${scalarLabel(runtime, "newMemoryPoolMaxTokens")}`,
|
|
@@ -272,7 +271,6 @@ export function registerSettingsCommand(pi: ExtensionAPI, runtime: Runtime): voi
|
|
|
272
271
|
["New memory pool protection budget (tokens):", "newMemoryPoolMaxTokens", "New memory pool protection budget (tokens)"],
|
|
273
272
|
["Old memory pool target (tokens, advisory):", "oldMemoryPoolTargetTokens", "Old memory pool target (tokens, advisory)"],
|
|
274
273
|
["Contemplator new-observation trigger (count):", "contemplatorMinNewObservations", "Contemplator new-observation trigger (count)"],
|
|
275
|
-
["Contemplator new-summary trigger (count):", "contemplatorMinNewSummaries", "Contemplator new-summary trigger (count)"],
|
|
276
274
|
["Contemplator response spacing (count):", "contemplatorMinTurns", "Contemplator response spacing (count)"],
|
|
277
275
|
["Summarizer old-pool retrigger growth (tokens):", "summarizerRetriggerTokens", "Summarizer old-pool retrigger growth (tokens)"],
|
|
278
276
|
["Summarizer input cap before sampling (tokens):", "summarizerSamplingThresholdTokens", "Summarizer input cap before sampling (tokens)"],
|
package/src/commands/status.ts
CHANGED
|
@@ -130,7 +130,7 @@ export function registerStatusCommand(pi: ExtensionAPI, runtime: Runtime): void
|
|
|
130
130
|
`Cumulative agent time: ${formatDuration(agentActiveTimeMs(entries))}`,
|
|
131
131
|
`Observe source during compaction: ${runtime.config.compactionObserverEnabled === false ? "disabled" : "enabled"}`,
|
|
132
132
|
`Contemplator: ${runtime.config.contemplatorEnabled ? "enabled" : "disabled"}`,
|
|
133
|
-
`Contemplator trigger: ${runtime.contemplatorState.pendingObservations} observations / ${runtime.contemplatorState.
|
|
133
|
+
`Contemplator trigger: ${runtime.contemplatorState.pendingObservations} observations / ${runtime.contemplatorState.pendingReviews} reviews pending; ${runtime.contemplatorState.responsesSinceRun} / ${runtime.config.contemplatorMinTurns} primary responses; ${contemplatorWaitingLabel(runtime.contemplatorState.waitingFor)}`,
|
|
134
134
|
`Contemplator model: ${runtime.config.contemplatorModel ? `${runtime.config.contemplatorModel.provider}/${runtime.config.contemplatorModel.id}` : "current session model"}`,
|
|
135
135
|
`Contemplator messages: ${runtime.config.showContemplatorMessages ? "visible" : "hidden"}`,
|
|
136
136
|
`Structural reviewer: ${runtime.config.reviewerEnabled === false ? "disabled" : "enabled"}`,
|
package/src/config.ts
CHANGED
|
@@ -60,7 +60,6 @@ export interface Config {
|
|
|
60
60
|
/** Optional model override used only by short-lived structural reviewers. */
|
|
61
61
|
reviewerModel?: ConfiguredModel;
|
|
62
62
|
contemplatorMinNewObservations: number;
|
|
63
|
-
contemplatorMinNewSummaries: number;
|
|
64
63
|
/** Minimum primary-model responses after contemplator completion, or after delivery of its probe, before the next run. */
|
|
65
64
|
contemplatorMinTurns: number;
|
|
66
65
|
/** Stateless loss-aware summarizer for the old memory pool. */
|
|
@@ -87,7 +86,6 @@ export const DEFAULTS: Config = {
|
|
|
87
86
|
showContemplatorMessages: true,
|
|
88
87
|
reviewerEnabled: true,
|
|
89
88
|
contemplatorMinNewObservations: 8,
|
|
90
|
-
contemplatorMinNewSummaries: 1,
|
|
91
89
|
contemplatorMinTurns: 10,
|
|
92
90
|
summarizerEnabled: true,
|
|
93
91
|
summarizerRetriggerTokens: 2_000,
|
|
@@ -210,7 +208,6 @@ function normalizeSettingsConfig(value: Record<string, unknown>): Partial<Config
|
|
|
210
208
|
"oldMemoryPoolTargetTokens",
|
|
211
209
|
"agentMaxTurns",
|
|
212
210
|
"contemplatorMinNewObservations",
|
|
213
|
-
"contemplatorMinNewSummaries",
|
|
214
211
|
"contemplatorMinTurns",
|
|
215
212
|
"summarizerRetriggerTokens",
|
|
216
213
|
"summarizerSamplingThresholdTokens",
|
|
@@ -393,7 +393,8 @@ export function scheduleSummarizer(pi: ExtensionAPI, runtime: Runtime, ctx: Cons
|
|
|
393
393
|
runtime.lastSummarizerRun = { ...runtime.lastSummarizerRun!, status: "completed", summary };
|
|
394
394
|
debugLog("summarizer.appended", { summaries: result.commit.summaries.length, consumed: result.commit.metrics.consumedMemoryCount, sampled: result.sample?.sampled ?? false });
|
|
395
395
|
if (shouldNotifyWorker(runtime, ctx)) ctx.ui?.notify(`pi-contemplator: summarizer completed — ${summary}`, "info");
|
|
396
|
-
|
|
396
|
+
// Summaries compact older memories; they are not new events and must not
|
|
397
|
+
// wake or enter the contemplator's incremental update stream.
|
|
397
398
|
} else {
|
|
398
399
|
successfullyCompleted = true;
|
|
399
400
|
runtime.lastSummarizerRun = { ...runtime.lastSummarizerRun!, status: "completed", summary: "No safe summaries were created." };
|
package/src/runtime.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type SessionSettings = Partial<Pick<Config,
|
|
|
25
25
|
| "compactAfterTokensMode" | "compactAfterTokensRatio"
|
|
26
26
|
| "newMemoryPoolMaxTokens" | "oldMemoryPoolTargetTokens" | "agentMaxTurns"
|
|
27
27
|
| "showWorkerNotifications" | "passive" | "compactionObserverEnabled" | "contemplatorEnabled" | "showContemplatorMessages" | "reviewerEnabled"
|
|
28
|
-
| "contemplatorMinNewObservations" | "
|
|
28
|
+
| "contemplatorMinNewObservations" | "contemplatorMinTurns"
|
|
29
29
|
| "summarizerEnabled" | "summarizerRetriggerTokens" | "summarizerSamplingThresholdTokens"
|
|
30
30
|
| "debugLog"
|
|
31
31
|
>> & {
|
|
@@ -80,7 +80,6 @@ export interface SummarizerRunView {
|
|
|
80
80
|
export interface ContemplatorRunState {
|
|
81
81
|
running: boolean;
|
|
82
82
|
pendingObservations: number;
|
|
83
|
-
pendingSummaries: number;
|
|
84
83
|
pendingReviews: number;
|
|
85
84
|
/** Completed primary-model responses since the current completion/probe-delivery spacing anchor. */
|
|
86
85
|
responsesSinceRun: number;
|
|
@@ -140,7 +139,7 @@ export function computeSessionSettings(entries: readonly unknown[]): SessionSett
|
|
|
140
139
|
const numberKeys = [
|
|
141
140
|
"observeAfterTokens", "observerChunkMaxTokens", "compactAfterTokens",
|
|
142
141
|
"newMemoryPoolMaxTokens", "oldMemoryPoolTargetTokens", "agentMaxTurns",
|
|
143
|
-
"contemplatorMinNewObservations", "
|
|
142
|
+
"contemplatorMinNewObservations", "contemplatorMinTurns",
|
|
144
143
|
"summarizerRetriggerTokens", "summarizerSamplingThresholdTokens",
|
|
145
144
|
] as const;
|
|
146
145
|
for (const key of booleanKeys) if (typeof data[key] === "boolean") restored[key] = data[key];
|
|
@@ -209,7 +208,6 @@ export class Runtime {
|
|
|
209
208
|
contemplatorState: ContemplatorRunState = {
|
|
210
209
|
running: false,
|
|
211
210
|
pendingObservations: 0,
|
|
212
|
-
pendingSummaries: 0,
|
|
213
211
|
pendingReviews: 0,
|
|
214
212
|
responsesSinceRun: 0,
|
|
215
213
|
waitingFor: "idle",
|
|
@@ -299,7 +297,6 @@ export class Runtime {
|
|
|
299
297
|
this.contemplatorState = {
|
|
300
298
|
running: false,
|
|
301
299
|
pendingObservations: 0,
|
|
302
|
-
pendingSummaries: 0,
|
|
303
300
|
pendingReviews: 0,
|
|
304
301
|
responsesSinceRun: 0,
|
|
305
302
|
waitingFor: "idle",
|