@caupulican/pi-adaptative 0.80.75 → 0.80.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/core/agent-session.d.ts +22 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +54 -3
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/cost-guard.d.ts +55 -0
- package/dist/core/cost-guard.d.ts.map +1 -0
- package/dist/core/cost-guard.js +50 -0
- package/dist/core/cost-guard.js.map +1 -0
- package/dist/core/learning/reflection-engine.d.ts +7 -0
- package/dist/core/learning/reflection-engine.d.ts.map +1 -1
- package/dist/core/learning/reflection-engine.js +22 -13
- package/dist/core/learning/reflection-engine.js.map +1 -1
- package/dist/core/settings-manager.d.ts +10 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +7 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proactive per-turn token cost guard (Hermes-parity superiority item #34).
|
|
3
|
+
*
|
|
4
|
+
* Hermes (and pi today) only react to context growth by compressing AFTER it's expensive. This estimates
|
|
5
|
+
* the dollar cost of the NEXT LLM call BEFORE it is submitted, so the agent can warn the user or
|
|
6
|
+
* automatically reduce reasoning effort before a runaway billing spike — a proactive ceiling, not a
|
|
7
|
+
* reactive cleanup. Pure functions: no I/O, fully testable.
|
|
8
|
+
*/
|
|
9
|
+
/** Per-token USD prices (as carried on `Model.cost`, which is per-token, not per-million). */
|
|
10
|
+
export interface ModelTokenCost {
|
|
11
|
+
input: number;
|
|
12
|
+
output: number;
|
|
13
|
+
cacheRead?: number;
|
|
14
|
+
cacheWrite?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Estimate the USD cost of one turn: the whole current context is billed as input, plus up to
|
|
18
|
+
* `maxOutputTokens` of output. `cachedInputTokens` (prefix-cache hits) are billed at the cheaper
|
|
19
|
+
* cache-read rate instead of the full input rate. This is an UPPER bound on the turn (it assumes the
|
|
20
|
+
* model emits its full output budget), which is what a spending ceiling should bound against.
|
|
21
|
+
*/
|
|
22
|
+
export declare function estimateTurnCostUsd(args: {
|
|
23
|
+
inputTokens: number;
|
|
24
|
+
maxOutputTokens: number;
|
|
25
|
+
cost: ModelTokenCost;
|
|
26
|
+
cachedInputTokens?: number;
|
|
27
|
+
}): number;
|
|
28
|
+
/** What to do when a turn's projected cost exceeds the threshold. */
|
|
29
|
+
export type CostGuardAction = "warn" | "downgrade";
|
|
30
|
+
export interface CostGuardSettings {
|
|
31
|
+
/** Per-turn USD ceiling. `0` (default) disables the guard entirely. */
|
|
32
|
+
maxTurnUsd: number;
|
|
33
|
+
/** Over the ceiling: `warn` (surface a notice) or `downgrade` (also reduce reasoning effort). */
|
|
34
|
+
action: CostGuardAction;
|
|
35
|
+
}
|
|
36
|
+
export declare const DEFAULT_COST_GUARD_SETTINGS: CostGuardSettings;
|
|
37
|
+
export interface CostGuardDecision {
|
|
38
|
+
/** True when the guard is enabled AND the projected cost exceeds the ceiling. */
|
|
39
|
+
over: boolean;
|
|
40
|
+
estUsd: number;
|
|
41
|
+
thresholdUsd: number;
|
|
42
|
+
action: CostGuardAction;
|
|
43
|
+
}
|
|
44
|
+
/** Decide whether the projected turn cost trips the guard. Disabled (`maxTurnUsd<=0`) is never `over`. */
|
|
45
|
+
export declare function evaluateCostGuard(estUsd: number, settings: CostGuardSettings): CostGuardDecision;
|
|
46
|
+
/** Reasoning levels in descending cost order, used to pick the next-cheaper level on a downgrade. */
|
|
47
|
+
declare const REASONING_LADDER: readonly ["xhigh", "high", "medium", "low", "minimal", "off"];
|
|
48
|
+
export type ReasoningLevel = (typeof REASONING_LADDER)[number];
|
|
49
|
+
/**
|
|
50
|
+
* One step down the reasoning ladder (cost reduction) from `current`. Returns `current` unchanged when
|
|
51
|
+
* already at the floor or unrecognized — the guard never raises effort, only lowers it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function downgradeReasoning(current: string): ReasoningLevel | string;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=cost-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-guard.d.ts","sourceRoot":"","sources":["../../src/core/cost-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,MAAM,CAQT;AAED,qEAAqE;AACrE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IACjC,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,iGAAiG;IACjG,MAAM,EAAE,eAAe,CAAC;CACxB;AAED,eAAO,MAAM,2BAA2B,EAAE,iBAGzC,CAAC;AAEF,MAAM,WAAW,iBAAiB;IACjC,iFAAiF;IACjF,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;CACxB;AAED,0GAA0G;AAC1G,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,iBAAiB,CAQhG;AAED,qGAAqG;AACrG,QAAA,MAAM,gBAAgB,+DAAgE,CAAC;AACvF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,MAAM,CAI3E","sourcesContent":["/**\n * Proactive per-turn token cost guard (Hermes-parity superiority item #34).\n *\n * Hermes (and pi today) only react to context growth by compressing AFTER it's expensive. This estimates\n * the dollar cost of the NEXT LLM call BEFORE it is submitted, so the agent can warn the user or\n * automatically reduce reasoning effort before a runaway billing spike — a proactive ceiling, not a\n * reactive cleanup. Pure functions: no I/O, fully testable.\n */\n\n/** Per-token USD prices (as carried on `Model.cost`, which is per-token, not per-million). */\nexport interface ModelTokenCost {\n\tinput: number;\n\toutput: number;\n\tcacheRead?: number;\n\tcacheWrite?: number;\n}\n\n/**\n * Estimate the USD cost of one turn: the whole current context is billed as input, plus up to\n * `maxOutputTokens` of output. `cachedInputTokens` (prefix-cache hits) are billed at the cheaper\n * cache-read rate instead of the full input rate. This is an UPPER bound on the turn (it assumes the\n * model emits its full output budget), which is what a spending ceiling should bound against.\n */\nexport function estimateTurnCostUsd(args: {\n\tinputTokens: number;\n\tmaxOutputTokens: number;\n\tcost: ModelTokenCost;\n\tcachedInputTokens?: number;\n}): number {\n\tconst { inputTokens, maxOutputTokens, cost } = args;\n\tconst cached = Math.max(0, Math.min(args.cachedInputTokens ?? 0, inputTokens));\n\tconst freshInput = inputTokens - cached;\n\tconst cacheReadRate = cost.cacheRead ?? cost.input;\n\tconst inputUsd = freshInput * cost.input + cached * cacheReadRate;\n\tconst outputUsd = Math.max(0, maxOutputTokens) * cost.output;\n\treturn inputUsd + outputUsd;\n}\n\n/** What to do when a turn's projected cost exceeds the threshold. */\nexport type CostGuardAction = \"warn\" | \"downgrade\";\n\nexport interface CostGuardSettings {\n\t/** Per-turn USD ceiling. `0` (default) disables the guard entirely. */\n\tmaxTurnUsd: number;\n\t/** Over the ceiling: `warn` (surface a notice) or `downgrade` (also reduce reasoning effort). */\n\taction: CostGuardAction;\n}\n\nexport const DEFAULT_COST_GUARD_SETTINGS: CostGuardSettings = {\n\tmaxTurnUsd: 0,\n\taction: \"warn\",\n};\n\nexport interface CostGuardDecision {\n\t/** True when the guard is enabled AND the projected cost exceeds the ceiling. */\n\tover: boolean;\n\testUsd: number;\n\tthresholdUsd: number;\n\taction: CostGuardAction;\n}\n\n/** Decide whether the projected turn cost trips the guard. Disabled (`maxTurnUsd<=0`) is never `over`. */\nexport function evaluateCostGuard(estUsd: number, settings: CostGuardSettings): CostGuardDecision {\n\tconst enabled = settings.maxTurnUsd > 0;\n\treturn {\n\t\tover: enabled && estUsd > settings.maxTurnUsd,\n\t\testUsd,\n\t\tthresholdUsd: settings.maxTurnUsd,\n\t\taction: settings.action,\n\t};\n}\n\n/** Reasoning levels in descending cost order, used to pick the next-cheaper level on a downgrade. */\nconst REASONING_LADDER = [\"xhigh\", \"high\", \"medium\", \"low\", \"minimal\", \"off\"] as const;\nexport type ReasoningLevel = (typeof REASONING_LADDER)[number];\n\n/**\n * One step down the reasoning ladder (cost reduction) from `current`. Returns `current` unchanged when\n * already at the floor or unrecognized — the guard never raises effort, only lowers it.\n */\nexport function downgradeReasoning(current: string): ReasoningLevel | string {\n\tconst i = REASONING_LADDER.indexOf(current as ReasoningLevel);\n\tif (i < 0) return current;\n\treturn REASONING_LADDER[Math.min(i + 1, REASONING_LADDER.length - 1)];\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proactive per-turn token cost guard (Hermes-parity superiority item #34).
|
|
3
|
+
*
|
|
4
|
+
* Hermes (and pi today) only react to context growth by compressing AFTER it's expensive. This estimates
|
|
5
|
+
* the dollar cost of the NEXT LLM call BEFORE it is submitted, so the agent can warn the user or
|
|
6
|
+
* automatically reduce reasoning effort before a runaway billing spike — a proactive ceiling, not a
|
|
7
|
+
* reactive cleanup. Pure functions: no I/O, fully testable.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Estimate the USD cost of one turn: the whole current context is billed as input, plus up to
|
|
11
|
+
* `maxOutputTokens` of output. `cachedInputTokens` (prefix-cache hits) are billed at the cheaper
|
|
12
|
+
* cache-read rate instead of the full input rate. This is an UPPER bound on the turn (it assumes the
|
|
13
|
+
* model emits its full output budget), which is what a spending ceiling should bound against.
|
|
14
|
+
*/
|
|
15
|
+
export function estimateTurnCostUsd(args) {
|
|
16
|
+
const { inputTokens, maxOutputTokens, cost } = args;
|
|
17
|
+
const cached = Math.max(0, Math.min(args.cachedInputTokens ?? 0, inputTokens));
|
|
18
|
+
const freshInput = inputTokens - cached;
|
|
19
|
+
const cacheReadRate = cost.cacheRead ?? cost.input;
|
|
20
|
+
const inputUsd = freshInput * cost.input + cached * cacheReadRate;
|
|
21
|
+
const outputUsd = Math.max(0, maxOutputTokens) * cost.output;
|
|
22
|
+
return inputUsd + outputUsd;
|
|
23
|
+
}
|
|
24
|
+
export const DEFAULT_COST_GUARD_SETTINGS = {
|
|
25
|
+
maxTurnUsd: 0,
|
|
26
|
+
action: "warn",
|
|
27
|
+
};
|
|
28
|
+
/** Decide whether the projected turn cost trips the guard. Disabled (`maxTurnUsd<=0`) is never `over`. */
|
|
29
|
+
export function evaluateCostGuard(estUsd, settings) {
|
|
30
|
+
const enabled = settings.maxTurnUsd > 0;
|
|
31
|
+
return {
|
|
32
|
+
over: enabled && estUsd > settings.maxTurnUsd,
|
|
33
|
+
estUsd,
|
|
34
|
+
thresholdUsd: settings.maxTurnUsd,
|
|
35
|
+
action: settings.action,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Reasoning levels in descending cost order, used to pick the next-cheaper level on a downgrade. */
|
|
39
|
+
const REASONING_LADDER = ["xhigh", "high", "medium", "low", "minimal", "off"];
|
|
40
|
+
/**
|
|
41
|
+
* One step down the reasoning ladder (cost reduction) from `current`. Returns `current` unchanged when
|
|
42
|
+
* already at the floor or unrecognized — the guard never raises effort, only lowers it.
|
|
43
|
+
*/
|
|
44
|
+
export function downgradeReasoning(current) {
|
|
45
|
+
const i = REASONING_LADDER.indexOf(current);
|
|
46
|
+
if (i < 0)
|
|
47
|
+
return current;
|
|
48
|
+
return REASONING_LADDER[Math.min(i + 1, REASONING_LADDER.length - 1)];
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=cost-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-guard.js","sourceRoot":"","sources":["../../src/core/cost-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAKnC,EAAU;IACV,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;IAC/E,MAAM,UAAU,GAAG,WAAW,GAAG,MAAM,CAAC;IACxC,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC;IACnD,MAAM,QAAQ,GAAG,UAAU,GAAG,IAAI,CAAC,KAAK,GAAG,MAAM,GAAG,aAAa,CAAC;IAClE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IAC7D,OAAO,QAAQ,GAAG,SAAS,CAAC;AAAA,CAC5B;AAYD,MAAM,CAAC,MAAM,2BAA2B,GAAsB;IAC7D,UAAU,EAAE,CAAC;IACb,MAAM,EAAE,MAAM;CACd,CAAC;AAUF,0GAA0G;AAC1G,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,QAA2B,EAAqB;IACjG,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,GAAG,CAAC,CAAC;IACxC,OAAO;QACN,IAAI,EAAE,OAAO,IAAI,MAAM,GAAG,QAAQ,CAAC,UAAU;QAC7C,MAAM;QACN,YAAY,EAAE,QAAQ,CAAC,UAAU;QACjC,MAAM,EAAE,QAAQ,CAAC,MAAM;KACvB,CAAC;AAAA,CACF;AAED,qGAAqG;AACrG,MAAM,gBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAU,CAAC;AAGvF;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAA2B;IAC5E,MAAM,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,OAAyB,CAAC,CAAC;IAC9D,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IAC1B,OAAO,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AAAA,CACtE","sourcesContent":["/**\n * Proactive per-turn token cost guard (Hermes-parity superiority item #34).\n *\n * Hermes (and pi today) only react to context growth by compressing AFTER it's expensive. This estimates\n * the dollar cost of the NEXT LLM call BEFORE it is submitted, so the agent can warn the user or\n * automatically reduce reasoning effort before a runaway billing spike — a proactive ceiling, not a\n * reactive cleanup. Pure functions: no I/O, fully testable.\n */\n\n/** Per-token USD prices (as carried on `Model.cost`, which is per-token, not per-million). */\nexport interface ModelTokenCost {\n\tinput: number;\n\toutput: number;\n\tcacheRead?: number;\n\tcacheWrite?: number;\n}\n\n/**\n * Estimate the USD cost of one turn: the whole current context is billed as input, plus up to\n * `maxOutputTokens` of output. `cachedInputTokens` (prefix-cache hits) are billed at the cheaper\n * cache-read rate instead of the full input rate. This is an UPPER bound on the turn (it assumes the\n * model emits its full output budget), which is what a spending ceiling should bound against.\n */\nexport function estimateTurnCostUsd(args: {\n\tinputTokens: number;\n\tmaxOutputTokens: number;\n\tcost: ModelTokenCost;\n\tcachedInputTokens?: number;\n}): number {\n\tconst { inputTokens, maxOutputTokens, cost } = args;\n\tconst cached = Math.max(0, Math.min(args.cachedInputTokens ?? 0, inputTokens));\n\tconst freshInput = inputTokens - cached;\n\tconst cacheReadRate = cost.cacheRead ?? cost.input;\n\tconst inputUsd = freshInput * cost.input + cached * cacheReadRate;\n\tconst outputUsd = Math.max(0, maxOutputTokens) * cost.output;\n\treturn inputUsd + outputUsd;\n}\n\n/** What to do when a turn's projected cost exceeds the threshold. */\nexport type CostGuardAction = \"warn\" | \"downgrade\";\n\nexport interface CostGuardSettings {\n\t/** Per-turn USD ceiling. `0` (default) disables the guard entirely. */\n\tmaxTurnUsd: number;\n\t/** Over the ceiling: `warn` (surface a notice) or `downgrade` (also reduce reasoning effort). */\n\taction: CostGuardAction;\n}\n\nexport const DEFAULT_COST_GUARD_SETTINGS: CostGuardSettings = {\n\tmaxTurnUsd: 0,\n\taction: \"warn\",\n};\n\nexport interface CostGuardDecision {\n\t/** True when the guard is enabled AND the projected cost exceeds the ceiling. */\n\tover: boolean;\n\testUsd: number;\n\tthresholdUsd: number;\n\taction: CostGuardAction;\n}\n\n/** Decide whether the projected turn cost trips the guard. Disabled (`maxTurnUsd<=0`) is never `over`. */\nexport function evaluateCostGuard(estUsd: number, settings: CostGuardSettings): CostGuardDecision {\n\tconst enabled = settings.maxTurnUsd > 0;\n\treturn {\n\t\tover: enabled && estUsd > settings.maxTurnUsd,\n\t\testUsd,\n\t\tthresholdUsd: settings.maxTurnUsd,\n\t\taction: settings.action,\n\t};\n}\n\n/** Reasoning levels in descending cost order, used to pick the next-cheaper level on a downgrade. */\nconst REASONING_LADDER = [\"xhigh\", \"high\", \"medium\", \"low\", \"minimal\", \"off\"] as const;\nexport type ReasoningLevel = (typeof REASONING_LADDER)[number];\n\n/**\n * One step down the reasoning ladder (cost reduction) from `current`. Returns `current` unchanged when\n * already at the floor or unrecognized — the guard never raises effort, only lowers it.\n */\nexport function downgradeReasoning(current: string): ReasoningLevel | string {\n\tconst i = REASONING_LADDER.indexOf(current as ReasoningLevel);\n\tif (i < 0) return current;\n\treturn REASONING_LADDER[Math.min(i + 1, REASONING_LADDER.length - 1)];\n}\n"]}
|
|
@@ -51,6 +51,13 @@ export interface ReflectionResult {
|
|
|
51
51
|
usage: Usage;
|
|
52
52
|
rationale: string;
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* STATIC reflection system prompt (Hermes-parity #33). It is byte-identical across every reflection
|
|
56
|
+
* pass — the variable parts (existing memory snapshot + the turn transcript) live in the USER prompt —
|
|
57
|
+
* so the provider prompt-cache reuses this prefix instead of re-billing it each pass (cost guard).
|
|
58
|
+
* Do NOT interpolate per-call data into this constant or caching breaks.
|
|
59
|
+
*/
|
|
60
|
+
export declare const REFLECTION_SYSTEM_PROMPT = "You are a reflection engine. Your job is to analyze the recent conversation turn, compare it against the agent's existing memory, and decide if any memory updates are needed.\n\nMemory guidelines:\n- \"MEMORY\" is for project facts, configuration, repeatable workflows, and coding findings.\n- \"USER\" is for user preferences, patterns, and style specifications.\n- Avoid duplicate facts. If the fact is already represented, do not add it.\n- CONFRONT existing memory: if the new turn contradicts or updates an existing fact, use \"memory_replace\" or \"memory_remove\" to supersede the old fact rather than blindly appending.\n- Keep memories short, factual, and direct. No fluff.\n- Do NOT capture transient/environment-specific noise: tool/network failures, one-off errors, or a single narrative event. Persist only durable facts and preferences.\n- PROMOTE to behavior: if the turn established a REPEATABLE, multi-step PROCEDURE/workflow (not a one-off fact) that should govern a future class of tasks, emit a \"promote_skill\" instead of (or in addition to) a memory fact. Only promote a genuinely reusable procedure \u2014 never a single fact, a one-off narrative, or environment-specific noise. Prefer a memory fact when unsure.\n\nYou must output your analysis and writes in the following JSON format inside a ```json``` code fence:\n{\n \"rationale\": \"Explanation of your reasoning\",\n \"writes\": [\n { \"kind\": \"memory_add\", \"section\": \"MEMORY\" | \"USER\", \"text\": \"New direct fact to append\" },\n { \"kind\": \"memory_replace\", \"target\": \"Exact text substring to replace\", \"text\": \"New replacement text\" },\n { \"kind\": \"memory_remove\", \"target\": \"Exact text substring to remove\" },\n { \"kind\": \"promote_skill\", \"name\": \"kebab-case-skill-name\", \"description\": \"one line of when to use it\", \"body\": \"Markdown: the step-by-step procedure\" }\n ]\n}\n";
|
|
54
61
|
export declare class ReflectionEngine {
|
|
55
62
|
/**
|
|
56
63
|
* Build the reflection prompt, call the injected isolated complete(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reflection-engine.d.ts","sourceRoot":"","sources":["../../../src/core/learning/reflection-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAE3E,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,YAAY,GAAG,aAAa,GAAG,MAAM,CAAC;AAElF,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,UAAU,CAyB/D;AAED,MAAM,WAAW,eAAe;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;IAEjB,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;CAC1F;AAED,MAAM,MAAM,eAAe,GACxB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAEzC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,gBAAgB;IAC5B;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"reflection-engine.d.ts","sourceRoot":"","sources":["../../../src/core/learning/reflection-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAE3E,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,YAAY,GAAG,aAAa,GAAG,MAAM,CAAC;AAElF,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,UAAU,CAyB/D;AAED,MAAM,WAAW,eAAe;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC;IAEjB,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;CAC1F;AAED,MAAM,MAAM,eAAe,GACxB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAEzC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,s4DAqBpC,CAAC;AAEF,qBAAa,gBAAgB;IAC5B;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+E/D;CACD","sourcesContent":["import type { Usage } from \"@caupulican/pi-ai\";\n\nexport type StopReason = \"stop\" | \"toolUse\" | \"aborted\" | \"error\" | string;\n\nexport interface IsolatedCompletionResult {\n\ttext: string;\n\tusage: Usage;\n\tstopReason: StopReason;\n}\n\nexport type ReflectionTrigger = \"complex\" | \"corrective\" | \"session-end\" | \"none\";\n\nexport interface DemandSignals {\n\ttrigger: ReflectionTrigger;\n\ttoolCallCount: number;\n\thadCorrection: boolean;\n\tcontextHeadroomPct: number; // 0..100\n\tusefulLately: number; // 0..1 rolling score\n}\n\nexport interface DemandPlan {\n\tact: \"skip\" | \"reflect\";\n\treason: string;\n\ttokenBudget: number;\n}\n\n/**\n * Pure zero-I/O heuristic to decide whether the current turn justifies a reflection run\n * and determine the token budget under the cheap-tool net-negative doctrine.\n */\nexport function decideDemand(signals: DemandSignals): DemandPlan {\n\tif (signals.trigger === \"none\") {\n\t\treturn { act: \"skip\", reason: \"No trigger detected\", tokenBudget: 0 };\n\t}\n\tif (signals.contextHeadroomPct < 10) {\n\t\treturn { act: \"skip\", reason: \"Context headroom is critically low (< 10%)\", tokenBudget: 0 };\n\t}\n\n\t// Dynamic token budget based on headroom (keep reflection bounded between 500 and 1500 tokens)\n\tconst baseBudget = 1000;\n\tconst tokenBudget = Math.max(500, Math.min(1500, Math.round(baseBudget * (signals.contextHeadroomPct / 100))));\n\n\tif (signals.hadCorrection) {\n\t\treturn { act: \"reflect\", reason: \"Correction detected in the turn\", tokenBudget };\n\t}\n\tif (signals.trigger === \"session-end\") {\n\t\treturn { act: \"reflect\", reason: \"Session end reflection triggered\", tokenBudget };\n\t}\n\tif (signals.trigger === \"complex\") {\n\t\tif (signals.toolCallCount >= 3) {\n\t\t\treturn { act: \"reflect\", reason: `Complex turn with ${signals.toolCallCount} tool calls`, tokenBudget };\n\t\t}\n\t}\n\n\treturn { act: \"skip\", reason: \"Signals do not justify reflection overhead\", tokenBudget: 0 };\n}\n\nexport interface ReflectionInput {\n\trecentTurnText: string; // host serializes the just-finished turn\n\texistingMemory: string; // current MEMORY.md + USER.md snapshot\n\tplan: DemandPlan;\n\t// host-injected isolated completion function:\n\tcomplete: (systemPrompt: string, userPrompt: string) => Promise<IsolatedCompletionResult>;\n}\n\nexport type ReflectionWrite =\n\t| { kind: \"memory_add\"; section: \"MEMORY\" | \"USER\"; text: string }\n\t| { kind: \"memory_replace\"; target: string; text: string }\n\t| { kind: \"memory_remove\"; target: string }\n\t// R7 memory-to-behavior: promote a recurring procedural workflow into an executable skill.\n\t| { kind: \"promote_skill\"; name: string; description: string; body: string };\n\nexport interface ReflectionResult {\n\twrites: ReflectionWrite[];\n\tusage: Usage;\n\trationale: string;\n}\n\n/**\n * STATIC reflection system prompt (Hermes-parity #33). It is byte-identical across every reflection\n * pass — the variable parts (existing memory snapshot + the turn transcript) live in the USER prompt —\n * so the provider prompt-cache reuses this prefix instead of re-billing it each pass (cost guard).\n * Do NOT interpolate per-call data into this constant or caching breaks.\n */\nexport const REFLECTION_SYSTEM_PROMPT = `You are a reflection engine. Your job is to analyze the recent conversation turn, compare it against the agent's existing memory, and decide if any memory updates are needed.\n\nMemory guidelines:\n- \"MEMORY\" is for project facts, configuration, repeatable workflows, and coding findings.\n- \"USER\" is for user preferences, patterns, and style specifications.\n- Avoid duplicate facts. If the fact is already represented, do not add it.\n- CONFRONT existing memory: if the new turn contradicts or updates an existing fact, use \"memory_replace\" or \"memory_remove\" to supersede the old fact rather than blindly appending.\n- Keep memories short, factual, and direct. No fluff.\n- Do NOT capture transient/environment-specific noise: tool/network failures, one-off errors, or a single narrative event. Persist only durable facts and preferences.\n- PROMOTE to behavior: if the turn established a REPEATABLE, multi-step PROCEDURE/workflow (not a one-off fact) that should govern a future class of tasks, emit a \"promote_skill\" instead of (or in addition to) a memory fact. Only promote a genuinely reusable procedure — never a single fact, a one-off narrative, or environment-specific noise. Prefer a memory fact when unsure.\n\nYou must output your analysis and writes in the following JSON format inside a \\`\\`\\`json\\`\\`\\` code fence:\n{\n \"rationale\": \"Explanation of your reasoning\",\n \"writes\": [\n { \"kind\": \"memory_add\", \"section\": \"MEMORY\" | \"USER\", \"text\": \"New direct fact to append\" },\n { \"kind\": \"memory_replace\", \"target\": \"Exact text substring to replace\", \"text\": \"New replacement text\" },\n { \"kind\": \"memory_remove\", \"target\": \"Exact text substring to remove\" },\n { \"kind\": \"promote_skill\", \"name\": \"kebab-case-skill-name\", \"description\": \"one line of when to use it\", \"body\": \"Markdown: the step-by-step procedure\" }\n ]\n}\n`;\n\nexport class ReflectionEngine {\n\t/**\n\t * Build the reflection prompt, call the injected isolated complete(),\n\t * parse the response, confront existing memory, and return memory writes.\n\t * Zero direct I/O.\n\t */\n\tasync reflect(input: ReflectionInput): Promise<ReflectionResult> {\n\t\tconst systemPrompt = REFLECTION_SYSTEM_PROMPT;\n\n\t\t// Variable inputs go in the USER prompt so the system prefix above stays cache-stable (#33).\n\t\tconst userPrompt = `Existing Memory snapshot:\n${input.existingMemory}\n\nRecent turn transcript:\n${input.recentTurnText}\n\nAnalyze this turn against the existing memory and output your memory updates.`;\n\n\t\ttry {\n\t\t\tconst compResult = await input.complete(systemPrompt, userPrompt);\n\t\t\tconst text = compResult.text;\n\n\t\t\tconst jsonMatch = text.match(/```json\\s*([\\s\\S]*?)\\s*```/) || text.match(/{[\\s\\S]*}/);\n\t\t\tif (!jsonMatch) {\n\t\t\t\treturn {\n\t\t\t\t\twrites: [],\n\t\t\t\t\tusage: compResult.usage,\n\t\t\t\t\trationale: `Failed to locate JSON response. Raw text:\\n${text}`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst parsed = JSON.parse(jsonMatch[1] || jsonMatch[0]);\n\t\t\tconst rationale = parsed.rationale || \"\";\n\t\t\tconst writes: ReflectionWrite[] = [];\n\n\t\t\tif (Array.isArray(parsed.writes)) {\n\t\t\t\tfor (const w of parsed.writes) {\n\t\t\t\t\tif (w && typeof w === \"object\") {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tw.kind === \"memory_add\" &&\n\t\t\t\t\t\t\t(w.section === \"MEMORY\" || w.section === \"USER\") &&\n\t\t\t\t\t\t\ttypeof w.text === \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\twrites.push({ kind: \"memory_add\", section: w.section, text: w.text });\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tw.kind === \"memory_replace\" &&\n\t\t\t\t\t\t\ttypeof w.target === \"string\" &&\n\t\t\t\t\t\t\ttypeof w.text === \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\twrites.push({ kind: \"memory_replace\", target: w.target, text: w.text });\n\t\t\t\t\t\t} else if (w.kind === \"memory_remove\" && typeof w.target === \"string\") {\n\t\t\t\t\t\t\twrites.push({ kind: \"memory_remove\", target: w.target });\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tw.kind === \"promote_skill\" &&\n\t\t\t\t\t\t\ttypeof w.name === \"string\" &&\n\t\t\t\t\t\t\ttypeof w.description === \"string\" &&\n\t\t\t\t\t\t\ttypeof w.body === \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\twrites.push({ kind: \"promote_skill\", name: w.name, description: w.description, body: w.body });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\twrites,\n\t\t\t\tusage: compResult.usage,\n\t\t\t\trationale,\n\t\t\t};\n\t\t} catch (err) {\n\t\t\t// Zeroed/fallback usage representation\n\t\t\tconst emptyUsage: Usage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\treturn {\n\t\t\t\twrites: [],\n\t\t\t\tusage: emptyUsage,\n\t\t\t\trationale: `Error during reflection: ${String(err)}`,\n\t\t\t};\n\t\t}\n\t}\n}\n"]}
|
|
@@ -25,17 +25,13 @@ export function decideDemand(signals) {
|
|
|
25
25
|
}
|
|
26
26
|
return { act: "skip", reason: "Signals do not justify reflection overhead", tokenBudget: 0 };
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const systemPrompt = `You are a reflection engine. Your job is to analyze the recent conversation turn, compare it against the agent's existing memory, and decide if any memory updates are needed.
|
|
36
|
-
|
|
37
|
-
Existing Memory snapshot:
|
|
38
|
-
${input.existingMemory}
|
|
28
|
+
/**
|
|
29
|
+
* STATIC reflection system prompt (Hermes-parity #33). It is byte-identical across every reflection
|
|
30
|
+
* pass — the variable parts (existing memory snapshot + the turn transcript) live in the USER prompt —
|
|
31
|
+
* so the provider prompt-cache reuses this prefix instead of re-billing it each pass (cost guard).
|
|
32
|
+
* Do NOT interpolate per-call data into this constant or caching breaks.
|
|
33
|
+
*/
|
|
34
|
+
export const REFLECTION_SYSTEM_PROMPT = `You are a reflection engine. Your job is to analyze the recent conversation turn, compare it against the agent's existing memory, and decide if any memory updates are needed.
|
|
39
35
|
|
|
40
36
|
Memory guidelines:
|
|
41
37
|
- "MEMORY" is for project facts, configuration, repeatable workflows, and coding findings.
|
|
@@ -43,6 +39,7 @@ Memory guidelines:
|
|
|
43
39
|
- Avoid duplicate facts. If the fact is already represented, do not add it.
|
|
44
40
|
- CONFRONT existing memory: if the new turn contradicts or updates an existing fact, use "memory_replace" or "memory_remove" to supersede the old fact rather than blindly appending.
|
|
45
41
|
- Keep memories short, factual, and direct. No fluff.
|
|
42
|
+
- Do NOT capture transient/environment-specific noise: tool/network failures, one-off errors, or a single narrative event. Persist only durable facts and preferences.
|
|
46
43
|
- PROMOTE to behavior: if the turn established a REPEATABLE, multi-step PROCEDURE/workflow (not a one-off fact) that should govern a future class of tasks, emit a "promote_skill" instead of (or in addition to) a memory fact. Only promote a genuinely reusable procedure — never a single fact, a one-off narrative, or environment-specific noise. Prefer a memory fact when unsure.
|
|
47
44
|
|
|
48
45
|
You must output your analysis and writes in the following JSON format inside a \`\`\`json\`\`\` code fence:
|
|
@@ -56,10 +53,22 @@ You must output your analysis and writes in the following JSON format inside a \
|
|
|
56
53
|
]
|
|
57
54
|
}
|
|
58
55
|
`;
|
|
59
|
-
|
|
56
|
+
export class ReflectionEngine {
|
|
57
|
+
/**
|
|
58
|
+
* Build the reflection prompt, call the injected isolated complete(),
|
|
59
|
+
* parse the response, confront existing memory, and return memory writes.
|
|
60
|
+
* Zero direct I/O.
|
|
61
|
+
*/
|
|
62
|
+
async reflect(input) {
|
|
63
|
+
const systemPrompt = REFLECTION_SYSTEM_PROMPT;
|
|
64
|
+
// Variable inputs go in the USER prompt so the system prefix above stays cache-stable (#33).
|
|
65
|
+
const userPrompt = `Existing Memory snapshot:
|
|
66
|
+
${input.existingMemory}
|
|
67
|
+
|
|
68
|
+
Recent turn transcript:
|
|
60
69
|
${input.recentTurnText}
|
|
61
70
|
|
|
62
|
-
Analyze this turn and output your memory updates.`;
|
|
71
|
+
Analyze this turn against the existing memory and output your memory updates.`;
|
|
63
72
|
try {
|
|
64
73
|
const compResult = await input.complete(systemPrompt, userPrompt);
|
|
65
74
|
const text = compResult.text;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reflection-engine.js","sourceRoot":"","sources":["../../../src/core/learning/reflection-engine.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Usage } from \"@caupulican/pi-ai\";\n\nexport type StopReason = \"stop\" | \"toolUse\" | \"aborted\" | \"error\" | string;\n\nexport interface IsolatedCompletionResult {\n\ttext: string;\n\tusage: Usage;\n\tstopReason: StopReason;\n}\n\nexport type ReflectionTrigger = \"complex\" | \"corrective\" | \"session-end\" | \"none\";\n\nexport interface DemandSignals {\n\ttrigger: ReflectionTrigger;\n\ttoolCallCount: number;\n\thadCorrection: boolean;\n\tcontextHeadroomPct: number; // 0..100\n\tusefulLately: number; // 0..1 rolling score\n}\n\nexport interface DemandPlan {\n\tact: \"skip\" | \"reflect\";\n\treason: string;\n\ttokenBudget: number;\n}\n\n/**\n * Pure zero-I/O heuristic to decide whether the current turn justifies a reflection run\n * and determine the token budget under the cheap-tool net-negative doctrine.\n */\nexport function decideDemand(signals: DemandSignals): DemandPlan {\n\tif (signals.trigger === \"none\") {\n\t\treturn { act: \"skip\", reason: \"No trigger detected\", tokenBudget: 0 };\n\t}\n\tif (signals.contextHeadroomPct < 10) {\n\t\treturn { act: \"skip\", reason: \"Context headroom is critically low (< 10%)\", tokenBudget: 0 };\n\t}\n\n\t// Dynamic token budget based on headroom (keep reflection bounded between 500 and 1500 tokens)\n\tconst baseBudget = 1000;\n\tconst tokenBudget = Math.max(500, Math.min(1500, Math.round(baseBudget * (signals.contextHeadroomPct / 100))));\n\n\tif (signals.hadCorrection) {\n\t\treturn { act: \"reflect\", reason: \"Correction detected in the turn\", tokenBudget };\n\t}\n\tif (signals.trigger === \"session-end\") {\n\t\treturn { act: \"reflect\", reason: \"Session end reflection triggered\", tokenBudget };\n\t}\n\tif (signals.trigger === \"complex\") {\n\t\tif (signals.toolCallCount >= 3) {\n\t\t\treturn { act: \"reflect\", reason: `Complex turn with ${signals.toolCallCount} tool calls`, tokenBudget };\n\t\t}\n\t}\n\n\treturn { act: \"skip\", reason: \"Signals do not justify reflection overhead\", tokenBudget: 0 };\n}\n\nexport interface ReflectionInput {\n\trecentTurnText: string; // host serializes the just-finished turn\n\texistingMemory: string; // current MEMORY.md + USER.md snapshot\n\tplan: DemandPlan;\n\t// host-injected isolated completion function:\n\tcomplete: (systemPrompt: string, userPrompt: string) => Promise<IsolatedCompletionResult>;\n}\n\nexport type ReflectionWrite =\n\t| { kind: \"memory_add\"; section: \"MEMORY\" | \"USER\"; text: string }\n\t| { kind: \"memory_replace\"; target: string; text: string }\n\t| { kind: \"memory_remove\"; target: string }\n\t// R7 memory-to-behavior: promote a recurring procedural workflow into an executable skill.\n\t| { kind: \"promote_skill\"; name: string; description: string; body: string };\n\nexport interface ReflectionResult {\n\twrites: ReflectionWrite[];\n\tusage: Usage;\n\trationale: string;\n}\n\nexport class ReflectionEngine {\n\t/**\n\t * Build the reflection prompt, call the injected isolated complete(),\n\t * parse the response, confront existing memory, and return memory writes.\n\t * Zero direct I/O.\n\t */\n\tasync reflect(input: ReflectionInput): Promise<ReflectionResult> {\n\t\tconst systemPrompt = `You are a reflection engine. Your job is to analyze the recent conversation turn, compare it against the agent's existing memory, and decide if any memory updates are needed.\n\nExisting Memory snapshot:\n${input.existingMemory}\n\nMemory guidelines:\n- \"MEMORY\" is for project facts, configuration, repeatable workflows, and coding findings.\n- \"USER\" is for user preferences, patterns, and style specifications.\n- Avoid duplicate facts. If the fact is already represented, do not add it.\n- CONFRONT existing memory: if the new turn contradicts or updates an existing fact, use \"memory_replace\" or \"memory_remove\" to supersede the old fact rather than blindly appending.\n- Keep memories short, factual, and direct. No fluff.\n- PROMOTE to behavior: if the turn established a REPEATABLE, multi-step PROCEDURE/workflow (not a one-off fact) that should govern a future class of tasks, emit a \"promote_skill\" instead of (or in addition to) a memory fact. Only promote a genuinely reusable procedure — never a single fact, a one-off narrative, or environment-specific noise. Prefer a memory fact when unsure.\n\nYou must output your analysis and writes in the following JSON format inside a \\`\\`\\`json\\`\\`\\` code fence:\n{\n \"rationale\": \"Explanation of your reasoning\",\n \"writes\": [\n { \"kind\": \"memory_add\", \"section\": \"MEMORY\" | \"USER\", \"text\": \"New direct fact to append\" },\n { \"kind\": \"memory_replace\", \"target\": \"Exact text substring to replace\", \"text\": \"New replacement text\" },\n { \"kind\": \"memory_remove\", \"target\": \"Exact text substring to remove\" },\n { \"kind\": \"promote_skill\", \"name\": \"kebab-case-skill-name\", \"description\": \"one line of when to use it\", \"body\": \"Markdown: the step-by-step procedure\" }\n ]\n}\n`;\n\n\t\tconst userPrompt = `Recent turn transcript:\n${input.recentTurnText}\n\nAnalyze this turn and output your memory updates.`;\n\n\t\ttry {\n\t\t\tconst compResult = await input.complete(systemPrompt, userPrompt);\n\t\t\tconst text = compResult.text;\n\n\t\t\tconst jsonMatch = text.match(/```json\\s*([\\s\\S]*?)\\s*```/) || text.match(/{[\\s\\S]*}/);\n\t\t\tif (!jsonMatch) {\n\t\t\t\treturn {\n\t\t\t\t\twrites: [],\n\t\t\t\t\tusage: compResult.usage,\n\t\t\t\t\trationale: `Failed to locate JSON response. Raw text:\\n${text}`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst parsed = JSON.parse(jsonMatch[1] || jsonMatch[0]);\n\t\t\tconst rationale = parsed.rationale || \"\";\n\t\t\tconst writes: ReflectionWrite[] = [];\n\n\t\t\tif (Array.isArray(parsed.writes)) {\n\t\t\t\tfor (const w of parsed.writes) {\n\t\t\t\t\tif (w && typeof w === \"object\") {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tw.kind === \"memory_add\" &&\n\t\t\t\t\t\t\t(w.section === \"MEMORY\" || w.section === \"USER\") &&\n\t\t\t\t\t\t\ttypeof w.text === \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\twrites.push({ kind: \"memory_add\", section: w.section, text: w.text });\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tw.kind === \"memory_replace\" &&\n\t\t\t\t\t\t\ttypeof w.target === \"string\" &&\n\t\t\t\t\t\t\ttypeof w.text === \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\twrites.push({ kind: \"memory_replace\", target: w.target, text: w.text });\n\t\t\t\t\t\t} else if (w.kind === \"memory_remove\" && typeof w.target === \"string\") {\n\t\t\t\t\t\t\twrites.push({ kind: \"memory_remove\", target: w.target });\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tw.kind === \"promote_skill\" &&\n\t\t\t\t\t\t\ttypeof w.name === \"string\" &&\n\t\t\t\t\t\t\ttypeof w.description === \"string\" &&\n\t\t\t\t\t\t\ttypeof w.body === \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\twrites.push({ kind: \"promote_skill\", name: w.name, description: w.description, body: w.body });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\twrites,\n\t\t\t\tusage: compResult.usage,\n\t\t\t\trationale,\n\t\t\t};\n\t\t} catch (err) {\n\t\t\t// Zeroed/fallback usage representation\n\t\t\tconst emptyUsage: Usage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\treturn {\n\t\t\t\twrites: [],\n\t\t\t\tusage: emptyUsage,\n\t\t\t\trationale: `Error during reflection: ${String(err)}`,\n\t\t\t};\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reflection-engine.js","sourceRoot":"","sources":["../../../src/core/learning/reflection-engine.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Usage } from \"@caupulican/pi-ai\";\n\nexport type StopReason = \"stop\" | \"toolUse\" | \"aborted\" | \"error\" | string;\n\nexport interface IsolatedCompletionResult {\n\ttext: string;\n\tusage: Usage;\n\tstopReason: StopReason;\n}\n\nexport type ReflectionTrigger = \"complex\" | \"corrective\" | \"session-end\" | \"none\";\n\nexport interface DemandSignals {\n\ttrigger: ReflectionTrigger;\n\ttoolCallCount: number;\n\thadCorrection: boolean;\n\tcontextHeadroomPct: number; // 0..100\n\tusefulLately: number; // 0..1 rolling score\n}\n\nexport interface DemandPlan {\n\tact: \"skip\" | \"reflect\";\n\treason: string;\n\ttokenBudget: number;\n}\n\n/**\n * Pure zero-I/O heuristic to decide whether the current turn justifies a reflection run\n * and determine the token budget under the cheap-tool net-negative doctrine.\n */\nexport function decideDemand(signals: DemandSignals): DemandPlan {\n\tif (signals.trigger === \"none\") {\n\t\treturn { act: \"skip\", reason: \"No trigger detected\", tokenBudget: 0 };\n\t}\n\tif (signals.contextHeadroomPct < 10) {\n\t\treturn { act: \"skip\", reason: \"Context headroom is critically low (< 10%)\", tokenBudget: 0 };\n\t}\n\n\t// Dynamic token budget based on headroom (keep reflection bounded between 500 and 1500 tokens)\n\tconst baseBudget = 1000;\n\tconst tokenBudget = Math.max(500, Math.min(1500, Math.round(baseBudget * (signals.contextHeadroomPct / 100))));\n\n\tif (signals.hadCorrection) {\n\t\treturn { act: \"reflect\", reason: \"Correction detected in the turn\", tokenBudget };\n\t}\n\tif (signals.trigger === \"session-end\") {\n\t\treturn { act: \"reflect\", reason: \"Session end reflection triggered\", tokenBudget };\n\t}\n\tif (signals.trigger === \"complex\") {\n\t\tif (signals.toolCallCount >= 3) {\n\t\t\treturn { act: \"reflect\", reason: `Complex turn with ${signals.toolCallCount} tool calls`, tokenBudget };\n\t\t}\n\t}\n\n\treturn { act: \"skip\", reason: \"Signals do not justify reflection overhead\", tokenBudget: 0 };\n}\n\nexport interface ReflectionInput {\n\trecentTurnText: string; // host serializes the just-finished turn\n\texistingMemory: string; // current MEMORY.md + USER.md snapshot\n\tplan: DemandPlan;\n\t// host-injected isolated completion function:\n\tcomplete: (systemPrompt: string, userPrompt: string) => Promise<IsolatedCompletionResult>;\n}\n\nexport type ReflectionWrite =\n\t| { kind: \"memory_add\"; section: \"MEMORY\" | \"USER\"; text: string }\n\t| { kind: \"memory_replace\"; target: string; text: string }\n\t| { kind: \"memory_remove\"; target: string }\n\t// R7 memory-to-behavior: promote a recurring procedural workflow into an executable skill.\n\t| { kind: \"promote_skill\"; name: string; description: string; body: string };\n\nexport interface ReflectionResult {\n\twrites: ReflectionWrite[];\n\tusage: Usage;\n\trationale: string;\n}\n\n/**\n * STATIC reflection system prompt (Hermes-parity #33). It is byte-identical across every reflection\n * pass — the variable parts (existing memory snapshot + the turn transcript) live in the USER prompt —\n * so the provider prompt-cache reuses this prefix instead of re-billing it each pass (cost guard).\n * Do NOT interpolate per-call data into this constant or caching breaks.\n */\nexport const REFLECTION_SYSTEM_PROMPT = `You are a reflection engine. Your job is to analyze the recent conversation turn, compare it against the agent's existing memory, and decide if any memory updates are needed.\n\nMemory guidelines:\n- \"MEMORY\" is for project facts, configuration, repeatable workflows, and coding findings.\n- \"USER\" is for user preferences, patterns, and style specifications.\n- Avoid duplicate facts. If the fact is already represented, do not add it.\n- CONFRONT existing memory: if the new turn contradicts or updates an existing fact, use \"memory_replace\" or \"memory_remove\" to supersede the old fact rather than blindly appending.\n- Keep memories short, factual, and direct. No fluff.\n- Do NOT capture transient/environment-specific noise: tool/network failures, one-off errors, or a single narrative event. Persist only durable facts and preferences.\n- PROMOTE to behavior: if the turn established a REPEATABLE, multi-step PROCEDURE/workflow (not a one-off fact) that should govern a future class of tasks, emit a \"promote_skill\" instead of (or in addition to) a memory fact. Only promote a genuinely reusable procedure — never a single fact, a one-off narrative, or environment-specific noise. Prefer a memory fact when unsure.\n\nYou must output your analysis and writes in the following JSON format inside a \\`\\`\\`json\\`\\`\\` code fence:\n{\n \"rationale\": \"Explanation of your reasoning\",\n \"writes\": [\n { \"kind\": \"memory_add\", \"section\": \"MEMORY\" | \"USER\", \"text\": \"New direct fact to append\" },\n { \"kind\": \"memory_replace\", \"target\": \"Exact text substring to replace\", \"text\": \"New replacement text\" },\n { \"kind\": \"memory_remove\", \"target\": \"Exact text substring to remove\" },\n { \"kind\": \"promote_skill\", \"name\": \"kebab-case-skill-name\", \"description\": \"one line of when to use it\", \"body\": \"Markdown: the step-by-step procedure\" }\n ]\n}\n`;\n\nexport class ReflectionEngine {\n\t/**\n\t * Build the reflection prompt, call the injected isolated complete(),\n\t * parse the response, confront existing memory, and return memory writes.\n\t * Zero direct I/O.\n\t */\n\tasync reflect(input: ReflectionInput): Promise<ReflectionResult> {\n\t\tconst systemPrompt = REFLECTION_SYSTEM_PROMPT;\n\n\t\t// Variable inputs go in the USER prompt so the system prefix above stays cache-stable (#33).\n\t\tconst userPrompt = `Existing Memory snapshot:\n${input.existingMemory}\n\nRecent turn transcript:\n${input.recentTurnText}\n\nAnalyze this turn against the existing memory and output your memory updates.`;\n\n\t\ttry {\n\t\t\tconst compResult = await input.complete(systemPrompt, userPrompt);\n\t\t\tconst text = compResult.text;\n\n\t\t\tconst jsonMatch = text.match(/```json\\s*([\\s\\S]*?)\\s*```/) || text.match(/{[\\s\\S]*}/);\n\t\t\tif (!jsonMatch) {\n\t\t\t\treturn {\n\t\t\t\t\twrites: [],\n\t\t\t\t\tusage: compResult.usage,\n\t\t\t\t\trationale: `Failed to locate JSON response. Raw text:\\n${text}`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst parsed = JSON.parse(jsonMatch[1] || jsonMatch[0]);\n\t\t\tconst rationale = parsed.rationale || \"\";\n\t\t\tconst writes: ReflectionWrite[] = [];\n\n\t\t\tif (Array.isArray(parsed.writes)) {\n\t\t\t\tfor (const w of parsed.writes) {\n\t\t\t\t\tif (w && typeof w === \"object\") {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tw.kind === \"memory_add\" &&\n\t\t\t\t\t\t\t(w.section === \"MEMORY\" || w.section === \"USER\") &&\n\t\t\t\t\t\t\ttypeof w.text === \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\twrites.push({ kind: \"memory_add\", section: w.section, text: w.text });\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tw.kind === \"memory_replace\" &&\n\t\t\t\t\t\t\ttypeof w.target === \"string\" &&\n\t\t\t\t\t\t\ttypeof w.text === \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\twrites.push({ kind: \"memory_replace\", target: w.target, text: w.text });\n\t\t\t\t\t\t} else if (w.kind === \"memory_remove\" && typeof w.target === \"string\") {\n\t\t\t\t\t\t\twrites.push({ kind: \"memory_remove\", target: w.target });\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tw.kind === \"promote_skill\" &&\n\t\t\t\t\t\t\ttypeof w.name === \"string\" &&\n\t\t\t\t\t\t\ttypeof w.description === \"string\" &&\n\t\t\t\t\t\t\ttypeof w.body === \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\twrites.push({ kind: \"promote_skill\", name: w.name, description: w.description, body: w.body });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\twrites,\n\t\t\t\tusage: compResult.usage,\n\t\t\t\trationale,\n\t\t\t};\n\t\t} catch (err) {\n\t\t\t// Zeroed/fallback usage representation\n\t\t\tconst emptyUsage: Usage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\treturn {\n\t\t\t\twrites: [],\n\t\t\t\tusage: emptyUsage,\n\t\t\t\trationale: `Error during reflection: ${String(err)}`,\n\t\t\t};\n\t\t}\n\t}\n}\n"]}
|
|
@@ -123,6 +123,11 @@ export interface Settings {
|
|
|
123
123
|
/** Resource catalog directory (round resource management): the folder pi installs/updates/backs up from. */
|
|
124
124
|
catalogDir?: string;
|
|
125
125
|
compaction?: CompactionSettings;
|
|
126
|
+
/** Proactive per-turn cost guard (#34). */
|
|
127
|
+
costGuard?: {
|
|
128
|
+
maxTurnUsd?: number;
|
|
129
|
+
action?: "warn" | "downgrade";
|
|
130
|
+
};
|
|
126
131
|
contextGc?: ContextGcSettings;
|
|
127
132
|
branchSummary?: BranchSummarySettings;
|
|
128
133
|
retry?: RetrySettings;
|
|
@@ -339,6 +344,11 @@ export declare class SettingsManager {
|
|
|
339
344
|
getCompactionReserveTokens(): number;
|
|
340
345
|
getCompactionKeepRecentTokens(): number;
|
|
341
346
|
getCompactionTriggerPercent(): number;
|
|
347
|
+
/** Proactive per-turn cost guard (#34). `maxTurnUsd<=0` (default) disables it. */
|
|
348
|
+
getCostGuardSettings(): {
|
|
349
|
+
maxTurnUsd: number;
|
|
350
|
+
action: "warn" | "downgrade";
|
|
351
|
+
};
|
|
342
352
|
/** Configured auxiliary summarizer model id, or "auto" (default) to pick the cheapest authed model. */
|
|
343
353
|
getCompactionModel(): string;
|
|
344
354
|
getCompactionSettings(): {
|