@dory-agentic/dory-agentic-sdk 0.2.0
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/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/agent/createLfsAgent.d.ts +27 -0
- package/dist/agent/createLfsAgent.d.ts.map +1 -0
- package/dist/agent/createLfsAgent.js +51 -0
- package/dist/agent/loopConfig.d.ts +6 -0
- package/dist/agent/loopConfig.d.ts.map +1 -0
- package/dist/agent/loopConfig.js +5 -0
- package/dist/agent/runLfsTurn.d.ts +10 -0
- package/dist/agent/runLfsTurn.d.ts.map +1 -0
- package/dist/agent/runLfsTurn.js +20 -0
- package/dist/history/dorycodeAdapter.d.ts +16 -0
- package/dist/history/dorycodeAdapter.d.ts.map +1 -0
- package/dist/history/dorycodeAdapter.js +18 -0
- package/dist/history/formatForModel.d.ts +19 -0
- package/dist/history/formatForModel.d.ts.map +1 -0
- package/dist/history/formatForModel.js +22 -0
- package/dist/history/lanes.d.ts +3 -0
- package/dist/history/lanes.d.ts.map +1 -0
- package/dist/history/lanes.js +3 -0
- package/dist/history/messageManifest.d.ts +45 -0
- package/dist/history/messageManifest.d.ts.map +1 -0
- package/dist/history/messageManifest.js +231 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/lfs/adaptiveCoefficients.d.ts +13 -0
- package/dist/lfs/adaptiveCoefficients.d.ts.map +1 -0
- package/dist/lfs/adaptiveCoefficients.js +16 -0
- package/dist/lfs/attenuationCompiler.d.ts +16 -0
- package/dist/lfs/attenuationCompiler.d.ts.map +1 -0
- package/dist/lfs/attenuationCompiler.js +123 -0
- package/dist/lfs/chunkManager.d.ts +6 -0
- package/dist/lfs/chunkManager.d.ts.map +1 -0
- package/dist/lfs/chunkManager.js +142 -0
- package/dist/lfs/config.d.ts +23 -0
- package/dist/lfs/config.d.ts.map +1 -0
- package/dist/lfs/config.js +34 -0
- package/dist/lfs/contextAdapter.d.ts +5 -0
- package/dist/lfs/contextAdapter.d.ts.map +1 -0
- package/dist/lfs/contextAdapter.js +161 -0
- package/dist/lfs/memoryEngine.d.ts +108 -0
- package/dist/lfs/memoryEngine.d.ts.map +1 -0
- package/dist/lfs/memoryEngine.js +322 -0
- package/dist/lfs/memoryParser.d.ts +121 -0
- package/dist/lfs/memoryParser.d.ts.map +1 -0
- package/dist/lfs/memoryParser.js +743 -0
- package/dist/lfs/paritySwapper.d.ts +20 -0
- package/dist/lfs/paritySwapper.d.ts.map +1 -0
- package/dist/lfs/paritySwapper.js +317 -0
- package/dist/lfs/preflightRouter.d.ts +14 -0
- package/dist/lfs/preflightRouter.d.ts.map +1 -0
- package/dist/lfs/preflightRouter.js +24 -0
- package/dist/lfs/runtimeConfig.d.ts +31 -0
- package/dist/lfs/runtimeConfig.d.ts.map +1 -0
- package/dist/lfs/runtimeConfig.js +40 -0
- package/dist/lfs/sessionStore.d.ts +14 -0
- package/dist/lfs/sessionStore.d.ts.map +1 -0
- package/dist/lfs/sessionStore.js +52 -0
- package/dist/lfs/tokenCounter.d.ts +90 -0
- package/dist/lfs/tokenCounter.d.ts.map +1 -0
- package/dist/lfs/tokenCounter.js +26 -0
- package/dist/lfs/types.d.ts +44 -0
- package/dist/lfs/types.d.ts.map +1 -0
- package/dist/lfs/types.js +1 -0
- package/dist/lfs/vectorDb.d.ts +19 -0
- package/dist/lfs/vectorDb.d.ts.map +1 -0
- package/dist/lfs/vectorDb.js +158 -0
- package/dist/mcp/client.d.ts +23 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +60 -0
- package/dist/mcp/config.d.ts +10 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +72 -0
- package/dist/providers/ollama.d.ts +2 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +7 -0
- package/dist/providers/registry.d.ts +5 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +9 -0
- package/dist/tools/registry.d.ts +14 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +26 -0
- package/dist/tools/remind.d.ts +15 -0
- package/dist/tools/remind.d.ts.map +1 -0
- package/dist/tools/remind.js +53 -0
- package/dist/tools/skillLoader.d.ts +12 -0
- package/dist/tools/skillLoader.d.ts.map +1 -0
- package/dist/tools/skillLoader.js +38 -0
- package/package.json +66 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const LFS_CONFIG = {
|
|
2
|
+
BYPASS_THRESHOLD: 0.25,
|
|
3
|
+
PREFLIGHT_PASS_THRU_THRESHOLD: 0.50,
|
|
4
|
+
DECAY_BASELINE_FACTOR: 1.2,
|
|
5
|
+
ALPHA_MASS_PENALTY: 0.35,
|
|
6
|
+
/** When UI memory cap is 100%, effective eviction cap uses this fraction of contextMax. */
|
|
7
|
+
PHYSICAL_FIREBREAK: 0.95,
|
|
8
|
+
NATIVE_CONTEXT_FRACTION: 0.82,
|
|
9
|
+
LFS_API_SEND_FRACTION: 0.80,
|
|
10
|
+
OUTPUT_RESERVE_FRACTION: 0.05,
|
|
11
|
+
OUTPUT_RESERVE_FLOOR: 2048,
|
|
12
|
+
OUTPUT_RESERVE_CEILING: 16384,
|
|
13
|
+
};
|
|
14
|
+
export const LFS_MEMORY_CAP = {
|
|
15
|
+
DEFAULT: 0.70,
|
|
16
|
+
MIN: 0.50,
|
|
17
|
+
MAX: 0.99,
|
|
18
|
+
};
|
|
19
|
+
export function clampMemoryCapFraction(fraction) {
|
|
20
|
+
return Math.min(LFS_MEMORY_CAP.MAX, Math.max(LFS_MEMORY_CAP.MIN, fraction));
|
|
21
|
+
}
|
|
22
|
+
/** UI 100% maps to PHYSICAL_FIREBREAK so provider wrappers never hit hard truncation. */
|
|
23
|
+
export function effectiveMemoryCapFraction(uiFraction) {
|
|
24
|
+
const clamped = clampMemoryCapFraction(uiFraction);
|
|
25
|
+
// Preserve the UI semantic that "100%" maps to a physical firebreak.
|
|
26
|
+
if (uiFraction >= 1.0 || clamped >= 1.0) {
|
|
27
|
+
return LFS_CONFIG.PHYSICAL_FIREBREAK;
|
|
28
|
+
}
|
|
29
|
+
return clamped;
|
|
30
|
+
}
|
|
31
|
+
export function computeOutputReserveTokens(contextMax) {
|
|
32
|
+
const proportional = Math.floor(contextMax * LFS_CONFIG.OUTPUT_RESERVE_FRACTION);
|
|
33
|
+
return Math.min(Math.max(LFS_CONFIG.OUTPUT_RESERVE_FLOOR, proportional), LFS_CONFIG.OUTPUT_RESERVE_CEILING);
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contextAdapter.d.ts","sourceRoot":"","sources":["../../src/lfs/contextAdapter.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAqDnE,qBAAa,iBAAiB;IACtB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA8HvE"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { LFS_CONFIG } from "./config";
|
|
2
|
+
import { chunkMessageTurn, calculateDynamicChunkSize } from "./chunkManager";
|
|
3
|
+
import { calculateCreditBudget, initializeForgetScoresMidConversation, } from "./memoryEngine";
|
|
4
|
+
import { determineExecutionLane } from "./preflightRouter";
|
|
5
|
+
import { executeParitySwap } from "./paritySwapper";
|
|
6
|
+
import { resolveLfsRuntimeConfig } from "./runtimeConfig";
|
|
7
|
+
import { getOrCreateSessionState, updateSessionHistory } from "./sessionStore";
|
|
8
|
+
import { estimateMessagesTokens, estimateTextTokens } from "./tokenCounter";
|
|
9
|
+
import { upsertChunkVector } from "./vectorDb";
|
|
10
|
+
import { formatHistoryForModel } from "../history/messageManifest";
|
|
11
|
+
import { buildLfsRemindClause } from "../tools/remind";
|
|
12
|
+
function buildTurnSystemPrompt(params) {
|
|
13
|
+
if (params.lane === "standard" || !params.isMmuActive) {
|
|
14
|
+
return `You are a technical software engineering assistant in the ${params.lane.toUpperCase()} lane.`;
|
|
15
|
+
}
|
|
16
|
+
return "You are a cognitive memory manager in the OPTIMIZED lane.";
|
|
17
|
+
}
|
|
18
|
+
function latestUserQuery(messages) {
|
|
19
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
20
|
+
if (messages[i].role === "user" && messages[i].content?.trim()) {
|
|
21
|
+
return messages[i].content;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return messages[messages.length - 1]?.content ?? "";
|
|
25
|
+
}
|
|
26
|
+
async function ensureChunksForNewMessages(params) {
|
|
27
|
+
const maxChunk = calculateDynamicChunkSize(params.contextMax);
|
|
28
|
+
const updated = [];
|
|
29
|
+
for (const msg of params.history) {
|
|
30
|
+
if (msg.role === "user" &&
|
|
31
|
+
!msg.chunks?.length &&
|
|
32
|
+
msg.content.trim() &&
|
|
33
|
+
estimateTextTokens(msg.content) > maxChunk / 2) {
|
|
34
|
+
const chunks = chunkMessageTurn(msg.id, msg.content, maxChunk, params.turnIndex);
|
|
35
|
+
for (const chunk of chunks) {
|
|
36
|
+
await upsertChunkVector(chunk.chunkId, msg.id, chunk.content);
|
|
37
|
+
}
|
|
38
|
+
updated.push({ ...msg, chunks });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
if (msg.chunks) {
|
|
42
|
+
for (const chunk of msg.chunks) {
|
|
43
|
+
if (!chunk.isArchived) {
|
|
44
|
+
await upsertChunkVector(chunk.chunkId, msg.id, chunk.content);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
updated.push(msg);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return updated;
|
|
52
|
+
}
|
|
53
|
+
export class LfsContextAdapter {
|
|
54
|
+
async prepareStep(input) {
|
|
55
|
+
const runtime = resolveLfsRuntimeConfig({
|
|
56
|
+
contextMax: input.contextMax,
|
|
57
|
+
memoryCapFraction: input.memoryCapFraction,
|
|
58
|
+
});
|
|
59
|
+
const session = getOrCreateSessionState({
|
|
60
|
+
sessionId: input.sessionId,
|
|
61
|
+
contextMax: runtime.contextMax,
|
|
62
|
+
memoryCapFraction: runtime.memoryCapFraction,
|
|
63
|
+
});
|
|
64
|
+
if (input.agentWeights) {
|
|
65
|
+
session.agentWeights = { ...session.agentWeights, ...input.agentWeights };
|
|
66
|
+
}
|
|
67
|
+
if (input.turnIndex != null) {
|
|
68
|
+
session.turnIndex = input.turnIndex;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
session.turnIndex += 1;
|
|
72
|
+
}
|
|
73
|
+
const mergedHistory = input.replaceSessionHistory
|
|
74
|
+
? [...input.incomingMessages]
|
|
75
|
+
: [...session.fullHistory, ...input.incomingMessages];
|
|
76
|
+
updateSessionHistory(input.sessionId, mergedHistory);
|
|
77
|
+
session.memoryCapFraction = runtime.effectiveMemoryCapFraction;
|
|
78
|
+
if (input.lane === "standard") {
|
|
79
|
+
const systemPrompt = buildTurnSystemPrompt({ lane: "standard", isMmuActive: false });
|
|
80
|
+
return {
|
|
81
|
+
lane: input.lane,
|
|
82
|
+
executionLane: "PASS_THRU",
|
|
83
|
+
isMmuActive: false,
|
|
84
|
+
messagesForModel: formatHistoryForModel(mergedHistory, systemPrompt, {
|
|
85
|
+
lane: "standard",
|
|
86
|
+
isMmuActive: false,
|
|
87
|
+
}),
|
|
88
|
+
systemBlocks: [systemPrompt],
|
|
89
|
+
session,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const query = input.query ?? latestUserQuery(mergedHistory);
|
|
93
|
+
const lastUserMsg = [...mergedHistory].reverse().find((m) => m.role === "user");
|
|
94
|
+
const activeHistoryTokens = mergedHistory
|
|
95
|
+
.filter((m) => !m.isArchived && m.role !== "system" && m.id !== lastUserMsg?.id)
|
|
96
|
+
.reduce((sum, m) => sum + (m.tokenCount || estimateTextTokens(m.content) || 0), 0);
|
|
97
|
+
const executionLane = determineExecutionLane({
|
|
98
|
+
activeHistoryTokens,
|
|
99
|
+
incomingTextLength: lastUserMsg?.content ?? "",
|
|
100
|
+
contextMax: runtime.contextMax,
|
|
101
|
+
});
|
|
102
|
+
let workingHistory = await ensureChunksForNewMessages({
|
|
103
|
+
history: mergedHistory,
|
|
104
|
+
contextMax: runtime.contextMax,
|
|
105
|
+
turnIndex: session.turnIndex,
|
|
106
|
+
});
|
|
107
|
+
const coeffs = runtime.coeffs;
|
|
108
|
+
let activeHistory = executionLane === "ELASTIC_SWAP"
|
|
109
|
+
? initializeForgetScoresMidConversation(workingHistory, coeffs)
|
|
110
|
+
: workingHistory;
|
|
111
|
+
const activeTokens = estimateMessagesTokens(activeHistory.filter((m) => !m.isArchived && m.role !== "system"));
|
|
112
|
+
const isMmuActive = executionLane === "ELASTIC_SWAP" &&
|
|
113
|
+
activeTokens / runtime.contextMax >= LFS_CONFIG.BYPASS_THRESHOLD;
|
|
114
|
+
if (isMmuActive) {
|
|
115
|
+
activeHistory = await executeParitySwap({
|
|
116
|
+
history: activeHistory,
|
|
117
|
+
longTermMemory: session.longTermMemory,
|
|
118
|
+
query,
|
|
119
|
+
agentWeights: session.agentWeights,
|
|
120
|
+
contextMax: runtime.contextMax,
|
|
121
|
+
hardBufferCap: coeffs.hardBufferCap,
|
|
122
|
+
currentTurnIndex: session.turnIndex,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const tokensBefore = estimateMessagesTokens(activeHistory);
|
|
126
|
+
const budget = isMmuActive
|
|
127
|
+
? calculateCreditBudget(tokensBefore, runtime.contextMax, coeffs.cMax)
|
|
128
|
+
: 0;
|
|
129
|
+
const systemPrompt = buildTurnSystemPrompt({ lane: "optimized", isMmuActive });
|
|
130
|
+
const remindFromSession = buildLfsRemindClause(session);
|
|
131
|
+
const remindClause = input.benchmarkSuite
|
|
132
|
+
? "No tools in benchmark mode."
|
|
133
|
+
: input.useRemindTool === false
|
|
134
|
+
? "No tools this turn — all visible messages are active."
|
|
135
|
+
: [
|
|
136
|
+
remindFromSession,
|
|
137
|
+
"REMIND TOOL: Call remind(target) with chunkId or messageId for archived disk summaries when exact raw text is required.",
|
|
138
|
+
input.hydrationBeacon ?? "",
|
|
139
|
+
]
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.join("\n");
|
|
142
|
+
const messagesForModel = formatHistoryForModel(activeHistory, systemPrompt, {
|
|
143
|
+
lane: "optimized",
|
|
144
|
+
budget: input.benchmarkSuite ? 0 : budget,
|
|
145
|
+
remindClause,
|
|
146
|
+
isMmuActive,
|
|
147
|
+
});
|
|
148
|
+
updateSessionHistory(input.sessionId, activeHistory);
|
|
149
|
+
session.longTermMemory = { ...session.longTermMemory };
|
|
150
|
+
return {
|
|
151
|
+
lane: input.lane,
|
|
152
|
+
executionLane,
|
|
153
|
+
isMmuActive,
|
|
154
|
+
messagesForModel,
|
|
155
|
+
systemBlocks: [systemPrompt],
|
|
156
|
+
session,
|
|
157
|
+
budget,
|
|
158
|
+
remindClause,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { AdaptiveMessage, AllocationMap, TurnMemorySnapshot } from './tokenCounter';
|
|
2
|
+
import type { ContextShift } from './memoryParser';
|
|
3
|
+
export type MemoryCoefficients = {
|
|
4
|
+
contextMax: number;
|
|
5
|
+
decayBaseline: number;
|
|
6
|
+
alpha: number;
|
|
7
|
+
cMax: number;
|
|
8
|
+
hardBufferCap: number;
|
|
9
|
+
};
|
|
10
|
+
/** User/assistant turns only — excludes agentic traces and archived/system rows. */
|
|
11
|
+
export declare function isConversationalMessage(m: AdaptiveMessage): boolean;
|
|
12
|
+
/** Latest conversational row — the message being sent now (must not decay this turn). */
|
|
13
|
+
export declare function findActiveTurnAnchorId(history: AdaptiveMessage[]): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Conversational message immediately BEFORE the active anchor.
|
|
16
|
+
* - user send → prior assistant/user turn (not the new user message)
|
|
17
|
+
* - assistant output → prior turn before the user prompt being answered (not that user prompt)
|
|
18
|
+
*/
|
|
19
|
+
export declare function findPredecessorMessageId(history: AdaptiveMessage[]): string | null;
|
|
20
|
+
/** Decay all scorable messages (except the active turn anchor) when the user sends a new message. */
|
|
21
|
+
export declare function applyUserSendDecay(history: AdaptiveMessage[], coeffs: MemoryCoefficients): AdaptiveMessage[];
|
|
22
|
+
/**
|
|
23
|
+
* Initializes forgetScore values mid-conversation when the MMU scoring engine wakes up.
|
|
24
|
+
* Based on their mass-driven baseline age.
|
|
25
|
+
*/
|
|
26
|
+
export declare function initializeForgetScoresMidConversation(history: AdaptiveMessage[], coeffs: MemoryCoefficients): AdaptiveMessage[];
|
|
27
|
+
/**
|
|
28
|
+
* Hydration Displacement Decay — Fluid Context Rebalancing.
|
|
29
|
+
*
|
|
30
|
+
* When archived messages are hydrated back into active context, this function
|
|
31
|
+
* runs a unified decay pass on all existing active messages to prevent the
|
|
32
|
+
* context window from spiking past the 40% target pressure ceiling.
|
|
33
|
+
*
|
|
34
|
+
* Unified Formula:
|
|
35
|
+
* Decay_final = [baseline + α*(T_msg/T_max)] * e^(5*(x-0.40)) * (1 + T_hydrated/(T_max*0.40))
|
|
36
|
+
*
|
|
37
|
+
* Three lifecycle behaviors emerge naturally:
|
|
38
|
+
* Stage 1 (x≈0.15): e^(-1.25)≈0.28 → massive dampener, decay is tiny, plenty of room
|
|
39
|
+
* Stage 2 (x≈0.35): e^(-0.25)≈0.77 → smooth tightening, stale messages accelerate toward edge
|
|
40
|
+
* Stage 3 (x≈0.45): e^(+0.25)≈1.28 → surgical displacement, non-relevant blocks jump past 1.0
|
|
41
|
+
*
|
|
42
|
+
* Hydrated messages receive forgetScore = 0.0 (zero-score memory lock).
|
|
43
|
+
* Messages that reach forgetScore >= 1.0 should be immediately paged out
|
|
44
|
+
* (displacement eviction bypasses the normal turnsAtLimit dampening).
|
|
45
|
+
*/
|
|
46
|
+
export declare function applyHydrationDisplacementDecay(params: {
|
|
47
|
+
activeHistory: AdaptiveMessage[];
|
|
48
|
+
hydratedTokens: number;
|
|
49
|
+
hydratedIds: Set<string>;
|
|
50
|
+
coeffs: MemoryCoefficients;
|
|
51
|
+
}): AdaptiveMessage[];
|
|
52
|
+
export declare function calculateCreditBudget(currentTokens: number, maxTokens: number, cMax: number): number;
|
|
53
|
+
export declare function validateAndApplyCredits(scaledAllocation: AllocationMap, messages: AdaptiveMessage[]): AdaptiveMessage[];
|
|
54
|
+
/** One agentic-loop memory evaluation (credits + snapshot). Decay is handled at user send. */
|
|
55
|
+
export declare function applyAgenticStepMemory(params: {
|
|
56
|
+
history: AdaptiveMessage[];
|
|
57
|
+
coeffs: MemoryCoefficients;
|
|
58
|
+
contextShift: ContextShift;
|
|
59
|
+
agentWeights: AllocationMap;
|
|
60
|
+
referencedIds: string[];
|
|
61
|
+
protectedIds: Set<string>;
|
|
62
|
+
stepIndex: number;
|
|
63
|
+
/** Who is "sending" at this memory boundary — drives predecessor decay. */
|
|
64
|
+
outgoingRole?: 'user' | 'assistant';
|
|
65
|
+
}): {
|
|
66
|
+
history: AdaptiveMessage[];
|
|
67
|
+
budget: number;
|
|
68
|
+
creditsConsumed: number;
|
|
69
|
+
scaledAllocation: AllocationMap;
|
|
70
|
+
snapshot: TurnMemorySnapshot;
|
|
71
|
+
tokens: number;
|
|
72
|
+
};
|
|
73
|
+
export declare function getPressureRatio(tokens: number, contextMax: number): number;
|
|
74
|
+
/**
|
|
75
|
+
* UI-only suggestion for memory cap slider (does not override user profile at execution).
|
|
76
|
+
*
|
|
77
|
+
* Formula:
|
|
78
|
+
* contextMax ≤ 16,384 → 0.40
|
|
79
|
+
* otherwise → min(0.40 + log10(contextMax / 10_000) × 0.15, 0.75)
|
|
80
|
+
*
|
|
81
|
+
* Effective ranges:
|
|
82
|
+
* 10k → 0.40
|
|
83
|
+
* 1M → 0.70
|
|
84
|
+
* 10M+ → 0.75 (ceiling)
|
|
85
|
+
*/
|
|
86
|
+
export declare function suggestMemoryCapFraction(contextMax: number): number;
|
|
87
|
+
/** @deprecated Use suggestMemoryCapFraction for UI hints; use resolveLfsRuntimeConfig for execution. */
|
|
88
|
+
export declare function calculateCapacityFactor(contextMax: number): number;
|
|
89
|
+
/**
|
|
90
|
+
* Returns the effective token cap for the given context window and cap fraction.
|
|
91
|
+
*/
|
|
92
|
+
export declare function calculateTargetMemoryCap(contextMax: number, memoryCapFraction?: number): number;
|
|
93
|
+
/**
|
|
94
|
+
* Normalised pressure against the memory cap (0.0 → ∞).
|
|
95
|
+
*/
|
|
96
|
+
export declare function getNormalizedPressure(activeTokens: number, contextMax: number, memoryCapFraction?: number): number;
|
|
97
|
+
/**
|
|
98
|
+
* Credit budget with logarithmic floor — guarantees ≥ 10 credits even under
|
|
99
|
+
* severe context pressure, preventing the agent from going completely silent.
|
|
100
|
+
*
|
|
101
|
+
* Formula:
|
|
102
|
+
* pressure < 1.0 → 100
|
|
103
|
+
* otherwise → max(⌊100 / (1 + ln(pressure))⌋, 10)
|
|
104
|
+
*
|
|
105
|
+
* Uses the auto-derived cap when called without a hardBufferCap override.
|
|
106
|
+
*/
|
|
107
|
+
export declare function calculateCreditBudgetV2(activeTokens: number, contextMax: number, hardBufferCapOverride?: number): number;
|
|
108
|
+
//# sourceMappingURL=memoryEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryEngine.d.ts","sourceRoot":"","sources":["../../src/lfs/memoryEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { estimateMessagesTokens, estimateTextTokens } from './tokenCounter';
|
|
2
|
+
import { resolveTurnCredits, buildTurnMemorySnapshot, collectScorableMessageIds, } from './memoryParser';
|
|
3
|
+
/** User/assistant turns only — excludes agentic traces and archived/system rows. */
|
|
4
|
+
export function isConversationalMessage(m) {
|
|
5
|
+
if (m.isArchived || m.role === 'system' || m.role === 'tool')
|
|
6
|
+
return false;
|
|
7
|
+
if (m.stepKind === 'thought' || m.stepKind === 'memory')
|
|
8
|
+
return false;
|
|
9
|
+
return m.role === 'user' || m.role === 'assistant';
|
|
10
|
+
}
|
|
11
|
+
/** Latest conversational row — the message being sent now (must not decay this turn). */
|
|
12
|
+
export function findActiveTurnAnchorId(history) {
|
|
13
|
+
const conv = history.filter(isConversationalMessage);
|
|
14
|
+
return conv[conv.length - 1]?.id ?? null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Conversational message immediately BEFORE the active anchor.
|
|
18
|
+
* - user send → prior assistant/user turn (not the new user message)
|
|
19
|
+
* - assistant output → prior turn before the user prompt being answered (not that user prompt)
|
|
20
|
+
*/
|
|
21
|
+
export function findPredecessorMessageId(history) {
|
|
22
|
+
const conv = history.filter(isConversationalMessage);
|
|
23
|
+
if (conv.length < 2)
|
|
24
|
+
return null;
|
|
25
|
+
return conv[conv.length - 2]?.id ?? null;
|
|
26
|
+
}
|
|
27
|
+
function applyDecayDelta(m, coeffs, decayBaselineActive) {
|
|
28
|
+
const tokenCount = m.tokenCount || estimateTextTokens(m.content) || 1;
|
|
29
|
+
const decayApplied = decayBaselineActive + coeffs.alpha * (tokenCount / coeffs.contextMax);
|
|
30
|
+
const nextScore = Math.min(1.0, (m.forgetScore ?? 0.0) + decayApplied);
|
|
31
|
+
return {
|
|
32
|
+
forgetScore: Number(nextScore.toFixed(4)),
|
|
33
|
+
decayApplied: Number(decayApplied.toFixed(4)),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Decay all scorable messages (except the active turn anchor) when the user sends a new message. */
|
|
37
|
+
export function applyUserSendDecay(history, coeffs) {
|
|
38
|
+
const activeAnchorId = findActiveTurnAnchorId(history);
|
|
39
|
+
// Calculate active tokens size in current context (excluding archived messages and system instructions)
|
|
40
|
+
const activeTokens = history
|
|
41
|
+
.filter((m) => !m.isArchived && m.role !== 'system')
|
|
42
|
+
.reduce((sum, m) => sum + (m.tokenCount || estimateTextTokens(m.content) || 0), 0);
|
|
43
|
+
const pressure = activeTokens / coeffs.contextMax;
|
|
44
|
+
// 70% Hard Headroom Buffer Cap Trigger
|
|
45
|
+
const effectivePressure = pressure > coeffs.hardBufferCap
|
|
46
|
+
? pressure * 1.4 // Dynamically steepen the curve to force intense evictions
|
|
47
|
+
: pressure;
|
|
48
|
+
const targetPressure = 0.40;
|
|
49
|
+
const sensitivity = 5.0; // Control stiffness of regulation band
|
|
50
|
+
// Exponential baseline decay scaling: multiplier is 1.0 at 40% pressure
|
|
51
|
+
const pressureMultiplier = Math.exp(sensitivity * (effectivePressure - targetPressure));
|
|
52
|
+
// Bound the decay baseline to prevent extreme behavior (min 0.005, max 0.80)
|
|
53
|
+
const decayBaselineActive = Math.min(0.80, Math.max(0.005, coeffs.decayBaseline * pressureMultiplier));
|
|
54
|
+
return history.map((m) => {
|
|
55
|
+
if (!isConversationalMessage(m) || !m.id || m.id === activeAnchorId)
|
|
56
|
+
return m;
|
|
57
|
+
// HYDRATION SHIELD GRACE PERIOD
|
|
58
|
+
if (m.hydrationGraceTurns && m.hydrationGraceTurns > 0) {
|
|
59
|
+
return {
|
|
60
|
+
...m,
|
|
61
|
+
forgetScore: 0.0, // Retain perfect memory immunity
|
|
62
|
+
turnsAtLimit: 0,
|
|
63
|
+
hydrationGraceTurns: m.hydrationGraceTurns - 1,
|
|
64
|
+
decayApplied: 0,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const { forgetScore, decayApplied } = applyDecayDelta(m, coeffs, decayBaselineActive);
|
|
68
|
+
const turnsAtLimit = forgetScore >= 1.0 ? (m.turnsAtLimit ?? 0) + 1 : 0;
|
|
69
|
+
// Explicit LFS v4 Displacement tagging
|
|
70
|
+
if (forgetScore >= 1.0 && !m.isArchived) {
|
|
71
|
+
return {
|
|
72
|
+
...m,
|
|
73
|
+
forgetScore: 1.0,
|
|
74
|
+
turnsAtLimit,
|
|
75
|
+
decayApplied,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return { ...m, forgetScore, decayApplied, turnsAtLimit };
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Initializes forgetScore values mid-conversation when the MMU scoring engine wakes up.
|
|
83
|
+
* Based on their mass-driven baseline age.
|
|
84
|
+
*/
|
|
85
|
+
export function initializeForgetScoresMidConversation(history, coeffs) {
|
|
86
|
+
const conv = history.filter(isConversationalMessage);
|
|
87
|
+
const activeAnchorId = findActiveTurnAnchorId(history);
|
|
88
|
+
return history.map((m) => {
|
|
89
|
+
if (!isConversationalMessage(m) || !m.id || m.id === activeAnchorId || m.forgetScore !== undefined) {
|
|
90
|
+
return m;
|
|
91
|
+
}
|
|
92
|
+
const idx = conv.findIndex((x) => x.id === m.id);
|
|
93
|
+
if (idx === -1)
|
|
94
|
+
return m;
|
|
95
|
+
const turnsExisted = conv.length - 1 - idx;
|
|
96
|
+
if (turnsExisted <= 0)
|
|
97
|
+
return m;
|
|
98
|
+
const tokenCount = m.tokenCount || estimateTextTokens(m.content) || 1;
|
|
99
|
+
const baseDecay = coeffs.decayBaseline + coeffs.alpha * (tokenCount / coeffs.contextMax);
|
|
100
|
+
const initialScore = Math.min(0.95, turnsExisted * baseDecay);
|
|
101
|
+
return {
|
|
102
|
+
...m,
|
|
103
|
+
forgetScore: Number(initialScore.toFixed(4)),
|
|
104
|
+
turnsAtLimit: 0,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Hydration Displacement Decay — Fluid Context Rebalancing.
|
|
110
|
+
*
|
|
111
|
+
* When archived messages are hydrated back into active context, this function
|
|
112
|
+
* runs a unified decay pass on all existing active messages to prevent the
|
|
113
|
+
* context window from spiking past the 40% target pressure ceiling.
|
|
114
|
+
*
|
|
115
|
+
* Unified Formula:
|
|
116
|
+
* Decay_final = [baseline + α*(T_msg/T_max)] * e^(5*(x-0.40)) * (1 + T_hydrated/(T_max*0.40))
|
|
117
|
+
*
|
|
118
|
+
* Three lifecycle behaviors emerge naturally:
|
|
119
|
+
* Stage 1 (x≈0.15): e^(-1.25)≈0.28 → massive dampener, decay is tiny, plenty of room
|
|
120
|
+
* Stage 2 (x≈0.35): e^(-0.25)≈0.77 → smooth tightening, stale messages accelerate toward edge
|
|
121
|
+
* Stage 3 (x≈0.45): e^(+0.25)≈1.28 → surgical displacement, non-relevant blocks jump past 1.0
|
|
122
|
+
*
|
|
123
|
+
* Hydrated messages receive forgetScore = 0.0 (zero-score memory lock).
|
|
124
|
+
* Messages that reach forgetScore >= 1.0 should be immediately paged out
|
|
125
|
+
* (displacement eviction bypasses the normal turnsAtLimit dampening).
|
|
126
|
+
*/
|
|
127
|
+
export function applyHydrationDisplacementDecay(params) {
|
|
128
|
+
const { activeHistory, hydratedTokens, hydratedIds, coeffs } = params;
|
|
129
|
+
const activeAnchorId = findActiveTurnAnchorId(activeHistory);
|
|
130
|
+
// Current system pressure x = activeTokens / contextMax
|
|
131
|
+
const activeTokens = activeHistory
|
|
132
|
+
.filter((m) => !m.isArchived && m.role !== 'system')
|
|
133
|
+
.reduce((sum, m) => sum + (m.tokenCount || estimateTextTokens(m.content) || 0), 0);
|
|
134
|
+
const pressure = activeTokens / coeffs.contextMax;
|
|
135
|
+
// 70% Hard Headroom Buffer Cap Trigger
|
|
136
|
+
const effectivePressure = pressure > coeffs.hardBufferCap
|
|
137
|
+
? pressure * 1.4 // Dynamically steepen the curve to force intense evictions
|
|
138
|
+
: pressure;
|
|
139
|
+
const targetCeiling = coeffs.contextMax * 0.40;
|
|
140
|
+
// Exponential pressure modifier: e^(5.0 * (x - 0.40))
|
|
141
|
+
const pressureMultiplier = Math.exp(5.0 * (effectivePressure - 0.40));
|
|
142
|
+
// Hydration volume mass booster: 1 + (T_hydrated / (T_max * 0.40))
|
|
143
|
+
const hydrationMassBooster = 1 + (hydratedTokens / targetCeiling);
|
|
144
|
+
return activeHistory.map((m) => {
|
|
145
|
+
// Skip non-conversational, archived, system messages
|
|
146
|
+
if (!isConversationalMessage(m) || !m.id)
|
|
147
|
+
return m;
|
|
148
|
+
// Active turn anchor: never displace
|
|
149
|
+
if (m.id === activeAnchorId)
|
|
150
|
+
return m;
|
|
151
|
+
// Hydrated messages: zero-score memory lock (protected)
|
|
152
|
+
if (hydratedIds.has(m.id)) {
|
|
153
|
+
return { ...m, forgetScore: 0.0, turnsAtLimit: 0, decayApplied: 0 };
|
|
154
|
+
}
|
|
155
|
+
// Grace shield protection without decrementing (happens in user send decay)
|
|
156
|
+
if (m.hydrationGraceTurns && m.hydrationGraceTurns > 0) {
|
|
157
|
+
return { ...m, forgetScore: 0.0, turnsAtLimit: 0, decayApplied: 0 };
|
|
158
|
+
}
|
|
159
|
+
// Unified displacement decay formula
|
|
160
|
+
const tokenCount = m.tokenCount || estimateTextTokens(m.content) || 1;
|
|
161
|
+
const baseDecay = coeffs.decayBaseline + (coeffs.alpha * (tokenCount / coeffs.contextMax));
|
|
162
|
+
const totalTurnDecay = baseDecay * pressureMultiplier * hydrationMassBooster;
|
|
163
|
+
const newForgetScore = Math.min(1.0, (m.forgetScore ?? 0) + totalTurnDecay);
|
|
164
|
+
return {
|
|
165
|
+
...m,
|
|
166
|
+
forgetScore: Number(newForgetScore.toFixed(4)),
|
|
167
|
+
decayApplied: Number(totalTurnDecay.toFixed(4)),
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
export function calculateCreditBudget(currentTokens, maxTokens, cMax) {
|
|
172
|
+
const x = currentTokens / maxTokens;
|
|
173
|
+
return Math.round(cMax * Math.exp(-2.5 * x));
|
|
174
|
+
}
|
|
175
|
+
export function validateAndApplyCredits(scaledAllocation, messages) {
|
|
176
|
+
return messages.map((m) => {
|
|
177
|
+
if (m.isArchived || m.role === 'system')
|
|
178
|
+
return m;
|
|
179
|
+
const finalCredits = scaledAllocation[m.id] || 0;
|
|
180
|
+
if (finalCredits <= 0)
|
|
181
|
+
return m;
|
|
182
|
+
const tokenCount = m.tokenCount || estimateTextTokens(m.content) || 1;
|
|
183
|
+
const reduction = finalCredits / Math.sqrt(tokenCount);
|
|
184
|
+
const nextScore = Math.max(0.0, (m.forgetScore ?? 0.0) - reduction);
|
|
185
|
+
return {
|
|
186
|
+
...m,
|
|
187
|
+
forgetScore: Number(nextScore.toFixed(4)),
|
|
188
|
+
turnsAtLimit: 0,
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/** One agentic-loop memory evaluation (credits + snapshot). Decay is handled at user send. */
|
|
193
|
+
export function applyAgenticStepMemory(params) {
|
|
194
|
+
const { history, coeffs, contextShift, agentWeights, referencedIds, protectedIds, outgoingRole } = params;
|
|
195
|
+
const tokens = estimateMessagesTokens(history);
|
|
196
|
+
const budget = calculateCreditBudget(tokens, coeffs.contextMax, coeffs.cMax);
|
|
197
|
+
const activeTurnId = outgoingRole ? findActiveTurnAnchorId(history) : null;
|
|
198
|
+
const activeIds = new Set(collectScorableMessageIds(history));
|
|
199
|
+
const updatedScores = history.map((m) => {
|
|
200
|
+
if (!isConversationalMessage(m))
|
|
201
|
+
return m;
|
|
202
|
+
if (protectedIds.has(m.id)) {
|
|
203
|
+
return { ...m, forgetScore: 0.0, decayApplied: 0 };
|
|
204
|
+
}
|
|
205
|
+
if (activeTurnId && m.id === activeTurnId) {
|
|
206
|
+
return { ...m, decayApplied: 0 };
|
|
207
|
+
}
|
|
208
|
+
if (referencedIds.includes(m.id)) {
|
|
209
|
+
return {
|
|
210
|
+
...m,
|
|
211
|
+
decayApplied: -(m.forgetScore ?? 0.0),
|
|
212
|
+
forgetScore: 0.0,
|
|
213
|
+
turnsAtLimit: 0,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// Otherwise, keep the message as is (keeps its decayApplied computed at the start of the turn)
|
|
217
|
+
return m;
|
|
218
|
+
});
|
|
219
|
+
const filteredAgentWeights = { ...agentWeights };
|
|
220
|
+
if (activeTurnId && filteredAgentWeights[activeTurnId]) {
|
|
221
|
+
delete filteredAgentWeights[activeTurnId];
|
|
222
|
+
}
|
|
223
|
+
const creditResolution = resolveTurnCredits({
|
|
224
|
+
contextShift,
|
|
225
|
+
agentWeights: filteredAgentWeights,
|
|
226
|
+
referencedIds,
|
|
227
|
+
budget,
|
|
228
|
+
activeIds,
|
|
229
|
+
activeTurnId,
|
|
230
|
+
});
|
|
231
|
+
const scaledAllocation = { ...creditResolution.scaledAllocation };
|
|
232
|
+
const creditsConsumed = Object.values(scaledAllocation).reduce((sum, n) => sum + n, 0);
|
|
233
|
+
const credited = validateAndApplyCredits(scaledAllocation, updatedScores).map((m) => ({
|
|
234
|
+
...m,
|
|
235
|
+
creditsAllocated: scaledAllocation[m.id]
|
|
236
|
+
? Math.round(scaledAllocation[m.id])
|
|
237
|
+
: 0,
|
|
238
|
+
}));
|
|
239
|
+
const snapshot = buildTurnMemorySnapshot({
|
|
240
|
+
turnBudget: budget,
|
|
241
|
+
contextShift,
|
|
242
|
+
creditsConsumed,
|
|
243
|
+
agentAllocation: creditResolution.agentAllocation,
|
|
244
|
+
effectiveAllocation: creditResolution.effectiveAllocation,
|
|
245
|
+
usedFallback: creditResolution.usedFallback,
|
|
246
|
+
scaledAllocation,
|
|
247
|
+
scale: creditResolution.scale,
|
|
248
|
+
totalRequested: creditResolution.totalRequested,
|
|
249
|
+
referencedIds,
|
|
250
|
+
messages: credited,
|
|
251
|
+
estimateTokens: estimateTextTokens,
|
|
252
|
+
invalidAgentIds: creditResolution.invalidAgentIds,
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
history: credited,
|
|
256
|
+
budget,
|
|
257
|
+
creditsConsumed,
|
|
258
|
+
scaledAllocation,
|
|
259
|
+
snapshot,
|
|
260
|
+
tokens,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export function getPressureRatio(tokens, contextMax) {
|
|
264
|
+
return tokens / contextMax;
|
|
265
|
+
}
|
|
266
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
267
|
+
// LFS v4.6 — Zero-Configuration Dynamic Watermarking Engine
|
|
268
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
269
|
+
/**
|
|
270
|
+
* UI-only suggestion for memory cap slider (does not override user profile at execution).
|
|
271
|
+
*
|
|
272
|
+
* Formula:
|
|
273
|
+
* contextMax ≤ 16,384 → 0.40
|
|
274
|
+
* otherwise → min(0.40 + log10(contextMax / 10_000) × 0.15, 0.75)
|
|
275
|
+
*
|
|
276
|
+
* Effective ranges:
|
|
277
|
+
* 10k → 0.40
|
|
278
|
+
* 1M → 0.70
|
|
279
|
+
* 10M+ → 0.75 (ceiling)
|
|
280
|
+
*/
|
|
281
|
+
export function suggestMemoryCapFraction(contextMax) {
|
|
282
|
+
if (contextMax <= 16384)
|
|
283
|
+
return 0.40;
|
|
284
|
+
return Math.min(0.40 + Math.log10(contextMax / 10_000) * 0.15, 0.75);
|
|
285
|
+
}
|
|
286
|
+
/** @deprecated Use suggestMemoryCapFraction for UI hints; use resolveLfsRuntimeConfig for execution. */
|
|
287
|
+
export function calculateCapacityFactor(contextMax) {
|
|
288
|
+
return suggestMemoryCapFraction(contextMax);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Returns the effective token cap for the given context window and cap fraction.
|
|
292
|
+
*/
|
|
293
|
+
export function calculateTargetMemoryCap(contextMax, memoryCapFraction) {
|
|
294
|
+
const fraction = memoryCapFraction ?? suggestMemoryCapFraction(contextMax);
|
|
295
|
+
return Math.floor(contextMax * fraction);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Normalised pressure against the memory cap (0.0 → ∞).
|
|
299
|
+
*/
|
|
300
|
+
export function getNormalizedPressure(activeTokens, contextMax, memoryCapFraction) {
|
|
301
|
+
const cap = calculateTargetMemoryCap(contextMax, memoryCapFraction);
|
|
302
|
+
return activeTokens / Math.max(1, cap);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Credit budget with logarithmic floor — guarantees ≥ 10 credits even under
|
|
306
|
+
* severe context pressure, preventing the agent from going completely silent.
|
|
307
|
+
*
|
|
308
|
+
* Formula:
|
|
309
|
+
* pressure < 1.0 → 100
|
|
310
|
+
* otherwise → max(⌊100 / (1 + ln(pressure))⌋, 10)
|
|
311
|
+
*
|
|
312
|
+
* Uses the auto-derived cap when called without a hardBufferCap override.
|
|
313
|
+
*/
|
|
314
|
+
export function calculateCreditBudgetV2(activeTokens, contextMax, hardBufferCapOverride) {
|
|
315
|
+
const cap = hardBufferCapOverride != null
|
|
316
|
+
? contextMax * hardBufferCapOverride
|
|
317
|
+
: calculateTargetMemoryCap(contextMax);
|
|
318
|
+
const pressure = activeTokens / cap;
|
|
319
|
+
if (pressure < 1.0)
|
|
320
|
+
return 100;
|
|
321
|
+
return Math.max(Math.floor(100 / (1 + Math.log(pressure))), 10);
|
|
322
|
+
}
|