pi-observational-memory 2.4.1 → 2.4.3
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/README.md +30 -2
- package/package.json +6 -5
- package/src/commands/status.ts +4 -3
- package/src/commands/view.ts +5 -3
- package/src/compaction.ts +448 -35
- package/src/config.ts +56 -0
- package/src/debug-log.ts +53 -0
- package/src/hooks/compaction-hook.ts +197 -8
- package/src/hooks/compaction-trigger.ts +24 -1
- package/src/hooks/observer-trigger.ts +70 -37
- package/src/model-budget.ts +9 -0
- package/src/observer.ts +24 -6
- package/src/progress.ts +155 -0
- package/src/prompts.ts +23 -22
package/src/observer.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { agentLoop, type AgentContext, type AgentLoopConfig, type AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
|
-
import type { Message, Model } from "@mariozechner/pi-ai";
|
|
2
|
+
import type { Message, Model, ModelThinkingLevel } from "@mariozechner/pi-ai";
|
|
3
3
|
import { Type } from "@mariozechner/pi-ai";
|
|
4
|
-
import type { Static } from "
|
|
4
|
+
import type { Static } from "typebox";
|
|
5
5
|
import { hashId } from "./ids.js";
|
|
6
|
+
import { AGENT_LOOP_MAX_TOKENS, boundedMaxTokens } from "./model-budget.js";
|
|
6
7
|
import { OBSERVER_SYSTEM } from "./prompts.js";
|
|
7
8
|
import { nowTimestamp, truncateRecordContent } from "./serialize.js";
|
|
8
9
|
import type { ObservationRecord, Relevance } from "./types.js";
|
|
@@ -16,6 +17,9 @@ interface RunObserverArgs {
|
|
|
16
17
|
chunk: string;
|
|
17
18
|
allowedSourceEntryIds: string[];
|
|
18
19
|
signal?: AbortSignal;
|
|
20
|
+
agentLoop?: typeof agentLoop;
|
|
21
|
+
maxTurns?: number;
|
|
22
|
+
thinkingLevel?: ModelThinkingLevel;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
const RelevanceSchema = Type.Union([
|
|
@@ -25,11 +29,13 @@ const RelevanceSchema = Type.Union([
|
|
|
25
29
|
Type.Literal("critical"),
|
|
26
30
|
]);
|
|
27
31
|
|
|
32
|
+
export const OBSERVATION_TIMESTAMP_PATTERN = "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$";
|
|
33
|
+
|
|
28
34
|
const RecordObservationsSchema = Type.Object({
|
|
29
35
|
observations: Type.Array(
|
|
30
36
|
Type.Object({
|
|
31
37
|
timestamp: Type.String({
|
|
32
|
-
pattern:
|
|
38
|
+
pattern: OBSERVATION_TIMESTAMP_PATTERN,
|
|
33
39
|
description: "Observation time in local 'YYYY-MM-DD HH:MM' format.",
|
|
34
40
|
}),
|
|
35
41
|
content: Type.String({
|
|
@@ -156,17 +162,29 @@ ${conversation}`;
|
|
|
156
162
|
};
|
|
157
163
|
|
|
158
164
|
const reasoning = (model as { reasoning?: unknown }).reasoning;
|
|
165
|
+
const thinkingLevel = args.thinkingLevel ?? "low";
|
|
166
|
+
const effectiveMaxTurns = args.maxTurns && args.maxTurns > 0 ? args.maxTurns : undefined;
|
|
167
|
+
let turnCount = 0;
|
|
159
168
|
const config: AgentLoopConfig = {
|
|
160
169
|
model,
|
|
161
170
|
apiKey,
|
|
162
171
|
headers,
|
|
163
|
-
maxTokens:
|
|
172
|
+
maxTokens: boundedMaxTokens(model, AGENT_LOOP_MAX_TOKENS),
|
|
164
173
|
convertToLlm: (msgs) => msgs as Message[],
|
|
165
174
|
toolExecution: "sequential",
|
|
166
|
-
...(reasoning ? { reasoning:
|
|
175
|
+
...(reasoning && thinkingLevel !== "off" ? { reasoning: thinkingLevel } : {}),
|
|
176
|
+
...(effectiveMaxTurns !== undefined
|
|
177
|
+
? {
|
|
178
|
+
shouldStopAfterTurn: () => {
|
|
179
|
+
turnCount++;
|
|
180
|
+
return turnCount >= effectiveMaxTurns;
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
: {}),
|
|
167
184
|
};
|
|
168
185
|
|
|
169
|
-
const
|
|
186
|
+
const loop = args.agentLoop ?? agentLoop;
|
|
187
|
+
const stream = loop(prompts, context, config, signal);
|
|
170
188
|
for await (const _event of stream) {
|
|
171
189
|
// Drain events; the tool's execute already collects records.
|
|
172
190
|
}
|
package/src/progress.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { AgentEvent } from "@mariozechner/pi-agent-core";
|
|
2
|
+
|
|
3
|
+
export type CompactionPhase = "observer" | "reflector" | "pruner";
|
|
4
|
+
|
|
5
|
+
export interface ThemeLike {
|
|
6
|
+
fg: (color: string, text: string) => string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ALL_PHASES: CompactionPhase[] = ["observer", "reflector", "pruner"];
|
|
10
|
+
|
|
11
|
+
export class CompactionProgressTracker {
|
|
12
|
+
private phase: CompactionPhase | undefined;
|
|
13
|
+
private pass = 0;
|
|
14
|
+
private maxPasses = 0;
|
|
15
|
+
private toolCallCount = 0;
|
|
16
|
+
private turnCount = 0;
|
|
17
|
+
// Starting counts before compaction tools run
|
|
18
|
+
private startingReflections = 0;
|
|
19
|
+
private startingObservations = 0;
|
|
20
|
+
// Accumulated deltas across all passes within a phase
|
|
21
|
+
private reflectionsAdded = 0;
|
|
22
|
+
private reflectionsMerged = 0;
|
|
23
|
+
private observationsDropped = 0;
|
|
24
|
+
private completedPhases: CompactionPhase[] = [];
|
|
25
|
+
|
|
26
|
+
getPhase(): CompactionPhase | undefined {
|
|
27
|
+
return this.phase;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getPass(): number {
|
|
31
|
+
return this.pass;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getMaxPasses(): number {
|
|
35
|
+
return this.maxPasses;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getToolCallCount(): number {
|
|
39
|
+
return this.toolCallCount;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getTurnCount(): number {
|
|
43
|
+
return this.turnCount;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setPhase(phase: CompactionPhase, pass: number, maxPasses: number): void {
|
|
47
|
+
if (this.phase && this.phase !== phase && !this.completedPhases.includes(this.phase)) {
|
|
48
|
+
this.completedPhases.push(this.phase);
|
|
49
|
+
}
|
|
50
|
+
// Reset deltas when transitioning to a different phase
|
|
51
|
+
if (this.phase !== phase) {
|
|
52
|
+
this.reflectionsAdded = 0;
|
|
53
|
+
this.reflectionsMerged = 0;
|
|
54
|
+
this.observationsDropped = 0;
|
|
55
|
+
}
|
|
56
|
+
this.phase = phase;
|
|
57
|
+
this.pass = pass;
|
|
58
|
+
this.maxPasses = maxPasses;
|
|
59
|
+
this.toolCallCount = 0;
|
|
60
|
+
this.turnCount = 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setStartingCounts(reflections: number, observations: number): void {
|
|
64
|
+
this.startingReflections = reflections;
|
|
65
|
+
this.startingObservations = observations;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setCompletedPhases(phases: CompactionPhase[]): void {
|
|
69
|
+
this.completedPhases = phases;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
onEvent(event: AgentEvent): void {
|
|
73
|
+
if (!this.phase) return;
|
|
74
|
+
switch (event.type) {
|
|
75
|
+
case "tool_execution_start":
|
|
76
|
+
this.toolCallCount++;
|
|
77
|
+
break;
|
|
78
|
+
case "tool_execution_end": {
|
|
79
|
+
if (event.isError) break;
|
|
80
|
+
const details = (event.result as { details?: Record<string, unknown> } | undefined)?.details;
|
|
81
|
+
if (!details) break;
|
|
82
|
+
if (event.toolName === "record_reflections") {
|
|
83
|
+
this.reflectionsAdded += (details.added as number) ?? 0;
|
|
84
|
+
this.reflectionsMerged += (details.merged as number) ?? 0;
|
|
85
|
+
} else if (event.toolName === "drop_observations") {
|
|
86
|
+
this.observationsDropped += Array.isArray(details.dropped) ? details.dropped.length : 0;
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "turn_start":
|
|
91
|
+
this.turnCount++;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
clear(): void {
|
|
97
|
+
this.phase = undefined;
|
|
98
|
+
this.pass = 0;
|
|
99
|
+
this.maxPasses = 0;
|
|
100
|
+
this.toolCallCount = 0;
|
|
101
|
+
this.turnCount = 0;
|
|
102
|
+
this.startingReflections = 0;
|
|
103
|
+
this.startingObservations = 0;
|
|
104
|
+
this.reflectionsAdded = 0;
|
|
105
|
+
this.reflectionsMerged = 0;
|
|
106
|
+
this.observationsDropped = 0;
|
|
107
|
+
this.completedPhases = [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
formatWidget(theme: ThemeLike): string {
|
|
111
|
+
if (!this.phase) return "";
|
|
112
|
+
|
|
113
|
+
const parts: string[] = [];
|
|
114
|
+
|
|
115
|
+
// Pipeline overview: show all phases with completion state
|
|
116
|
+
const phaseLabels = ALL_PHASES.map((p) => {
|
|
117
|
+
if (p === this.phase) {
|
|
118
|
+
return theme.fg("accent", p.charAt(0).toUpperCase() + p.slice(1));
|
|
119
|
+
}
|
|
120
|
+
if (this.completedPhases.includes(p)) {
|
|
121
|
+
return theme.fg("success", `✓${p.charAt(0).toUpperCase()}`);
|
|
122
|
+
}
|
|
123
|
+
return theme.fg("dim", p.charAt(0).toUpperCase());
|
|
124
|
+
});
|
|
125
|
+
parts.push(phaseLabels.join(theme.fg("dim", " → ")));
|
|
126
|
+
|
|
127
|
+
// Pass info (only for multi-pass phases)
|
|
128
|
+
if (this.maxPasses > 1) {
|
|
129
|
+
parts.push(theme.fg("muted", `pass ${this.pass}/${this.maxPasses}`));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Tool calls
|
|
133
|
+
const tcLabel = this.toolCallCount === 1 ? "tool call" : "tool calls";
|
|
134
|
+
parts.push(theme.fg("muted", `${this.toolCallCount} ${tcLabel}`));
|
|
135
|
+
|
|
136
|
+
// Delta counters: R total(+accumulated), M total(+accumulated), O remaining(-accumulated)
|
|
137
|
+
const deltas: string[] = [];
|
|
138
|
+
if (this.reflectionsAdded > 0) {
|
|
139
|
+
const total = this.startingReflections + this.reflectionsAdded;
|
|
140
|
+
deltas.push(`R ${total}(+${this.reflectionsAdded})`);
|
|
141
|
+
}
|
|
142
|
+
if (this.reflectionsMerged > 0) {
|
|
143
|
+
deltas.push(`M ${this.reflectionsMerged}`);
|
|
144
|
+
}
|
|
145
|
+
if (this.observationsDropped > 0) {
|
|
146
|
+
const remaining = this.startingObservations - this.observationsDropped;
|
|
147
|
+
deltas.push(`O ${remaining}(-${this.observationsDropped})`);
|
|
148
|
+
}
|
|
149
|
+
if (deltas.length > 0) {
|
|
150
|
+
parts.push(theme.fg("accent", deltas.join(" ")));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return parts.join(theme.fg("dim", " · "));
|
|
154
|
+
}
|
|
155
|
+
}
|
package/src/prompts.ts
CHANGED
|
@@ -150,7 +150,7 @@ You receive:
|
|
|
150
150
|
|
|
151
151
|
How you work:
|
|
152
152
|
1. Read current reflections and observations to understand what is already crystallized and what new signal exists in the pool.
|
|
153
|
-
2. Identify
|
|
153
|
+
2. Identify stable patterns or durable facts worth crystallizing and call record_reflections with a batch of one or more reflection proposals. Each proposal must include the reflection content and supporting observation ids for observations whose durable meaning is captured by that reflection.
|
|
154
154
|
3. Read the receipt. If more reflections are warranted, call record_reflections again with another batch. You may call the tool many times.
|
|
155
155
|
4. When nothing more is stable enough to crystallize, STOP calling the tool and reply with a brief plain-text confirmation (one short sentence). That ends the run.
|
|
156
156
|
|
|
@@ -160,9 +160,10 @@ What to emit:
|
|
|
160
160
|
- To promote a legacy/no-provenance reflection, emit the exact same reflection content with valid supportingObservationIds; the system will replace it with a provenance-backed reflection.
|
|
161
161
|
- When repeating exact existing content, emit only the reflection prose; omit any bracketed id handle.
|
|
162
162
|
- Do not lightly reword existing reflections. Rewording creates a separate reflection, so only use different wording when the durable meaning is materially different, more specific, or corrects/refines the existing reflection.
|
|
163
|
-
- For every reflection proposal, include supportingObservationIds
|
|
163
|
+
- For every reflection proposal, include supportingObservationIds for current observations whose durable meaning is captured by the reflection and can be treated as redundant active-memory detail. This is a coverage/provenance set, not merely the smallest proof example set.
|
|
164
|
+
- Include additional current observation ids when the reflection preserves their durable meaning with equivalent fidelity. Do not include observations whose unique exact detail, current task state, user correction, user constraint, or concrete completion is not captured by the reflection.
|
|
164
165
|
- Never invent supporting observation ids. Use only ids printed in the current observations list. Reflection proposals with missing, empty, or invalid supportingObservationIds will be rejected and not recorded.
|
|
165
|
-
- Crystallize preferentially from "high" and "critical" observations; ignore "low" unless a pattern across many "low" observations is itself significant.
|
|
166
|
+
- Crystallize preferentially from "high" and "critical" observations, then old "medium" observations whose durable meaning can be covered; ignore "low" unless a pattern across many "low" observations is itself significant.
|
|
166
167
|
- Focus on:
|
|
167
168
|
- User identity, role, preferences, constraints.
|
|
168
169
|
- Project goals, architectural decisions, key technical decisions and their rationale.
|
|
@@ -206,10 +207,12 @@ You receive:
|
|
|
206
207
|
- Current observations (timestamped, relevance-tagged events to prune). Each is shown as "[id] YYYY-MM-DD HH:MM [relevance] [coverage: tag] content", where id is the 12-character hex handle you reference when dropping.
|
|
207
208
|
- A pressure line stating pool size, target, tokens still to cut, and the current pass strategy.
|
|
208
209
|
|
|
209
|
-
Coverage tags are
|
|
210
|
+
Coverage tags are pruning signals derived from current provenance-backed reflection support ids. They are strong evidence, not blind commands:
|
|
210
211
|
- [coverage: uncited] means no current provenance-backed reflection cites this observation. Prune cautiously, especially for medium/high/critical observations, because durable meaning may not be captured elsewhere.
|
|
211
|
-
- [coverage: cited] means 1-3 current provenance-backed reflections cite this observation.
|
|
212
|
-
- [coverage: reinforced] means 4 or more current provenance-backed reflections cite this observation.
|
|
212
|
+
- [coverage: cited] means 1-3 current provenance-backed reflections cite this observation. Once it is old, it is a strong pruning candidate for low/medium observations when the reflection preserves equivalent meaning. Old high observations can also be dropped when the reflection captures the same fact, unless they carry current task state or exact details not captured with equivalent fidelity.
|
|
213
|
+
- [coverage: reinforced] means 4 or more current provenance-backed reflections cite this observation. Once it is old, it is a presumptive drop candidate because durable meaning is likely represented. Still preserve it if it carries current/recent task state, exact errors, file paths, commands, identifiers, user assertions, constraints, corrections, concrete completions, or nuance not captured with equivalent fidelity.
|
|
214
|
+
|
|
215
|
+
Active-memory framing. Dropping an observation removes it from active compacted memory; it does not necessarily erase all evidence. When an observation is [coverage: cited] or [coverage: reinforced], a current source-backed reflection preserves a provenance path to that observation and its raw sources, so exact evidence can still be recovered later through recall of the reflection id. Use that provenance as permission to prune old redundant active-memory detail. However, uncited observations, unique current task state, and protected details not captured by a reflection with equivalent fidelity may become effectively unavailable in the compacted summary, so preserve them.
|
|
213
216
|
|
|
214
217
|
How you work:
|
|
215
218
|
1. Read reflections and the observation pool.
|
|
@@ -220,7 +223,7 @@ How you work:
|
|
|
220
223
|
This agent may be invoked again in a follow-up pass if the pool is still over budget — focus each run on your next-weakest drops rather than trying to do everything in one call.
|
|
221
224
|
|
|
222
225
|
What to drop (in priority order):
|
|
223
|
-
- Signal-captured: observations tagged [coverage:
|
|
226
|
+
- Signal-captured: observations tagged [coverage: reinforced] or [coverage: cited] whose durable meaning is captured by a reflection now in the reflections list. Old reinforced observations should usually be dropped unless they uniquely carry protected details. Old cited low/medium observations are strong drop candidates. Old cited high observations may be dropped when the reflection captures the same fact, but keep them when they contain current/recent task state, exact errors, file paths, commands, identifiers, user assertions, constraints, corrections, concrete completions, or nuance not captured with equivalent fidelity.
|
|
224
227
|
- Superseded: directly contradicted or replaced by a newer observation.
|
|
225
228
|
- Redundant: near-duplicate of another observation (keep the higher-relevance or more recent one).
|
|
226
229
|
- Exhausted routine: tool-call acks, status updates, trivia that no longer affects the work.
|
|
@@ -232,8 +235,8 @@ Age-gradient rule. Recent observations carry working context the assistant still
|
|
|
232
235
|
|
|
233
236
|
Relevance guidance:
|
|
234
237
|
- "low": drop freely once reviewed. Why: these were marked low because they add little signal; keeping them crowds out more useful records.
|
|
235
|
-
- "medium": drop when redundant with reflections or other observations, or when the task context has moved on.
|
|
236
|
-
- "high": drop
|
|
238
|
+
- "medium": drop when redundant with reflections or other observations, especially when [coverage: cited] or [coverage: reinforced], or when the task context has moved on.
|
|
239
|
+
- "high": drop when clearly superseded or already captured by a reflection; for old [coverage: cited] or [coverage: reinforced] high observations, require only that the reflection captures the same durable fact and no protected exact detail is unique to the observation.
|
|
237
240
|
- "critical": NEVER drop. These encode user identity, explicit corrections, and concrete completions. Why this matters: dropping a critical item causes the assistant to repeat finished work, contradict an explicit correction, or misrepresent who the user is. No amount of budget pressure justifies this.
|
|
238
241
|
|
|
239
242
|
User assertions and concrete completions are never droppable, even at non-critical relevance. If the relevance was mis-labeled but the content is load-bearing (an assertion about the user or a marker that work is done), treat the content as authoritative and skip the drop.
|
|
@@ -254,40 +257,38 @@ If one of these categories is ALSO captured by an existing reflection with equiv
|
|
|
254
257
|
BAD: drop "[id] 2025-12-04 14:30 [medium] Build failed: TS2322 at src/auth.ts:47 — Type 'string | undefined' is not assignable to type 'string'" because it is only medium and the task moved on.
|
|
255
258
|
GOOD: keep that observation; it is a verbatim error the user hit, not captured in any reflection. Future debugging may need the exact code and location.
|
|
256
259
|
|
|
257
|
-
When in doubt, prefer dropping
|
|
260
|
+
When in doubt, prefer dropping reinforced observations first, then cited observations, before uncited observations. Coverage tags are strong signals, not blind commands: reflections protect durable facts only when they preserve equivalent meaning. The only things you must preserve unconditionally are critical observations, user assertions, and concrete completions.
|
|
258
261
|
|
|
259
262
|
What you CANNOT do:
|
|
260
263
|
- You cannot merge observations. If two overlap, drop the weaker one.
|
|
261
264
|
- You cannot rewrite or edit observations. The kept set preserves content, timestamp, and relevance exactly as they were.
|
|
262
265
|
- You cannot add new observations.
|
|
263
266
|
|
|
264
|
-
It is valid to end a pass with zero drops if the pool genuinely has nothing more to cut — a follow-up pass will be skipped when a run returns zero drops. Do not force drops you don't believe in.
|
|
267
|
+
It is valid to end a pass with zero drops if the pool genuinely has nothing more to cut — a follow-up pass will be skipped when a run returns zero drops. On late pressure passes, first re-check old [coverage: reinforced] and [coverage: cited] observations as active-memory redundancies before deciding there are no sound drops. Do not force drops you don't believe in.
|
|
265
268
|
|
|
266
|
-
Remember:
|
|
269
|
+
Remember: pruning is active-memory management, not source deletion. A drop that looks reasonable at "low" still becomes a mistake if the content was a user correction with a mis-labeled relevance and no reflection captures it with equivalent fidelity. Read before you cut.`;
|
|
267
270
|
|
|
268
|
-
type ReflectorPassTier = 1 | 2
|
|
271
|
+
type ReflectorPassTier = 1 | 2;
|
|
269
272
|
|
|
270
273
|
const REFLECTOR_PASS_STRATEGIES: Record<ReflectorPassTier, string> = {
|
|
271
|
-
1: `Pass strategy — multi-observation synthesis. Find broad durable patterns, repeated preferences, recurring constraints, stable work style, and project-level themes supported by multiple observations. Every reflection recorded in this pass must cite at least 2 distinct supportingObservationIds. Do not create one-off event summaries; leave important single-observation facts for the
|
|
272
|
-
2: `Pass strategy — atomic durable facts. Capture important durable facts that may be supported by a single authoritative observation: explicit user preferences, hard constraints, corrections, decisions, completed milestones, project facts, release or rollback caveats, and other load-bearing facts future agents must not forget.
|
|
273
|
-
3: `Pass strategy — final safety review. Review the full observation pool against the current reflections, including reflections created in earlier passes, and catch durable information still missing. Look especially for high or critical observations, explicit user assertions, corrections, constraints, decisions, completed work, and important technical context. Do not create reflections just to increase coverage; only record a reflection if the durable meaning is not already captured with sufficient fidelity.`,
|
|
274
|
+
1: `Pass strategy — multi-observation synthesis. Find broad durable patterns, repeated preferences, recurring constraints, stable work style, and project-level themes supported by multiple observations. Every reflection recorded in this pass must cite at least 2 distinct supportingObservationIds whose durable meaning is captured by the reflection. Do not create one-off event summaries; leave important single-observation facts, safety review, and coverage strengthening for the final reflector pass. If an existing reflection already captures the pattern, repeat the exact same content when adding support ids materially improves active-memory coverage.`,
|
|
275
|
+
2: `Pass strategy — final atomic durable facts, safety review, and coverage strengthening. Capture important durable facts that may be supported by a single authoritative observation: explicit user preferences, hard constraints, corrections, decisions, completed milestones, project facts, release or rollback caveats, important technical context, and other load-bearing facts future agents must not forget. Review high and critical observations against current reflections, including reflections created in pass 1, and catch durable information still missing. Strengthen existing or newly-created reflections by repeating exact reflection content with additional supportingObservationIds for high, critical, and old medium observations whose durable meaning is already captured by that reflection. Do not duplicate earlier reflections; repeat exact existing content only to add missing support or promote no-provenance legacy memory. Create new reflections only when durable meaning is still missing. Do not create low-quality reflections just for coverage, and do not attach observations whose unique exact detail or current task state is not captured with equivalent fidelity.`,
|
|
274
276
|
};
|
|
275
277
|
|
|
276
278
|
export function buildReflectorPassGuidance(pass: number, maxPasses: number): string {
|
|
277
|
-
const tier = (Math.min(
|
|
279
|
+
const tier = (Math.min(2, Math.max(1, pass)) as ReflectorPassTier);
|
|
278
280
|
return `Pass ${pass} of up to ${maxPasses}. ${REFLECTOR_PASS_STRATEGIES[tier]}`;
|
|
279
281
|
}
|
|
280
282
|
|
|
281
|
-
type PrunerPassTier = 1 | 2
|
|
283
|
+
type PrunerPassTier = 1 | 2;
|
|
282
284
|
|
|
283
285
|
const PRUNER_PASS_STRATEGIES: Record<PrunerPassTier, string> = {
|
|
284
|
-
1: `Pass strategy — clear-cut drops only. Prefer old low-value [coverage:
|
|
285
|
-
2: `Pass strategy — topic compression.
|
|
286
|
-
3: `Pass strategy — aggressive age compression. In the older half of the pool, drop all but the outcome-bearing "low" and "medium" observations, preferring [coverage: cited] and [coverage: reinforced] over [coverage: uncited]. Keep the most recent ~30% of the pool at higher detail. Drop "high" observations only when a reflection clearly captures the same fact. NEVER drop "critical" items, user assertions, or concrete completions regardless of age.`,
|
|
286
|
+
1: `Pass strategy — clear-cut source-backed drops only. Prefer old low-value [coverage: reinforced] observations, then old low/medium [coverage: cited] observations, when their durable meaning is represented by current reflections. Also remove exact duplicates, near-duplicates (keep the higher-relevance or more recent version), observations directly superseded by a newer one, and routine "low" tool-call acks. Do not touch ambiguous [coverage: uncited] cases on this pass unless the drop is an exact duplicate or direct supersession. Because there are only two pruning passes, do not defer obvious source-backed drops unnecessarily.`,
|
|
287
|
+
2: `Pass strategy — final topic compression, aggressive age compression, and budget-pressure rescue. This is the last pruning pass: make the strongest sound source-backed cuts available before stopping. First compress topics: drop "low" observations covered by recent "medium" or "high" observations, older [coverage: cited] "medium" observations whose substance is now covered by a reflection, and repeated tool-call sequences where one observation captures the learning. Then apply aggressive age compression: in the older half of the pool, drop non-outcome-bearing "low" and "medium" observations, strongly preferring [coverage: reinforced] and [coverage: cited] over [coverage: uncited]. Keep the most recent ~30% at higher detail unless an observation is clearly redundant and source-backed. Under budget pressure, treat old [coverage: reinforced] observations as active-memory redundancies by default when current source-backed reflections preserve their durable meaning. Drop reinforced low/medium/high observations unless they uniquely carry current task state or protected exact detail not captured with equivalent fidelity. Drop old [coverage: cited] high observations only when a reflection captures the same durable fact and the observation has no unique protected exact detail. Prefer source-backed reinforced/cited drops over any uncited drop. Do not drop critical observations, user assertions, concrete completions, explicit user corrections or constraints, current task state, unique exact errors/paths/commands/ids, dated events, or decision rationale unless an existing reflection preserves the same information with equivalent fidelity. Do not fabricate drops solely to hit the target.`,
|
|
287
288
|
};
|
|
288
289
|
|
|
289
290
|
export function buildPrunerPassGuidance(pass: number, maxPasses: number): string {
|
|
290
|
-
const tier = (Math.min(
|
|
291
|
+
const tier = (Math.min(2, Math.max(1, pass)) as PrunerPassTier);
|
|
291
292
|
return `Pass ${pass} of up to ${maxPasses}. ${PRUNER_PASS_STRATEGIES[tier]}`;
|
|
292
293
|
}
|
|
293
294
|
|