@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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +331 -0
  3. package/dist/agent/createLfsAgent.d.ts +27 -0
  4. package/dist/agent/createLfsAgent.d.ts.map +1 -0
  5. package/dist/agent/createLfsAgent.js +51 -0
  6. package/dist/agent/loopConfig.d.ts +6 -0
  7. package/dist/agent/loopConfig.d.ts.map +1 -0
  8. package/dist/agent/loopConfig.js +5 -0
  9. package/dist/agent/runLfsTurn.d.ts +10 -0
  10. package/dist/agent/runLfsTurn.d.ts.map +1 -0
  11. package/dist/agent/runLfsTurn.js +20 -0
  12. package/dist/history/dorycodeAdapter.d.ts +16 -0
  13. package/dist/history/dorycodeAdapter.d.ts.map +1 -0
  14. package/dist/history/dorycodeAdapter.js +18 -0
  15. package/dist/history/formatForModel.d.ts +19 -0
  16. package/dist/history/formatForModel.d.ts.map +1 -0
  17. package/dist/history/formatForModel.js +22 -0
  18. package/dist/history/lanes.d.ts +3 -0
  19. package/dist/history/lanes.d.ts.map +1 -0
  20. package/dist/history/lanes.js +3 -0
  21. package/dist/history/messageManifest.d.ts +45 -0
  22. package/dist/history/messageManifest.d.ts.map +1 -0
  23. package/dist/history/messageManifest.js +231 -0
  24. package/dist/index.d.ts +33 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +22 -0
  27. package/dist/lfs/adaptiveCoefficients.d.ts +13 -0
  28. package/dist/lfs/adaptiveCoefficients.d.ts.map +1 -0
  29. package/dist/lfs/adaptiveCoefficients.js +16 -0
  30. package/dist/lfs/attenuationCompiler.d.ts +16 -0
  31. package/dist/lfs/attenuationCompiler.d.ts.map +1 -0
  32. package/dist/lfs/attenuationCompiler.js +123 -0
  33. package/dist/lfs/chunkManager.d.ts +6 -0
  34. package/dist/lfs/chunkManager.d.ts.map +1 -0
  35. package/dist/lfs/chunkManager.js +142 -0
  36. package/dist/lfs/config.d.ts +23 -0
  37. package/dist/lfs/config.d.ts.map +1 -0
  38. package/dist/lfs/config.js +34 -0
  39. package/dist/lfs/contextAdapter.d.ts +5 -0
  40. package/dist/lfs/contextAdapter.d.ts.map +1 -0
  41. package/dist/lfs/contextAdapter.js +161 -0
  42. package/dist/lfs/memoryEngine.d.ts +108 -0
  43. package/dist/lfs/memoryEngine.d.ts.map +1 -0
  44. package/dist/lfs/memoryEngine.js +322 -0
  45. package/dist/lfs/memoryParser.d.ts +121 -0
  46. package/dist/lfs/memoryParser.d.ts.map +1 -0
  47. package/dist/lfs/memoryParser.js +743 -0
  48. package/dist/lfs/paritySwapper.d.ts +20 -0
  49. package/dist/lfs/paritySwapper.d.ts.map +1 -0
  50. package/dist/lfs/paritySwapper.js +317 -0
  51. package/dist/lfs/preflightRouter.d.ts +14 -0
  52. package/dist/lfs/preflightRouter.d.ts.map +1 -0
  53. package/dist/lfs/preflightRouter.js +24 -0
  54. package/dist/lfs/runtimeConfig.d.ts +31 -0
  55. package/dist/lfs/runtimeConfig.d.ts.map +1 -0
  56. package/dist/lfs/runtimeConfig.js +40 -0
  57. package/dist/lfs/sessionStore.d.ts +14 -0
  58. package/dist/lfs/sessionStore.d.ts.map +1 -0
  59. package/dist/lfs/sessionStore.js +52 -0
  60. package/dist/lfs/tokenCounter.d.ts +90 -0
  61. package/dist/lfs/tokenCounter.d.ts.map +1 -0
  62. package/dist/lfs/tokenCounter.js +26 -0
  63. package/dist/lfs/types.d.ts +44 -0
  64. package/dist/lfs/types.d.ts.map +1 -0
  65. package/dist/lfs/types.js +1 -0
  66. package/dist/lfs/vectorDb.d.ts +19 -0
  67. package/dist/lfs/vectorDb.d.ts.map +1 -0
  68. package/dist/lfs/vectorDb.js +158 -0
  69. package/dist/mcp/client.d.ts +23 -0
  70. package/dist/mcp/client.d.ts.map +1 -0
  71. package/dist/mcp/client.js +60 -0
  72. package/dist/mcp/config.d.ts +10 -0
  73. package/dist/mcp/config.d.ts.map +1 -0
  74. package/dist/mcp/config.js +72 -0
  75. package/dist/providers/ollama.d.ts +2 -0
  76. package/dist/providers/ollama.d.ts.map +1 -0
  77. package/dist/providers/ollama.js +7 -0
  78. package/dist/providers/registry.d.ts +5 -0
  79. package/dist/providers/registry.d.ts.map +1 -0
  80. package/dist/providers/registry.js +9 -0
  81. package/dist/tools/registry.d.ts +14 -0
  82. package/dist/tools/registry.d.ts.map +1 -0
  83. package/dist/tools/registry.js +26 -0
  84. package/dist/tools/remind.d.ts +15 -0
  85. package/dist/tools/remind.d.ts.map +1 -0
  86. package/dist/tools/remind.js +53 -0
  87. package/dist/tools/skillLoader.d.ts +12 -0
  88. package/dist/tools/skillLoader.d.ts.map +1 -0
  89. package/dist/tools/skillLoader.js +38 -0
  90. 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,5 @@
1
+ import type { PrepareStepInput, PrepareStepOutput } from "./types";
2
+ export declare class LfsContextAdapter {
3
+ prepareStep(input: PrepareStepInput): Promise<PrepareStepOutput>;
4
+ }
5
+ //# sourceMappingURL=contextAdapter.d.ts.map
@@ -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
+ }