@librechat/agents 3.7.15 → 3.7.16

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 (120) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +91 -0
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/agents/projection.cjs +2 -1
  4. package/dist/cjs/agents/projection.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +47 -6
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +20 -7
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +1 -1
  10. package/dist/cjs/llm/openai/utils/index.cjs +1 -1
  11. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  12. package/dist/cjs/llm/preempt.cjs +1 -10
  13. package/dist/cjs/llm/preempt.cjs.map +1 -1
  14. package/dist/cjs/main.cjs +23 -3
  15. package/dist/cjs/messages/contextPruning.cjs +1 -1
  16. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  17. package/dist/cjs/messages/core.cjs +29 -1
  18. package/dist/cjs/messages/core.cjs.map +1 -1
  19. package/dist/cjs/messages/fading.cjs +122 -0
  20. package/dist/cjs/messages/fading.cjs.map +1 -0
  21. package/dist/cjs/messages/format.cjs +1 -1
  22. package/dist/cjs/messages/index.cjs +1 -0
  23. package/dist/cjs/messages/prune.cjs +364 -236
  24. package/dist/cjs/messages/prune.cjs.map +1 -1
  25. package/dist/cjs/run.cjs +27 -0
  26. package/dist/cjs/run.cjs.map +1 -1
  27. package/dist/cjs/session/AgentSession.cjs +213 -8
  28. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  29. package/dist/cjs/session/JsonlSessionStore.cjs +41 -0
  30. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
  31. package/dist/cjs/session/index.cjs +1 -1
  32. package/dist/cjs/stream.cjs +1 -1
  33. package/dist/cjs/summarization/node.cjs +1 -1
  34. package/dist/cjs/tools/ToolNode.cjs +1 -1
  35. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +2 -0
  36. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  37. package/dist/cjs/tools/subagent/SubagentReplay.cjs +2 -1
  38. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  39. package/dist/cjs/utils/index.cjs +1 -1
  40. package/dist/cjs/utils/truncation.cjs +9 -0
  41. package/dist/cjs/utils/truncation.cjs.map +1 -1
  42. package/dist/esm/agents/AgentContext.mjs +92 -1
  43. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  44. package/dist/esm/agents/projection.mjs +2 -1
  45. package/dist/esm/agents/projection.mjs.map +1 -1
  46. package/dist/esm/graphs/Graph.mjs +47 -6
  47. package/dist/esm/graphs/Graph.mjs.map +1 -1
  48. package/dist/esm/graphs/MultiAgentGraph.mjs +20 -7
  49. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  50. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +1 -1
  51. package/dist/esm/llm/openai/utils/index.mjs +2 -2
  52. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  53. package/dist/esm/llm/preempt.mjs +1 -10
  54. package/dist/esm/llm/preempt.mjs.map +1 -1
  55. package/dist/esm/main.mjs +6 -5
  56. package/dist/esm/messages/contextPruning.mjs +1 -1
  57. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  58. package/dist/esm/messages/core.mjs +29 -2
  59. package/dist/esm/messages/core.mjs.map +1 -1
  60. package/dist/esm/messages/fading.mjs +108 -0
  61. package/dist/esm/messages/fading.mjs.map +1 -0
  62. package/dist/esm/messages/format.mjs +1 -1
  63. package/dist/esm/messages/index.mjs +1 -0
  64. package/dist/esm/messages/prune.mjs +363 -235
  65. package/dist/esm/messages/prune.mjs.map +1 -1
  66. package/dist/esm/run.mjs +27 -0
  67. package/dist/esm/run.mjs.map +1 -1
  68. package/dist/esm/session/AgentSession.mjs +213 -8
  69. package/dist/esm/session/AgentSession.mjs.map +1 -1
  70. package/dist/esm/session/JsonlSessionStore.mjs +41 -0
  71. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
  72. package/dist/esm/session/index.mjs +1 -1
  73. package/dist/esm/stream.mjs +1 -1
  74. package/dist/esm/summarization/node.mjs +1 -1
  75. package/dist/esm/tools/ToolNode.mjs +1 -1
  76. package/dist/esm/tools/subagent/SubagentExecutor.mjs +2 -0
  77. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  78. package/dist/esm/tools/subagent/SubagentReplay.mjs +2 -1
  79. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  80. package/dist/esm/utils/index.mjs +1 -1
  81. package/dist/esm/utils/truncation.mjs +7 -1
  82. package/dist/esm/utils/truncation.mjs.map +1 -1
  83. package/dist/types/agents/AgentContext.d.ts +32 -1
  84. package/dist/types/agents/projection.d.ts +3 -1
  85. package/dist/types/graphs/Graph.d.ts +14 -1
  86. package/dist/types/graphs/MultiAgentGraph.d.ts +7 -0
  87. package/dist/types/messages/contextPruning.d.ts +2 -0
  88. package/dist/types/messages/core.d.ts +2 -0
  89. package/dist/types/messages/fading.d.ts +81 -0
  90. package/dist/types/messages/index.d.ts +1 -0
  91. package/dist/types/messages/prune.d.ts +71 -24
  92. package/dist/types/run.d.ts +16 -0
  93. package/dist/types/session/AgentSession.d.ts +15 -0
  94. package/dist/types/session/JsonlSessionStore.d.ts +4 -1
  95. package/dist/types/session/types.d.ts +9 -0
  96. package/dist/types/tools/subagent/SubagentReplay.d.ts +3 -1
  97. package/dist/types/types/graph.d.ts +23 -0
  98. package/dist/types/types/run.d.ts +10 -0
  99. package/dist/types/utils/truncation.d.ts +9 -0
  100. package/package.json +1 -1
  101. package/src/agents/AgentContext.ts +175 -1
  102. package/src/agents/projection.ts +4 -0
  103. package/src/graphs/Graph.ts +97 -11
  104. package/src/graphs/MultiAgentGraph.ts +54 -6
  105. package/src/llm/openai/utils/index.ts +3 -1
  106. package/src/llm/preempt.ts +4 -17
  107. package/src/messages/contextPruning.ts +7 -1
  108. package/src/messages/core.ts +52 -0
  109. package/src/messages/fading.ts +301 -0
  110. package/src/messages/index.ts +1 -0
  111. package/src/messages/prune.ts +719 -507
  112. package/src/run.ts +52 -0
  113. package/src/session/AgentSession.ts +397 -8
  114. package/src/session/JsonlSessionStore.ts +71 -0
  115. package/src/session/types.ts +10 -0
  116. package/src/tools/subagent/SubagentExecutor.ts +6 -0
  117. package/src/tools/subagent/SubagentReplay.ts +15 -2
  118. package/src/types/graph.ts +25 -0
  119. package/src/types/run.ts +10 -0
  120. package/src/utils/truncation.ts +25 -0
package/src/run.ts CHANGED
@@ -22,6 +22,7 @@ import type {
22
22
  } from '@langchain/core/messages';
23
23
  import type { StringPromptValue } from '@langchain/core/prompt_values';
24
24
  import type { RunnableConfig } from '@langchain/core/runnables';
25
+ import { isFadingTier } from '@/messages/fading';
25
26
  import type { AggregatedHookResult, HookRegistry } from '@/hooks';
26
27
  import type { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
27
28
  import type { StandardGraph } from '@/graphs/Graph';
@@ -472,6 +473,10 @@ export class Run<_T extends t.BaseGraphState> {
472
473
  private subagentTasks?: t.SubagentTaskConfig;
473
474
  private indexTokenCountMap?: Record<string, number>;
474
475
  calibrationRatio: number = 1;
476
+ fadingTier?: t.FadingTier;
477
+ fadingTiers: t.FadingTiers = {};
478
+ private fadingTierReset: boolean = false;
479
+ private fadingTierResetAgentIds: string[] = [];
475
480
  graphRunnable?: t.CompiledStateWorkflow;
476
481
  Graph: StandardGraph | MultiAgentGraph | undefined;
477
482
  returnContent: boolean = false;
@@ -516,6 +521,16 @@ export class Run<_T extends t.BaseGraphState> {
516
521
  if (config.calibrationRatio != null && config.calibrationRatio > 0) {
517
522
  this.calibrationRatio = config.calibrationRatio;
518
523
  }
524
+ if (isFadingTier(config.fadingTier)) {
525
+ this.fadingTier = { ...config.fadingTier };
526
+ }
527
+ if (config.fadingTiers != null) {
528
+ this.fadingTiers = Object.fromEntries(
529
+ Object.entries(config.fadingTiers).flatMap(([agentId, tier]) =>
530
+ isFadingTier(tier) ? [[agentId, { ...tier }] as const] : []
531
+ )
532
+ );
533
+ }
519
534
 
520
535
  const handlerRegistry = new HandlerRegistry();
521
536
 
@@ -632,6 +647,8 @@ export class Run<_T extends t.BaseGraphState> {
632
647
  tokenCounter: this.tokenCounter,
633
648
  indexTokenCountMap: this.indexTokenCountMap,
634
649
  calibrationRatio: this.calibrationRatio,
650
+ fadingTier: this.fadingTier,
651
+ fadingTiers: this.fadingTiers,
635
652
  subagentUsageSink: this.subagentUsageSink,
636
653
  subagentTasks: this.subagentTasks,
637
654
  preemption: this.preemption,
@@ -672,6 +689,8 @@ export class Run<_T extends t.BaseGraphState> {
672
689
  tokenCounter: this.tokenCounter,
673
690
  indexTokenCountMap: this.indexTokenCountMap,
674
691
  calibrationRatio: this.calibrationRatio,
692
+ fadingTier: this.fadingTier,
693
+ fadingTiers: this.fadingTiers,
675
694
  subagentUsageSink: this.subagentUsageSink,
676
695
  subagentTasks: this.subagentTasks,
677
696
  preemption: this.preemption,
@@ -937,6 +956,35 @@ export class Run<_T extends t.BaseGraphState> {
937
956
  return this.calibrationRatio;
938
957
  }
939
958
 
959
+ /**
960
+ * Returns the default agent's latched context-fading tier. Single-agent hosts
961
+ * may persist it beside the calibration ratio; multi-agent hosts should use
962
+ * `getFadingTiers()` so independent agent tiers are retained.
963
+ */
964
+ getFadingTier(): t.FadingTier | undefined {
965
+ return this.fadingTier == null ? undefined : { ...this.fadingTier };
966
+ }
967
+
968
+ /** Returns a defensive snapshot of latched tiers keyed by agent ID. */
969
+ getFadingTiers(): t.FadingTiers {
970
+ return Object.fromEntries(
971
+ Object.entries(this.fadingTiers).map(([agentId, tier]) => [
972
+ agentId,
973
+ { ...tier },
974
+ ])
975
+ );
976
+ }
977
+
978
+ /** Agent IDs whose canonical history was compacted during this run. */
979
+ getFadingTierResetAgentIds(): string[] {
980
+ return [...this.fadingTierResetAgentIds];
981
+ }
982
+
983
+ /** Whether the default agent compacted canonical history during this run. */
984
+ didResetFadingTier(): boolean {
985
+ return this.fadingTierReset;
986
+ }
987
+
940
988
  getResolvedInstructionOverhead(): number | undefined {
941
989
  return this.Graph?.getResolvedInstructionOverhead();
942
990
  }
@@ -1761,6 +1809,10 @@ export class Run<_T extends t.BaseGraphState> {
1761
1809
  : undefined;
1762
1810
 
1763
1811
  this.calibrationRatio = this.Graph.getCalibrationRatio();
1812
+ this.fadingTier = this.Graph.getFadingTier();
1813
+ this.fadingTiers = this.Graph.getFadingTiers();
1814
+ this.fadingTierReset = this.Graph.didResetFadingTier();
1815
+ this.fadingTierResetAgentIds = this.Graph.getFadingTierResetAgentIds();
1764
1816
 
1765
1817
  /**
1766
1818
  * Skip `clearHeavyState()` when the run paused on a clean HITL
@@ -21,18 +21,22 @@ import type {
21
21
  SessionEntry,
22
22
  SessionForkOptions,
23
23
  } from './types';
24
+ import { isFadingTier } from '@/messages/fading';
24
25
  import type { HookRegistry } from '@/hooks';
25
26
  import type * as t from '@/types';
26
- import { deriveMessages } from './deriveMessages';
27
27
  import { createSummarizeNode } from '@/summarization/node';
28
28
  import { resolveStreamLimits } from '@/llm/streamLimits';
29
29
  import { JsonlSessionStore } from './JsonlSessionStore';
30
30
  import { AgentContext } from '@/agents/AgentContext';
31
31
  import { ContentTypes, GraphEvents } from '@/common';
32
32
  import { createRunId, createSessionId } from './ids';
33
+ import { deriveMessages } from './deriveMessages';
33
34
  import { createRunHandlers } from './handlers';
34
35
  import { Run } from '@/run';
35
36
 
37
+ /** Compact per-thread fading metadata retained for recently used checkpoints. */
38
+ const MAX_ALTERNATE_THREAD_FADING_STATES = 64;
39
+
36
40
  function isBaseMessage(value: unknown): value is BaseMessage {
37
41
  return (
38
42
  value instanceof BaseMessage ||
@@ -366,6 +370,90 @@ function createCallerConfig(
366
370
  };
367
371
  }
368
372
 
373
+ /** Generation key for one tier within a scope; `''` names the default tier. */
374
+ function fadingGenerationKey(scopeKey: string, agentKey: string): string {
375
+ return JSON.stringify([scopeKey, agentKey]);
376
+ }
377
+
378
+ function fadingGenerationScope(generationKey: string): string {
379
+ const parsed: unknown = JSON.parse(generationKey);
380
+ return Array.isArray(parsed) && typeof parsed[0] === 'string' ? parsed[0] : '';
381
+ }
382
+
383
+ /** What a run saw when it started, so its capture can tell stale from current. */
384
+ type FadingCaptureSnapshot = {
385
+ epoch: number;
386
+ generations: Map<string, number>;
387
+ };
388
+
389
+ function fadingScopeKey(threadId: string, checkpointNs = ''): string {
390
+ return JSON.stringify([threadId, checkpointNs]);
391
+ }
392
+
393
+ /** Merges two tiers monotonically. A malformed side (a corrupt store line or
394
+ * host input) is ignored rather than poisoning the budget with `NaN`. */
395
+ function mergeFadingTier(
396
+ currentTier: t.FadingTier | undefined,
397
+ incomingTier: t.FadingTier | undefined
398
+ ): t.FadingTier | undefined {
399
+ const current = isFadingTier(currentTier) ? currentTier : undefined;
400
+ const incoming = isFadingTier(incomingTier) ? incomingTier : undefined;
401
+ if (current == null) {
402
+ return incoming == null ? undefined : { ...incoming };
403
+ }
404
+ if (incoming == null) {
405
+ return { ...current };
406
+ }
407
+ const budgetTokens = Math.min(
408
+ current.budgetTokens,
409
+ incoming.budgetTokens
410
+ );
411
+ const masked = current.masked || incoming.masked;
412
+ const latched =
413
+ current.latched === true ||
414
+ incoming.latched === true ||
415
+ budgetTokens !== current.budgetTokens ||
416
+ budgetTokens !== incoming.budgetTokens ||
417
+ masked !== current.masked ||
418
+ masked !== incoming.masked;
419
+ return {
420
+ v: 1,
421
+ budgetTokens,
422
+ masked,
423
+ ...(latched ? { latched: true } : {}),
424
+ };
425
+ }
426
+
427
+ function mergeFadingTiers(
428
+ current: t.FadingTiers | undefined,
429
+ incoming: t.FadingTiers | undefined
430
+ ): t.FadingTiers {
431
+ const agentIds = new Set([
432
+ ...Object.keys(current ?? {}),
433
+ ...Object.keys(incoming ?? {}),
434
+ ]);
435
+ return Object.fromEntries(
436
+ [...agentIds].flatMap((agentId) => {
437
+ const tier = mergeFadingTier(
438
+ ownFadingTier(current, agentId),
439
+ ownFadingTier(incoming, agentId)
440
+ );
441
+ return tier == null ? [] : [[agentId, tier]];
442
+ })
443
+ );
444
+ }
445
+
446
+ /** Reads an agent's tier as an own property, so an ID such as `constructor`
447
+ * never resolves through the prototype chain. */
448
+ function ownFadingTier(
449
+ tiers: t.FadingTiers | undefined,
450
+ agentId: string
451
+ ): t.FadingTier | undefined {
452
+ return tiers != null && Object.prototype.hasOwnProperty.call(tiers, agentId)
453
+ ? tiers[agentId]
454
+ : undefined;
455
+ }
456
+
369
457
  function createCheckpointLookupConfig(config: RunnableConfig): RunnableConfig {
370
458
  const threadId = getConfigString(config, 'thread_id');
371
459
  if (threadId == null) {
@@ -850,6 +938,16 @@ export class AgentSession {
850
938
  private runConfig: t.RunConfig;
851
939
  private store: JsonlSessionStore | undefined;
852
940
  private calibrationRatio: number | undefined;
941
+ private fadingTier: t.FadingTier | undefined;
942
+ private fadingTiers: t.FadingTiers | undefined;
943
+ /** Reset generations keyed per scope and per tier (`''` for the default). */
944
+ private fadingGenerations = new Map<string, number>();
945
+ /** Advances on every explicit history rewrite (branch, compact, restore). */
946
+ private fadingRewriteEpoch = 0;
947
+ private alternateThreadFadingState = new Map<
948
+ string,
949
+ { fadingTier?: t.FadingTier; fadingTiers?: t.FadingTiers }
950
+ >();
853
951
  private checkpointing: SessionCheckpointingState;
854
952
  cwd: string;
855
953
  threadId: string;
@@ -866,6 +964,18 @@ export class AgentSession {
866
964
  this.threadId = params.threadId;
867
965
  this.store = params.store;
868
966
  this.checkpointing = params.checkpointing;
967
+ this.fadingTier =
968
+ params.runConfig.fadingTier == null
969
+ ? undefined
970
+ : { ...params.runConfig.fadingTier };
971
+ this.fadingTiers =
972
+ params.runConfig.fadingTiers == null
973
+ ? undefined
974
+ : Object.fromEntries(
975
+ Object.entries(params.runConfig.fadingTiers).map(
976
+ ([agentId, tier]) => [agentId, { ...tier }]
977
+ )
978
+ );
869
979
  }
870
980
 
871
981
  static async create(config: AgentSessionConfig): Promise<AgentSession> {
@@ -881,7 +991,7 @@ export class AgentSession {
881
991
  sessionId: explicitSessionId,
882
992
  ephemeral: normalized.ephemeral,
883
993
  });
884
- return new AgentSession({
994
+ const session = new AgentSession({
885
995
  runConfig: normalized.runConfig,
886
996
  cwd: normalized.cwd,
887
997
  threadId: store?.header.id ?? explicitSessionId ?? createSessionId(),
@@ -891,6 +1001,8 @@ export class AgentSession {
891
1001
  ),
892
1002
  store,
893
1003
  });
1004
+ session.restoreFadingStateFromStore();
1005
+ return session;
894
1006
  }
895
1007
 
896
1008
  get sessionPath(): string | undefined {
@@ -905,6 +1017,223 @@ export class AgentSession {
905
1017
  return this.checkpointing.checkpointer;
906
1018
  }
907
1019
 
1020
+ private getFadingState(threadId: string, checkpointNs = ''): {
1021
+ fadingTier?: t.FadingTier;
1022
+ fadingTiers?: t.FadingTiers;
1023
+ } {
1024
+ if (threadId === this.threadId && checkpointNs === '') {
1025
+ return { fadingTier: this.fadingTier, fadingTiers: this.fadingTiers };
1026
+ }
1027
+ const scopeKey = fadingScopeKey(threadId, checkpointNs);
1028
+ const state = this.alternateThreadFadingState.get(scopeKey);
1029
+ if (state == null) {
1030
+ return {};
1031
+ }
1032
+ /** Refresh insertion order so the bounded map acts as an LRU. */
1033
+ this.alternateThreadFadingState.delete(scopeKey);
1034
+ this.alternateThreadFadingState.set(scopeKey, state);
1035
+ return state;
1036
+ }
1037
+
1038
+ private setFadingState(
1039
+ threadId: string,
1040
+ checkpointNs: string,
1041
+ fadingTier: t.FadingTier | undefined,
1042
+ fadingTiers: t.FadingTiers
1043
+ ): void {
1044
+ if (threadId === this.threadId && checkpointNs === '') {
1045
+ this.fadingTier = mergeFadingTier(this.fadingTier, fadingTier);
1046
+ this.fadingTiers = mergeFadingTiers(this.fadingTiers, fadingTiers);
1047
+ return;
1048
+ }
1049
+ const scopeKey = fadingScopeKey(threadId, checkpointNs);
1050
+ const current = this.alternateThreadFadingState.get(scopeKey);
1051
+ this.alternateThreadFadingState.delete(scopeKey);
1052
+ this.alternateThreadFadingState.set(scopeKey, {
1053
+ fadingTier: mergeFadingTier(current?.fadingTier, fadingTier),
1054
+ fadingTiers: mergeFadingTiers(current?.fadingTiers, fadingTiers),
1055
+ });
1056
+ this.trimAlternateFadingStates();
1057
+ }
1058
+
1059
+ private trimAlternateFadingStates(): void {
1060
+ while (
1061
+ this.alternateThreadFadingState.size >
1062
+ MAX_ALTERNATE_THREAD_FADING_STATES
1063
+ ) {
1064
+ const oldestThreadId = this.alternateThreadFadingState.keys().next();
1065
+ if (oldestThreadId.done === true) {
1066
+ break;
1067
+ }
1068
+ this.alternateThreadFadingState.delete(oldestThreadId.value);
1069
+ for (const key of [...this.fadingGenerations.keys()]) {
1070
+ if (fadingGenerationScope(key) === oldestThreadId.value) {
1071
+ this.fadingGenerations.delete(key);
1072
+ }
1073
+ }
1074
+ }
1075
+ }
1076
+
1077
+ private clearFadingState(): void {
1078
+ this.fadingTier = undefined;
1079
+ this.fadingTiers = undefined;
1080
+ this.alternateThreadFadingState.clear();
1081
+ this.fadingGenerations.clear();
1082
+ /** A run that started before the rewrite would otherwise re-persist tiers
1083
+ * learned on a history that no longer exists once it completes. */
1084
+ this.fadingRewriteEpoch += 1;
1085
+ }
1086
+
1087
+ private snapshotFadingGenerations(): FadingCaptureSnapshot {
1088
+ return {
1089
+ epoch: this.fadingRewriteEpoch,
1090
+ generations: new Map(this.fadingGenerations),
1091
+ };
1092
+ }
1093
+
1094
+ private restoreFadingStateFromStore(clearWhenMissing = false): void {
1095
+ const states =
1096
+ this.store?.getFadingStates(
1097
+ this.threadId,
1098
+ MAX_ALTERNATE_THREAD_FADING_STATES
1099
+ ) ?? [];
1100
+ if (states.length === 0) {
1101
+ if (clearWhenMissing || this.store?.hasFadingState() === true) {
1102
+ this.clearFadingState();
1103
+ }
1104
+ return;
1105
+ }
1106
+ this.clearFadingState();
1107
+ /** Store returns newest first; replay oldest first to retain LRU order. */
1108
+ for (let i = states.length - 1; i >= 0; i--) {
1109
+ const state = states[i];
1110
+ this.setFadingState(
1111
+ state.threadId,
1112
+ state.checkpointNs ?? '',
1113
+ isFadingTier(state.fadingTier) ? { ...state.fadingTier } : undefined,
1114
+ state.fadingTiers == null
1115
+ ? {}
1116
+ : Object.fromEntries(
1117
+ Object.entries(state.fadingTiers).flatMap(([agentId, tier]) =>
1118
+ isFadingTier(tier) ? [[agentId, { ...tier }] as const] : []
1119
+ )
1120
+ )
1121
+ );
1122
+ }
1123
+ }
1124
+
1125
+ private async persistFadingState(
1126
+ threadId: string,
1127
+ checkpointNs = ''
1128
+ ): Promise<void> {
1129
+ const state = this.getFadingState(threadId, checkpointNs);
1130
+ await this.store?.appendFadingState({
1131
+ threadId,
1132
+ ...(checkpointNs === '' ? {} : { checkpointNs }),
1133
+ ...(state.fadingTier == null
1134
+ ? {}
1135
+ : { fadingTier: { ...state.fadingTier } }),
1136
+ ...(state.fadingTiers == null
1137
+ ? {}
1138
+ : {
1139
+ fadingTiers: Object.fromEntries(
1140
+ Object.entries(state.fadingTiers).map(([agentId, tier]) => [
1141
+ agentId,
1142
+ { ...tier },
1143
+ ])
1144
+ ),
1145
+ }),
1146
+ });
1147
+ }
1148
+
1149
+ private async captureRunContextState(
1150
+ threadId: string,
1151
+ checkpointNs: string,
1152
+ run: Run<t.IState>,
1153
+ snapshot: FadingCaptureSnapshot
1154
+ ): Promise<void> {
1155
+ this.calibrationRatio = run.getCalibrationRatio();
1156
+ if (snapshot.epoch < this.fadingRewriteEpoch) {
1157
+ return;
1158
+ }
1159
+ const current = this.getFadingState(threadId, checkpointNs);
1160
+ const incomingTier = run.getFadingTier();
1161
+ const incomingTiers = run.getFadingTiers();
1162
+ const scopeKey = fadingScopeKey(threadId, checkpointNs);
1163
+ /** A tier whose generation moved after this run started was reset by a
1164
+ * concurrent run; this run's view of it is obsolete, while its other
1165
+ * agents' tiers remain valid escalations. */
1166
+ const isStale = (agentKey: string): boolean => {
1167
+ const key = fadingGenerationKey(scopeKey, agentKey);
1168
+ return (
1169
+ (snapshot.generations.get(key) ?? 0) <
1170
+ (this.fadingGenerations.get(key) ?? 0)
1171
+ );
1172
+ };
1173
+ const advanceGeneration = (agentKey: string): void => {
1174
+ const key = fadingGenerationKey(scopeKey, agentKey);
1175
+ this.fadingGenerations.set(key, (this.fadingGenerations.get(key) ?? 0) + 1);
1176
+ };
1177
+ const mergeCapturedTier = (
1178
+ existing: t.FadingTier | undefined,
1179
+ incoming: t.FadingTier | undefined,
1180
+ reset: boolean
1181
+ ): t.FadingTier | undefined => {
1182
+ if (reset) {
1183
+ return incoming == null ? undefined : { ...incoming };
1184
+ }
1185
+ return mergeFadingTier(existing, incoming);
1186
+ };
1187
+ const resetAgentIds = new Set(run.getFadingTierResetAgentIds());
1188
+ const nextTiers: t.FadingTiers = Object.create(null) as t.FadingTiers;
1189
+ const allAgentIds = new Set([
1190
+ ...Object.keys(current.fadingTiers ?? {}),
1191
+ ...Object.keys(incomingTiers),
1192
+ ...resetAgentIds,
1193
+ ]);
1194
+ for (const agentId of allAgentIds) {
1195
+ const existing = ownFadingTier(current.fadingTiers, agentId);
1196
+ if (isStale(agentId)) {
1197
+ if (existing != null) {
1198
+ nextTiers[agentId] = existing;
1199
+ }
1200
+ continue;
1201
+ }
1202
+ const reset = resetAgentIds.has(agentId);
1203
+ if (reset) {
1204
+ advanceGeneration(agentId);
1205
+ }
1206
+ const tier = mergeCapturedTier(
1207
+ existing,
1208
+ ownFadingTier(incomingTiers, agentId),
1209
+ reset
1210
+ );
1211
+ if (tier != null) {
1212
+ nextTiers[agentId] = tier;
1213
+ }
1214
+ }
1215
+ let nextTier = current.fadingTier;
1216
+ if (!isStale('')) {
1217
+ const didResetTier = run.didResetFadingTier();
1218
+ if (didResetTier) {
1219
+ advanceGeneration('');
1220
+ }
1221
+ nextTier = mergeCapturedTier(current.fadingTier, incomingTier, didResetTier);
1222
+ }
1223
+ if (threadId === this.threadId && checkpointNs === '') {
1224
+ this.fadingTier = nextTier;
1225
+ this.fadingTiers = nextTiers;
1226
+ } else {
1227
+ this.alternateThreadFadingState.delete(scopeKey);
1228
+ this.alternateThreadFadingState.set(scopeKey, {
1229
+ fadingTier: nextTier,
1230
+ fadingTiers: nextTiers,
1231
+ });
1232
+ this.trimAlternateFadingStates();
1233
+ }
1234
+ await this.persistFadingState(threadId, checkpointNs);
1235
+ }
1236
+
908
1237
  async getLatestCheckpoint(
909
1238
  options: AgentSessionCheckpointLookupOptions = {}
910
1239
  ): Promise<AgentSessionCheckpointReference | undefined> {
@@ -1042,6 +1371,8 @@ export class AgentSession {
1042
1371
  const normalizedInput = normalizeInput(input);
1043
1372
  const inputMessages = normalizedInput.messages;
1044
1373
  const callerConfig = createCallerConfig(threadId, options);
1374
+ const checkpointNs = getConfigString(callerConfig, 'checkpoint_ns') ?? '';
1375
+ const fadingSnapshot = this.snapshotFadingGenerations();
1045
1376
  const useCheckpointState = await this.hasCheckpointState(callerConfig);
1046
1377
  let parentId = this.store?.getLeafEntry()?.id ?? null;
1047
1378
  if (isSessionThread) {
@@ -1080,7 +1411,9 @@ export class AgentSession {
1080
1411
  const sessionState = deriveMessages(
1081
1412
  isSessionThread ? (this.store?.getPath() ?? []) : []
1082
1413
  );
1414
+ const fadingState = this.getFadingState(threadId, checkpointNs);
1083
1415
  let run: Run<t.IState> | undefined;
1416
+ let runContextCaptured = false;
1084
1417
  try {
1085
1418
  const runConfig: t.RunConfig = {
1086
1419
  ...this.runConfig,
@@ -1094,6 +1427,8 @@ export class AgentSession {
1094
1427
  ),
1095
1428
  returnContent: true,
1096
1429
  calibrationRatio: this.calibrationRatio,
1430
+ fadingTier: fadingState.fadingTier,
1431
+ fadingTiers: fadingState.fadingTiers,
1097
1432
  customHandlers: {
1098
1433
  ...(this.runConfig.customHandlers ?? {}),
1099
1434
  ...handlerResult.handlers,
@@ -1115,8 +1450,14 @@ export class AgentSession {
1115
1450
  await this.store?.appendMessage(message);
1116
1451
  }
1117
1452
  }
1118
- this.calibrationRatio = run.getCalibrationRatio();
1119
1453
  const interrupt = run.getInterrupt();
1454
+ await this.captureRunContextState(
1455
+ threadId,
1456
+ interrupt?.checkpointNs ?? checkpointNs,
1457
+ run,
1458
+ fadingSnapshot
1459
+ );
1460
+ runContextCaptured = true;
1120
1461
  const haltedReason = run.getHaltReason();
1121
1462
  if (interrupt) {
1122
1463
  emitTerminalEvent({ type: 'run.interrupted' });
@@ -1165,6 +1506,14 @@ export class AgentSession {
1165
1506
  threadId,
1166
1507
  };
1167
1508
  } catch (error) {
1509
+ if (run != null && !runContextCaptured) {
1510
+ await this.captureRunContextState(
1511
+ threadId,
1512
+ checkpointNs,
1513
+ run,
1514
+ fadingSnapshot
1515
+ );
1516
+ }
1168
1517
  emitTerminalEvent({
1169
1518
  type: 'run.failed',
1170
1519
  data: error instanceof Error ? error.message : String(error),
@@ -1227,11 +1576,13 @@ export class AgentSession {
1227
1576
  this.store = await JsonlSessionStore.open(sessions[0].path);
1228
1577
  this.cwd = this.store.header.cwd;
1229
1578
  this.threadId = this.store.header.id;
1579
+ this.restoreFadingStateFromStore(true);
1230
1580
  return this;
1231
1581
  }
1232
1582
  this.store = await JsonlSessionStore.open(pathOrId);
1233
1583
  this.cwd = this.store.header.cwd;
1234
1584
  this.threadId = this.store.header.id;
1585
+ this.restoreFadingStateFromStore(true);
1235
1586
  return this;
1236
1587
  }
1237
1588
 
@@ -1240,13 +1591,19 @@ export class AgentSession {
1240
1591
  throw new Error('Cannot clone an ephemeral session');
1241
1592
  }
1242
1593
  const store = await this.store.clone(options);
1243
- return new AgentSession({
1244
- runConfig: this.runConfig,
1594
+ const session = new AgentSession({
1595
+ runConfig: {
1596
+ ...this.runConfig,
1597
+ fadingTier: this.fadingTier,
1598
+ fadingTiers: this.fadingTiers,
1599
+ },
1245
1600
  cwd: store.header.cwd,
1246
1601
  threadId: store.header.id,
1247
1602
  checkpointing: this.checkpointing,
1248
1603
  store,
1249
1604
  });
1605
+ await session.persistFadingState(session.threadId);
1606
+ return session;
1250
1607
  }
1251
1608
 
1252
1609
  async fork(
@@ -1256,14 +1613,22 @@ export class AgentSession {
1256
1613
  if (!this.store) {
1257
1614
  throw new Error('Cannot fork an ephemeral session');
1258
1615
  }
1616
+ const retainsActiveLeaf =
1617
+ options.position === 'at' && this.store.getLeafEntry()?.id === entryId;
1259
1618
  const store = await this.store.fork(entryId, options);
1260
- return new AgentSession({
1261
- runConfig: this.runConfig,
1619
+ const session = new AgentSession({
1620
+ runConfig: {
1621
+ ...this.runConfig,
1622
+ fadingTier: retainsActiveLeaf ? this.fadingTier : undefined,
1623
+ fadingTiers: retainsActiveLeaf ? this.fadingTiers : undefined,
1624
+ },
1262
1625
  cwd: store.header.cwd,
1263
1626
  threadId: store.header.id,
1264
1627
  checkpointing: this.checkpointing,
1265
1628
  store,
1266
1629
  });
1630
+ await session.persistFadingState(session.threadId);
1631
+ return session;
1267
1632
  }
1268
1633
 
1269
1634
  async branch(
@@ -1303,6 +1668,8 @@ export class AgentSession {
1303
1668
  return;
1304
1669
  }
1305
1670
  await store.setLeaf(leafId);
1671
+ this.clearFadingState();
1672
+ await store.appendFadingState(null);
1306
1673
  await this.resetCheckpointThreads('branch');
1307
1674
  }
1308
1675
 
@@ -1424,6 +1791,8 @@ export class AgentSession {
1424
1791
  retainedEntryIds,
1425
1792
  summarizedEntryIds: summarized.map((entry) => entry.id),
1426
1793
  });
1794
+ this.clearFadingState();
1795
+ await store.appendFadingState(null);
1427
1796
  return summary;
1428
1797
  }
1429
1798
 
@@ -1439,6 +1808,8 @@ export class AgentSession {
1439
1808
  baseCallerConfig,
1440
1809
  getStoredCheckpointForConfig(this.store, threadId, baseCallerConfig)?.data
1441
1810
  );
1811
+ const checkpointNs = getConfigString(callerConfig, 'checkpoint_ns') ?? '';
1812
+ const fadingSnapshot = this.snapshotFadingGenerations();
1442
1813
  await this.store?.appendRunEvent('run.started', undefined, {
1443
1814
  runId,
1444
1815
  threadId,
@@ -1451,7 +1822,9 @@ export class AgentSession {
1451
1822
  const sessionState = deriveMessages(
1452
1823
  isSessionThread ? (this.store?.getPath() ?? []) : []
1453
1824
  );
1825
+ const fadingState = this.getFadingState(threadId, checkpointNs);
1454
1826
  let run: Run<t.IState> | undefined;
1827
+ let runContextCaptured = false;
1455
1828
  try {
1456
1829
  run = await Run.create<t.IState>({
1457
1830
  ...this.runConfig,
@@ -1465,6 +1838,8 @@ export class AgentSession {
1465
1838
  ),
1466
1839
  returnContent: true,
1467
1840
  calibrationRatio: this.calibrationRatio,
1841
+ fadingTier: fadingState.fadingTier,
1842
+ fadingTiers: fadingState.fadingTiers,
1468
1843
  customHandlers: {
1469
1844
  ...(this.runConfig.customHandlers ?? {}),
1470
1845
  ...handlerResult.handlers,
@@ -1477,8 +1852,14 @@ export class AgentSession {
1477
1852
  await this.store?.appendMessage(message);
1478
1853
  }
1479
1854
  }
1480
- this.calibrationRatio = run.getCalibrationRatio();
1481
1855
  const interrupt = run.getInterrupt();
1856
+ await this.captureRunContextState(
1857
+ threadId,
1858
+ interrupt?.checkpointNs ?? checkpointNs,
1859
+ run,
1860
+ fadingSnapshot
1861
+ );
1862
+ runContextCaptured = true;
1482
1863
  const haltedReason = run.getHaltReason();
1483
1864
  if (interrupt) {
1484
1865
  await this.store?.appendRunEvent('run.interrupted', interrupt, {
@@ -1524,6 +1905,14 @@ export class AgentSession {
1524
1905
  threadId,
1525
1906
  };
1526
1907
  } catch (error) {
1908
+ if (run != null && !runContextCaptured) {
1909
+ await this.captureRunContextState(
1910
+ threadId,
1911
+ checkpointNs,
1912
+ run,
1913
+ fadingSnapshot
1914
+ );
1915
+ }
1527
1916
  await this.store?.appendRunEvent('run.failed', error, {
1528
1917
  runId,
1529
1918
  threadId,