pi-blackhole 0.4.2 → 0.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 +15 -0
- package/dist/index.js +11660 -0
- package/dist/index.js.map +1 -0
- package/example-config.json +1 -1
- package/index.ts +37 -63
- package/package.json +21 -9
- package/src/commands/cleanup.ts +279 -240
- package/src/commands/memory.ts +236 -184
- package/src/commands/pi-vcc.ts +202 -152
- package/src/commands/vcc-recall.ts +126 -95
- package/src/core/brief.ts +167 -33
- package/src/core/build-sections.ts +8 -2
- package/src/core/config-env.ts +117 -0
- package/src/core/content.ts +31 -7
- package/src/core/drill-down.ts +41 -11
- package/src/core/filter-noise.ts +9 -3
- package/src/core/format-recall.ts +15 -6
- package/src/core/format.ts +14 -4
- package/src/core/lineage.ts +9 -3
- package/src/core/load-messages.ts +24 -5
- package/src/core/normalize.ts +38 -14
- package/src/core/recall-scope.ts +11 -3
- package/src/core/render-entries.ts +22 -6
- package/src/core/sanitize.ts +5 -1
- package/src/core/search-entries.ts +111 -19
- package/src/core/settings.ts +1 -3
- package/src/core/summarize.ts +42 -21
- package/src/core/unified-config.ts +549 -411
- package/src/extract/commits.ts +4 -2
- package/src/extract/files.ts +10 -5
- package/src/extract/goals.ts +7 -2
- package/src/hooks/before-compact.ts +210 -88
- package/src/om/agents/dropper/agent.ts +380 -265
- package/src/om/agents/dropper/coverage.ts +102 -82
- package/src/om/agents/observer/agent.ts +242 -206
- package/src/om/agents/reflector/agent.ts +212 -153
- package/src/om/cleanup.ts +239 -218
- package/src/om/clipboard.ts +59 -51
- package/src/om/compaction-trigger.ts +448 -333
- package/src/om/config.ts +13 -6
- package/src/om/configure-overlay.ts +518 -355
- package/src/om/consolidation.ts +1460 -953
- package/src/om/cooldown.ts +75 -65
- package/src/om/debug-log.ts +86 -68
- package/src/om/ids.ts +1 -1
- package/src/om/ledger/fold.ts +89 -78
- package/src/om/ledger/progress.ts +181 -153
- package/src/om/ledger/projection.ts +248 -185
- package/src/om/ledger/recall.ts +247 -196
- package/src/om/ledger/render-summary.ts +79 -50
- package/src/om/ledger/types.ts +146 -117
- package/src/om/model-budget.ts +23 -13
- package/src/om/pending.ts +243 -179
- package/src/om/provider-stream.ts +52 -7
- package/src/om/retryable-error.ts +12 -16
- package/src/om/reverse-recall.ts +97 -91
- package/src/om/runtime.ts +474 -375
- package/src/om/serialize.ts +190 -166
- package/src/om/status-overlay.ts +246 -195
- package/src/om/tokens.ts +28 -21
- package/src/pi-base/blackhole-settings.ts +437 -0
- package/src/pi-base/config-manager.ts +440 -0
- package/src/pi-base/config.ts +469 -0
- package/src/pi-base/env.ts +43 -0
- package/src/pi-base/paths.ts +47 -0
- package/src/pi-base/settings/body.ts +1648 -0
- package/src/pi-base/settings/fields/action.ts +43 -0
- package/src/pi-base/settings/fields/boolean.ts +47 -0
- package/src/pi-base/settings/fields/custom.ts +72 -0
- package/src/pi-base/settings/fields/enum.ts +310 -0
- package/src/pi-base/settings/fields/index.ts +46 -0
- package/src/pi-base/settings/fields/model.ts +452 -0
- package/src/pi-base/settings/fields/string.ts +527 -0
- package/src/pi-base/settings/fields/text.ts +115 -0
- package/src/pi-base/settings/frame.ts +197 -0
- package/src/pi-base/settings/index.ts +77 -0
- package/src/pi-base/settings/inline-edit.ts +313 -0
- package/src/pi-base/settings/modal.ts +152 -0
- package/src/pi-base/settings/types.ts +500 -0
- package/src/pi-base/settings/validate-field.ts +113 -0
- package/src/pi-base/shell.ts +117 -0
- package/src/pi-base/types.ts +6 -0
- package/src/pi-base/ui.ts +32 -0
- package/src/tools/recall.ts +347 -225
- package/src/types.ts +20 -3
- package/tsup.config.ts +23 -0
- package/vitest.config.ts +15 -15
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* Modified by pi-vcc-om: detects agent_end stopReason="error" in the stream
|
|
6
6
|
* and throws if the API errored without collecting any drop candidates.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
agentLoop,
|
|
10
|
+
type AgentContext,
|
|
11
|
+
type AgentLoopConfig,
|
|
12
|
+
type AgentTool,
|
|
13
|
+
} from "@earendil-works/pi-agent-core";
|
|
9
14
|
import type { Message, Model, ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
10
15
|
import { createBridgeStreamFn } from "../../provider-stream.js";
|
|
11
16
|
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
@@ -13,319 +18,429 @@ import { Type } from "typebox";
|
|
|
13
18
|
import type { Static } from "typebox";
|
|
14
19
|
import { debugLog } from "../../debug-log.js";
|
|
15
20
|
import { AGENT_LOOP_MAX_TOKENS, boundedMaxTokens } from "../../model-budget.js";
|
|
16
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
reflectionToSummaryLine,
|
|
23
|
+
type Observation,
|
|
24
|
+
type Reflection,
|
|
25
|
+
} from "../../ledger/index.js";
|
|
17
26
|
import { DROPPER_SYSTEM } from "./prompts.js";
|
|
18
27
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
REFLECTION_COVERAGE_DROP_RANK,
|
|
29
|
+
coverageTierForObservation,
|
|
30
|
+
observationToDropperLine,
|
|
31
|
+
reflectionCoverageMap,
|
|
32
|
+
summarizeCoverageByRelevance,
|
|
33
|
+
summarizeCoverageByRelevanceForIds,
|
|
25
34
|
} from "./coverage.js";
|
|
26
35
|
|
|
27
36
|
interface RunDropperArgs {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
model: Model<any>;
|
|
38
|
+
apiKey: string;
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
reflections: Reflection[];
|
|
41
|
+
observations: Observation[];
|
|
42
|
+
/** Compact summary of existing active observations for context. */
|
|
43
|
+
existingObservationsSummary?: string;
|
|
44
|
+
budgetTokens: number;
|
|
45
|
+
/** Minimum pool fullness (fraction of budget) before dropping is allowed.
|
|
46
|
+
* Defaults to DROP_SKIP_FULLNESS (0.1) when unset. */
|
|
47
|
+
skipFullness?: number;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
agentLoop?: typeof agentLoop;
|
|
50
|
+
/** Optional custom stream function bypassing agentLoop's default streamSimple.
|
|
51
|
+
* Used by the Symbol.for bridge to access native pi-ai provider registrations
|
|
52
|
+
* from jiti-loaded consolidation agents. */
|
|
53
|
+
streamFn?: (model: any, context: any, options: any) => any;
|
|
54
|
+
maxTurns?: number;
|
|
55
|
+
thinkingLevel?: ModelThinkingLevel;
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
const DROP_SKIP_FULLNESS = 0.
|
|
47
|
-
const DROP_LOW_URGENCY_FULLNESS = 0.
|
|
48
|
-
const DROP_MEDIUM_URGENCY_FULLNESS = 0.
|
|
49
|
-
const DROP_MAX_FULLNESS = 1.
|
|
50
|
-
const DROP_MIN_RATIO = 0.
|
|
51
|
-
const DROP_MAX_RATIO = 0.
|
|
58
|
+
const DROP_SKIP_FULLNESS = 0.1;
|
|
59
|
+
const DROP_LOW_URGENCY_FULLNESS = 0.3;
|
|
60
|
+
const DROP_MEDIUM_URGENCY_FULLNESS = 0.6;
|
|
61
|
+
const DROP_MAX_FULLNESS = 1.0;
|
|
62
|
+
const DROP_MIN_RATIO = 0.1;
|
|
63
|
+
const DROP_MAX_RATIO = 0.5;
|
|
52
64
|
|
|
53
65
|
export type DropUrgency = "low" | "medium" | "high";
|
|
54
66
|
|
|
55
67
|
const RELEVANCE_DROP_RANK: Record<Observation["relevance"], number> = {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
low: 0,
|
|
69
|
+
medium: 1,
|
|
70
|
+
high: 2,
|
|
71
|
+
critical: 3,
|
|
60
72
|
};
|
|
61
73
|
|
|
62
74
|
const DropObservationsSchema = Type.Object({
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
ids: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
|
76
|
+
reason: Type.Optional(Type.String()),
|
|
65
77
|
});
|
|
66
78
|
|
|
67
79
|
type DropObservationsArgs = Static<typeof DropObservationsSchema>;
|
|
68
80
|
|
|
69
81
|
function joinOrEmpty(items: string[]): string {
|
|
70
|
-
|
|
82
|
+
return items.length ? items.join("\n") : "(none yet)";
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
export function observationPoolFullness(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
export function observationPoolFullness(
|
|
86
|
+
observationTokens: number,
|
|
87
|
+
budgetTokens: number,
|
|
88
|
+
): number {
|
|
89
|
+
if (!Number.isFinite(observationTokens) || observationTokens <= 0) return 0;
|
|
90
|
+
if (!Number.isFinite(budgetTokens) || budgetTokens <= 0) return 0;
|
|
91
|
+
return observationTokens / budgetTokens;
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
export function dropUrgencyForFullness(fullness: number): DropUrgency {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
if (fullness < DROP_LOW_URGENCY_FULLNESS) return "low";
|
|
96
|
+
if (fullness < DROP_MEDIUM_URGENCY_FULLNESS) return "medium";
|
|
97
|
+
return "high";
|
|
83
98
|
}
|
|
84
99
|
|
|
85
|
-
export function maxDropCountForPool(
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
export function maxDropCountForPool(
|
|
101
|
+
observations: readonly Observation[],
|
|
102
|
+
observationTokens: number,
|
|
103
|
+
budgetTokens: number,
|
|
104
|
+
skipFullness: number = DROP_SKIP_FULLNESS,
|
|
105
|
+
): number {
|
|
106
|
+
const droppableCount = observations.filter(
|
|
107
|
+
(observation) => observation.relevance !== "critical",
|
|
108
|
+
).length;
|
|
109
|
+
if (droppableCount === 0) return 0;
|
|
88
110
|
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
const fullness = observationPoolFullness(observationTokens, budgetTokens);
|
|
112
|
+
if (fullness < skipFullness) return 0;
|
|
91
113
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
const cappedFullness = Math.min(
|
|
115
|
+
DROP_MAX_FULLNESS,
|
|
116
|
+
Math.max(skipFullness, fullness),
|
|
117
|
+
);
|
|
118
|
+
const dropRatio =
|
|
119
|
+
DROP_MIN_RATIO +
|
|
120
|
+
((cappedFullness - skipFullness) / (DROP_MAX_FULLNESS - skipFullness)) *
|
|
121
|
+
(DROP_MAX_RATIO - DROP_MIN_RATIO);
|
|
122
|
+
return Math.max(1, Math.floor(droppableCount * dropRatio));
|
|
97
123
|
}
|
|
98
124
|
|
|
99
|
-
function relevanceCounts(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
125
|
+
function relevanceCounts(
|
|
126
|
+
observations: readonly Observation[],
|
|
127
|
+
): Record<Observation["relevance"], number> {
|
|
128
|
+
return observations.reduce<Record<Observation["relevance"], number>>(
|
|
129
|
+
(counts, observation) => {
|
|
130
|
+
if (observation.relevance in counts) counts[observation.relevance]++;
|
|
131
|
+
return counts;
|
|
132
|
+
},
|
|
133
|
+
{ low: 0, medium: 0, high: 0, critical: 0 },
|
|
134
|
+
);
|
|
104
135
|
}
|
|
105
136
|
|
|
106
137
|
export function normalizeDropObservationIds(
|
|
107
|
-
|
|
108
|
-
|
|
138
|
+
ids: readonly string[] | undefined,
|
|
139
|
+
observations: readonly Observation[],
|
|
109
140
|
): string[] | undefined {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
141
|
+
if (!ids || ids.length === 0) return undefined;
|
|
142
|
+
const allowed = new Map(
|
|
143
|
+
observations.map((observation) => [observation.id, observation]),
|
|
144
|
+
);
|
|
145
|
+
const result: string[] = [];
|
|
146
|
+
const seen = new Set<string>();
|
|
147
|
+
for (const id of ids) {
|
|
148
|
+
const observation = allowed.get(id);
|
|
149
|
+
if (!observation) continue;
|
|
150
|
+
if (seen.has(id)) continue;
|
|
151
|
+
seen.add(id);
|
|
152
|
+
result.push(id);
|
|
153
|
+
}
|
|
154
|
+
return result.length > 0 ? result : undefined;
|
|
122
155
|
}
|
|
123
156
|
|
|
124
157
|
function timestampRank(timestamp: string): number {
|
|
125
|
-
|
|
126
|
-
|
|
158
|
+
const parsed = Date.parse(timestamp);
|
|
159
|
+
return Number.isFinite(parsed) ? parsed : Number.POSITIVE_INFINITY;
|
|
127
160
|
}
|
|
128
161
|
|
|
129
162
|
export function selectDropCandidates(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
163
|
+
ids: readonly string[],
|
|
164
|
+
observations: readonly Observation[],
|
|
165
|
+
maxDrops: number,
|
|
166
|
+
reflections: readonly Reflection[] = [],
|
|
134
167
|
): string[] {
|
|
135
|
-
|
|
168
|
+
if (maxDrops <= 0 || ids.length === 0) return [];
|
|
136
169
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
170
|
+
const byId = new Map(
|
|
171
|
+
observations.map((observation) => [observation.id, observation]),
|
|
172
|
+
);
|
|
173
|
+
const coverageById = reflectionCoverageMap(observations, reflections);
|
|
174
|
+
const firstProposalIndex = new Map<string, number>();
|
|
175
|
+
for (let i = 0; i < ids.length; i++) {
|
|
176
|
+
const id = ids[i];
|
|
177
|
+
if (!firstProposalIndex.has(id)) firstProposalIndex.set(id, i);
|
|
178
|
+
}
|
|
144
179
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
180
|
+
return Array.from(firstProposalIndex.entries())
|
|
181
|
+
.map(([id, index]) => ({ id, index, observation: byId.get(id) }))
|
|
182
|
+
.filter(
|
|
183
|
+
(
|
|
184
|
+
candidate,
|
|
185
|
+
): candidate is { id: string; index: number; observation: Observation } =>
|
|
186
|
+
candidate.observation !== undefined,
|
|
187
|
+
)
|
|
188
|
+
.sort((a, b) => {
|
|
189
|
+
const coverageDelta =
|
|
190
|
+
REFLECTION_COVERAGE_DROP_RANK[
|
|
191
|
+
coverageTierForObservation(a.observation, coverageById)
|
|
192
|
+
] -
|
|
193
|
+
REFLECTION_COVERAGE_DROP_RANK[
|
|
194
|
+
coverageTierForObservation(b.observation, coverageById)
|
|
195
|
+
];
|
|
196
|
+
const relevanceDelta =
|
|
197
|
+
RELEVANCE_DROP_RANK[a.observation.relevance] -
|
|
198
|
+
RELEVANCE_DROP_RANK[b.observation.relevance];
|
|
199
|
+
const aAge = timestampRank(a.observation.timestamp);
|
|
200
|
+
const bAge = timestampRank(b.observation.timestamp);
|
|
201
|
+
const ageDelta = aAge === bAge ? 0 : aAge - bAge;
|
|
202
|
+
return coverageDelta || relevanceDelta || ageDelta || a.index - b.index;
|
|
203
|
+
})
|
|
204
|
+
.slice(0, maxDrops)
|
|
205
|
+
.map((candidate) => candidate.id);
|
|
161
206
|
}
|
|
162
207
|
|
|
163
|
-
export async function runDropper(
|
|
164
|
-
|
|
165
|
-
|
|
208
|
+
export async function runDropper(
|
|
209
|
+
args: RunDropperArgs,
|
|
210
|
+
): Promise<string[] | undefined> {
|
|
211
|
+
const {
|
|
212
|
+
model,
|
|
213
|
+
apiKey,
|
|
214
|
+
headers,
|
|
215
|
+
reflections,
|
|
216
|
+
observations,
|
|
217
|
+
budgetTokens,
|
|
218
|
+
skipFullness,
|
|
219
|
+
signal,
|
|
220
|
+
} = args;
|
|
221
|
+
if (observations.length === 0) return undefined;
|
|
166
222
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
223
|
+
const observationTokens = observations.reduce(
|
|
224
|
+
(sum, observation) => sum + observation.tokenCount,
|
|
225
|
+
0,
|
|
226
|
+
);
|
|
227
|
+
const fullness = observationPoolFullness(observationTokens, budgetTokens);
|
|
228
|
+
const urgency = dropUrgencyForFullness(fullness);
|
|
229
|
+
const maxDropsAllowed = maxDropCountForPool(
|
|
230
|
+
observations,
|
|
231
|
+
observationTokens,
|
|
232
|
+
budgetTokens,
|
|
233
|
+
skipFullness,
|
|
234
|
+
);
|
|
235
|
+
const coverageById = reflectionCoverageMap(observations, reflections);
|
|
236
|
+
const coverageSummaryByRelevance = summarizeCoverageByRelevance(
|
|
237
|
+
observations,
|
|
238
|
+
coverageById,
|
|
239
|
+
);
|
|
240
|
+
debugLog("dropper.agent_start", {
|
|
241
|
+
activeObservationCount: observations.length,
|
|
242
|
+
reflectionCount: reflections.length,
|
|
243
|
+
observationTokens,
|
|
244
|
+
budgetTokens,
|
|
245
|
+
fullness,
|
|
246
|
+
urgency,
|
|
247
|
+
maxDropsAllowed,
|
|
248
|
+
relevanceCounts: relevanceCounts(observations),
|
|
249
|
+
coverageSummaryByRelevance,
|
|
250
|
+
});
|
|
251
|
+
if (maxDropsAllowed <= 0) {
|
|
252
|
+
debugLog("dropper.result", {
|
|
253
|
+
reason: "not_over_target",
|
|
254
|
+
toolCallCount: 0,
|
|
255
|
+
rawRequestedIdsCount: 0,
|
|
256
|
+
acceptedCandidateCount: 0,
|
|
257
|
+
selectedDropsCount: 0,
|
|
258
|
+
selectedDropTokens: 0,
|
|
259
|
+
selectedCoverageSummaryByRelevance: summarizeCoverageByRelevanceForIds(
|
|
260
|
+
[],
|
|
261
|
+
observations,
|
|
262
|
+
coverageById,
|
|
263
|
+
),
|
|
264
|
+
maxDropsAllowed,
|
|
265
|
+
});
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
197
268
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
269
|
+
const proposedDropIds: string[] = [];
|
|
270
|
+
const proposed = new Set<string>();
|
|
271
|
+
const allowed = new Map(
|
|
272
|
+
observations.map((observation) => [observation.id, observation]),
|
|
273
|
+
);
|
|
274
|
+
let toolCallCount = 0;
|
|
275
|
+
let rawRequestedIdsCount = 0;
|
|
276
|
+
let missingIdsCount = 0;
|
|
277
|
+
let criticalCandidateIdsCount = 0;
|
|
278
|
+
let duplicateInRequestCount = 0;
|
|
279
|
+
let duplicateInRunCount = 0;
|
|
207
280
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
281
|
+
const dropObservations: AgentTool<typeof DropObservationsSchema> = {
|
|
282
|
+
name: "drop_observations",
|
|
283
|
+
label: "Drop observations",
|
|
284
|
+
description:
|
|
285
|
+
"Propose active observation ids that are safe to remove from compacted memory.",
|
|
286
|
+
parameters: DropObservationsSchema,
|
|
287
|
+
execute: async (_id, params: DropObservationsArgs) => {
|
|
288
|
+
toolCallCount++;
|
|
289
|
+
rawRequestedIdsCount += params.ids.length;
|
|
290
|
+
const seenInRequest = new Set<string>();
|
|
291
|
+
let added = 0;
|
|
292
|
+
let requestMissingIds = 0;
|
|
293
|
+
let requestCriticalCandidateIds = 0;
|
|
294
|
+
let requestDuplicateIds = 0;
|
|
295
|
+
let requestDuplicateInRunIds = 0;
|
|
296
|
+
for (const id of params.ids) {
|
|
297
|
+
const observation = allowed.get(id);
|
|
298
|
+
if (!observation) {
|
|
299
|
+
missingIdsCount++;
|
|
300
|
+
requestMissingIds++;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (seenInRequest.has(id)) {
|
|
304
|
+
duplicateInRequestCount++;
|
|
305
|
+
requestDuplicateIds++;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
seenInRequest.add(id);
|
|
309
|
+
if (proposed.has(id)) {
|
|
310
|
+
duplicateInRunCount++;
|
|
311
|
+
requestDuplicateInRunIds++;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
proposed.add(id);
|
|
315
|
+
proposedDropIds.push(id);
|
|
316
|
+
if (observation.relevance === "critical") {
|
|
317
|
+
criticalCandidateIdsCount++;
|
|
318
|
+
requestCriticalCandidateIds++;
|
|
319
|
+
}
|
|
320
|
+
added++;
|
|
321
|
+
}
|
|
322
|
+
debugLog("dropper.tool_call", {
|
|
323
|
+
toolCallCount,
|
|
324
|
+
rawRequestedIdsCount: params.ids.length,
|
|
325
|
+
acceptedIdsCount: added,
|
|
326
|
+
missingIdsCount: requestMissingIds,
|
|
327
|
+
criticalCandidateIdsCount: requestCriticalCandidateIds,
|
|
328
|
+
duplicateInRequestCount: requestDuplicateIds,
|
|
329
|
+
duplicateInRunCount: requestDuplicateInRunIds,
|
|
330
|
+
totalCandidates: proposedDropIds.length,
|
|
331
|
+
maxDropsAllowed,
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
content: [
|
|
335
|
+
{
|
|
336
|
+
type: "text",
|
|
337
|
+
text: `Queued ${added} drop candidate${added === 1 ? "" : "s"}. Candidates this run: ${proposedDropIds.length}. Maximum drops allowed: ${maxDropsAllowed}.`,
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
details: {
|
|
341
|
+
added,
|
|
342
|
+
totalCandidates: proposedDropIds.length,
|
|
343
|
+
maxDropsAllowed,
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
},
|
|
347
|
+
};
|
|
265
348
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
349
|
+
const fullnessPercent = Math.round(fullness * 100);
|
|
350
|
+
const existingObservationsContext = args.existingObservationsSummary
|
|
351
|
+
? `EXISTING ACTIVE OBSERVATIONS (for context only — these are NOT candidates for dropping):\n${args.existingObservationsSummary}\n\n`
|
|
352
|
+
: "";
|
|
270
353
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
354
|
+
const userText = `CURRENT REFLECTIONS:\n${joinOrEmpty(reflections.map(reflectionToSummaryLine))}\n\n${existingObservationsContext}NEW OBSERVATIONS TO EVALUATE FOR DROPPING:\n${joinOrEmpty(observations.map((observation) => observationToDropperLine(observation, coverageTierForObservation(observation, coverageById))))}\n\nObservation pool pressure: ~${observationTokens.toLocaleString()} tokens; target budget: ~${budgetTokens.toLocaleString()} tokens; fullness: ~${fullnessPercent.toLocaleString()}%.\nDrop urgency: ${urgency}.\nMaximum drops allowed this run: ${maxDropsAllowed.toLocaleString()} observation${maxDropsAllowed === 1 ? "" : "s"}.\nThis maximum is a hard upper bound, not a target. Drop fewer or none if fewer observations are clearly safe.`;
|
|
355
|
+
const prompts: Message[] = [
|
|
356
|
+
{
|
|
357
|
+
role: "user",
|
|
358
|
+
content: [{ type: "text", text: userText }],
|
|
359
|
+
timestamp: Date.now(),
|
|
360
|
+
},
|
|
361
|
+
];
|
|
362
|
+
const context: AgentContext = {
|
|
363
|
+
systemPrompt: DROPPER_SYSTEM,
|
|
364
|
+
messages: [],
|
|
365
|
+
tools: [dropObservations as AgentTool<any>],
|
|
366
|
+
};
|
|
367
|
+
const reasoning = (model as { reasoning?: unknown }).reasoning;
|
|
368
|
+
const thinkingLevel = args.thinkingLevel ?? "low";
|
|
369
|
+
const effectiveMaxTurns =
|
|
370
|
+
args.maxTurns && args.maxTurns > 0 ? args.maxTurns : undefined;
|
|
371
|
+
let turnCount = 0;
|
|
372
|
+
const config: AgentLoopConfig = {
|
|
373
|
+
model,
|
|
374
|
+
apiKey,
|
|
375
|
+
headers,
|
|
376
|
+
maxTokens: boundedMaxTokens(model, AGENT_LOOP_MAX_TOKENS),
|
|
377
|
+
convertToLlm: (msgs) => msgs as Message[],
|
|
378
|
+
toolExecution: "sequential",
|
|
379
|
+
...(reasoning && thinkingLevel !== "off"
|
|
380
|
+
? { reasoning: thinkingLevel }
|
|
381
|
+
: {}),
|
|
382
|
+
...(effectiveMaxTurns !== undefined
|
|
383
|
+
? { shouldStopAfterTurn: () => ++turnCount >= effectiveMaxTurns }
|
|
384
|
+
: {}),
|
|
385
|
+
};
|
|
288
386
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
387
|
+
const loop = args.agentLoop ?? agentLoop;
|
|
388
|
+
// ── Bridge stream function ──
|
|
389
|
+
const bridgeStreamFn = createBridgeStreamFn(streamSimple);
|
|
390
|
+
const streamFn = args.streamFn ?? bridgeStreamFn;
|
|
391
|
+
const stream = loop(prompts, context, config, signal, streamFn);
|
|
392
|
+
let agentError: string | undefined;
|
|
393
|
+
for await (const event of stream) {
|
|
394
|
+
// Tool execution collects candidate ids.
|
|
395
|
+
if (event.type === "agent_end") {
|
|
396
|
+
const msgs = ((event as any).messages || []) as Array<{
|
|
397
|
+
stopReason?: string;
|
|
398
|
+
errorMessage?: string;
|
|
399
|
+
}>;
|
|
400
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
401
|
+
if (lastMsg?.stopReason === "error") {
|
|
402
|
+
agentError = lastMsg.errorMessage ?? "Unknown API error";
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
await stream.result();
|
|
407
|
+
if (agentError && proposedDropIds.length === 0)
|
|
408
|
+
throw new Error(`Dropper API error: ${agentError}`);
|
|
409
|
+
const droppedIds = selectDropCandidates(
|
|
410
|
+
proposedDropIds,
|
|
411
|
+
observations,
|
|
412
|
+
maxDropsAllowed,
|
|
413
|
+
reflections,
|
|
414
|
+
);
|
|
415
|
+
const reason =
|
|
416
|
+
droppedIds.length > 0
|
|
417
|
+
? "selected_nonempty"
|
|
418
|
+
: toolCallCount === 0
|
|
419
|
+
? "no_tool_call"
|
|
420
|
+
: proposedDropIds.length === 0
|
|
421
|
+
? "all_filtered"
|
|
422
|
+
: "selected_empty";
|
|
423
|
+
const selectedDropTokens = droppedIds.reduce(
|
|
424
|
+
(sum, id) => sum + (allowed.get(id)?.tokenCount ?? 0),
|
|
425
|
+
0,
|
|
426
|
+
);
|
|
427
|
+
debugLog("dropper.result", {
|
|
428
|
+
reason,
|
|
429
|
+
toolCallCount,
|
|
430
|
+
rawRequestedIdsCount,
|
|
431
|
+
missingIdsCount,
|
|
432
|
+
criticalCandidateIdsCount,
|
|
433
|
+
duplicateInRequestCount,
|
|
434
|
+
duplicateInRunCount,
|
|
435
|
+
acceptedCandidateCount: proposedDropIds.length,
|
|
436
|
+
selectedDropsCount: droppedIds.length,
|
|
437
|
+
selectedDropTokens,
|
|
438
|
+
selectedCoverageSummaryByRelevance: summarizeCoverageByRelevanceForIds(
|
|
439
|
+
droppedIds,
|
|
440
|
+
observations,
|
|
441
|
+
coverageById,
|
|
442
|
+
),
|
|
443
|
+
maxDropsAllowed,
|
|
444
|
+
});
|
|
445
|
+
return droppedIds.length > 0 ? droppedIds : undefined;
|
|
331
446
|
}
|