agentfootprint 2.5.1 → 2.6.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 (115) hide show
  1. package/dist/adapters/llm/BrowserAnthropicProvider.js +68 -0
  2. package/dist/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
  3. package/dist/cache/CacheDecisionSubflow.js +172 -0
  4. package/dist/cache/CacheDecisionSubflow.js.map +1 -0
  5. package/dist/cache/CacheGateDecider.js +122 -0
  6. package/dist/cache/CacheGateDecider.js.map +1 -0
  7. package/dist/cache/applyCachePolicy.js +55 -0
  8. package/dist/cache/applyCachePolicy.js.map +1 -0
  9. package/dist/cache/cacheRecorder.js +120 -0
  10. package/dist/cache/cacheRecorder.js.map +1 -0
  11. package/dist/cache/index.js +47 -0
  12. package/dist/cache/index.js.map +1 -0
  13. package/dist/cache/strategies/AnthropicCacheStrategy.js +102 -0
  14. package/dist/cache/strategies/AnthropicCacheStrategy.js.map +1 -0
  15. package/dist/cache/strategies/BedrockCacheStrategy.js +81 -0
  16. package/dist/cache/strategies/BedrockCacheStrategy.js.map +1 -0
  17. package/dist/cache/strategies/NoOpCacheStrategy.js +40 -0
  18. package/dist/cache/strategies/NoOpCacheStrategy.js.map +1 -0
  19. package/dist/cache/strategies/OpenAICacheStrategy.js +75 -0
  20. package/dist/cache/strategies/OpenAICacheStrategy.js.map +1 -0
  21. package/dist/cache/strategyRegistry.js +80 -0
  22. package/dist/cache/strategyRegistry.js.map +1 -0
  23. package/dist/cache/types.js +25 -0
  24. package/dist/cache/types.js.map +1 -0
  25. package/dist/conventions.js +18 -0
  26. package/dist/conventions.js.map +1 -1
  27. package/dist/core/Agent.js +157 -4
  28. package/dist/core/Agent.js.map +1 -1
  29. package/dist/esm/adapters/llm/BrowserAnthropicProvider.js +68 -0
  30. package/dist/esm/adapters/llm/BrowserAnthropicProvider.js.map +1 -1
  31. package/dist/esm/cache/CacheDecisionSubflow.js +166 -0
  32. package/dist/esm/cache/CacheDecisionSubflow.js.map +1 -0
  33. package/dist/esm/cache/CacheGateDecider.js +116 -0
  34. package/dist/esm/cache/CacheGateDecider.js.map +1 -0
  35. package/dist/esm/cache/applyCachePolicy.js +50 -0
  36. package/dist/esm/cache/applyCachePolicy.js.map +1 -0
  37. package/dist/esm/cache/cacheRecorder.js +116 -0
  38. package/dist/esm/cache/cacheRecorder.js.map +1 -0
  39. package/dist/esm/cache/index.js +36 -0
  40. package/dist/esm/cache/index.js.map +1 -0
  41. package/dist/esm/cache/strategies/AnthropicCacheStrategy.js +98 -0
  42. package/dist/esm/cache/strategies/AnthropicCacheStrategy.js.map +1 -0
  43. package/dist/esm/cache/strategies/BedrockCacheStrategy.js +77 -0
  44. package/dist/esm/cache/strategies/BedrockCacheStrategy.js.map +1 -0
  45. package/dist/esm/cache/strategies/NoOpCacheStrategy.js +36 -0
  46. package/dist/esm/cache/strategies/NoOpCacheStrategy.js.map +1 -0
  47. package/dist/esm/cache/strategies/OpenAICacheStrategy.js +71 -0
  48. package/dist/esm/cache/strategies/OpenAICacheStrategy.js.map +1 -0
  49. package/dist/esm/cache/strategyRegistry.js +73 -0
  50. package/dist/esm/cache/strategyRegistry.js.map +1 -0
  51. package/dist/esm/cache/types.js +24 -0
  52. package/dist/esm/cache/types.js.map +1 -0
  53. package/dist/esm/conventions.js +18 -0
  54. package/dist/esm/conventions.js.map +1 -1
  55. package/dist/esm/core/Agent.js +157 -4
  56. package/dist/esm/core/Agent.js.map +1 -1
  57. package/dist/esm/index.js +9 -0
  58. package/dist/esm/index.js.map +1 -1
  59. package/dist/esm/lib/injection-engine/factories/defineFact.js +3 -0
  60. package/dist/esm/lib/injection-engine/factories/defineFact.js.map +1 -1
  61. package/dist/esm/lib/injection-engine/factories/defineInstruction.js +3 -0
  62. package/dist/esm/lib/injection-engine/factories/defineInstruction.js.map +1 -1
  63. package/dist/esm/lib/injection-engine/factories/defineSkill.js +5 -0
  64. package/dist/esm/lib/injection-engine/factories/defineSkill.js.map +1 -1
  65. package/dist/esm/lib/injection-engine/factories/defineSteering.js +3 -0
  66. package/dist/esm/lib/injection-engine/factories/defineSteering.js.map +1 -1
  67. package/dist/index.js +9 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/lib/injection-engine/factories/defineFact.js +3 -0
  70. package/dist/lib/injection-engine/factories/defineFact.js.map +1 -1
  71. package/dist/lib/injection-engine/factories/defineInstruction.js +3 -0
  72. package/dist/lib/injection-engine/factories/defineInstruction.js.map +1 -1
  73. package/dist/lib/injection-engine/factories/defineSkill.js +5 -0
  74. package/dist/lib/injection-engine/factories/defineSkill.js.map +1 -1
  75. package/dist/lib/injection-engine/factories/defineSteering.js +3 -0
  76. package/dist/lib/injection-engine/factories/defineSteering.js.map +1 -1
  77. package/dist/types/adapters/types.d.ts +11 -0
  78. package/dist/types/adapters/types.d.ts.map +1 -1
  79. package/dist/types/cache/CacheDecisionSubflow.d.ts +89 -0
  80. package/dist/types/cache/CacheDecisionSubflow.d.ts.map +1 -0
  81. package/dist/types/cache/CacheGateDecider.d.ts +114 -0
  82. package/dist/types/cache/CacheGateDecider.d.ts.map +1 -0
  83. package/dist/types/cache/applyCachePolicy.d.ts +38 -0
  84. package/dist/types/cache/applyCachePolicy.d.ts.map +1 -0
  85. package/dist/types/cache/cacheRecorder.d.ts +86 -0
  86. package/dist/types/cache/cacheRecorder.d.ts.map +1 -0
  87. package/dist/types/cache/index.d.ts +34 -0
  88. package/dist/types/cache/index.d.ts.map +1 -0
  89. package/dist/types/cache/strategies/AnthropicCacheStrategy.d.ts +39 -0
  90. package/dist/types/cache/strategies/AnthropicCacheStrategy.d.ts.map +1 -0
  91. package/dist/types/cache/strategies/BedrockCacheStrategy.d.ts +34 -0
  92. package/dist/types/cache/strategies/BedrockCacheStrategy.d.ts.map +1 -0
  93. package/dist/types/cache/strategies/NoOpCacheStrategy.d.ts +30 -0
  94. package/dist/types/cache/strategies/NoOpCacheStrategy.d.ts.map +1 -0
  95. package/dist/types/cache/strategies/OpenAICacheStrategy.d.ts +37 -0
  96. package/dist/types/cache/strategies/OpenAICacheStrategy.d.ts.map +1 -0
  97. package/dist/types/cache/strategyRegistry.d.ts +46 -0
  98. package/dist/types/cache/strategyRegistry.d.ts.map +1 -0
  99. package/dist/types/cache/types.d.ts +244 -0
  100. package/dist/types/cache/types.d.ts.map +1 -0
  101. package/dist/types/conventions.d.ts +18 -0
  102. package/dist/types/conventions.d.ts.map +1 -1
  103. package/dist/types/core/Agent.d.ts +86 -2
  104. package/dist/types/core/Agent.d.ts.map +1 -1
  105. package/dist/types/index.d.ts +3 -0
  106. package/dist/types/index.d.ts.map +1 -1
  107. package/dist/types/lib/injection-engine/factories/defineFact.d.ts +9 -0
  108. package/dist/types/lib/injection-engine/factories/defineFact.d.ts.map +1 -1
  109. package/dist/types/lib/injection-engine/factories/defineInstruction.d.ts +11 -0
  110. package/dist/types/lib/injection-engine/factories/defineInstruction.d.ts.map +1 -1
  111. package/dist/types/lib/injection-engine/factories/defineSkill.d.ts +15 -0
  112. package/dist/types/lib/injection-engine/factories/defineSkill.d.ts.map +1 -1
  113. package/dist/types/lib/injection-engine/factories/defineSteering.d.ts +12 -0
  114. package/dist/types/lib/injection-engine/factories/defineSteering.d.ts.map +1 -1
  115. package/package.json +1 -1
@@ -0,0 +1,77 @@
1
+ /**
2
+ * BedrockCacheStrategy — model-aware strategy for AWS Bedrock.
3
+ *
4
+ * Bedrock hosts multiple model families. Cache support varies:
5
+ * - Claude on Bedrock → identical mechanics to direct Anthropic
6
+ * (`cache_control: { type: 'ephemeral' }` markers, 4-marker
7
+ * limit). Strategy delegates to Anthropic-shaped behavior.
8
+ * - Llama / Mistral / Cohere on Bedrock → no cache support today
9
+ * (as of 2026-04-30). Strategy passes through, returns no metrics.
10
+ *
11
+ * Auto-detection: inspects `req.model` to decide. Claude model IDs
12
+ * start with `'anthropic.claude'` on Bedrock (e.g.,
13
+ * `anthropic.claude-3-5-sonnet-20240620-v1:0`).
14
+ *
15
+ * Auto-registers under provider name `'bedrock'`.
16
+ *
17
+ * Per the Phase 1 review (Reviewer 6 — Provider SDK expert): for
18
+ * non-Claude Bedrock models the strategy reports `enabled: false` in
19
+ * its capabilities so the CacheDecision subflow can short-circuit
20
+ * marker emission (potential v2.7 optimization). Today markers still
21
+ * emit and we drop them silently in prepareRequest.
22
+ */
23
+ import { registerCacheStrategy } from '../strategyRegistry.js';
24
+ /** Match Bedrock-Claude model ids: `anthropic.claude-...` */
25
+ const BEDROCK_CLAUDE_RE = /^anthropic\.claude/i;
26
+ /** Anthropic's 4-marker limit applies to Bedrock-Claude too. */
27
+ const BEDROCK_MAX_MARKERS = 4;
28
+ const BEDROCK_CAPABILITIES = Object.freeze({
29
+ // We say `enabled: true` at the capability level because Bedrock-
30
+ // Claude DOES support caching. Bedrock-Llama/Mistral land in the
31
+ // model-aware code path inside prepareRequest (no markers applied).
32
+ enabled: true,
33
+ maxMarkers: BEDROCK_MAX_MARKERS,
34
+ ttls: ['short', 'long'],
35
+ fields: ['system', 'tools', 'messages'],
36
+ automatic: false,
37
+ });
38
+ export class BedrockCacheStrategy {
39
+ providerName = 'bedrock';
40
+ capabilities = BEDROCK_CAPABILITIES;
41
+ async prepareRequest(req, candidates, ctx) {
42
+ if (ctx.cachingDisabled || candidates.length === 0) {
43
+ return { request: req, markersApplied: [] };
44
+ }
45
+ // Model-aware: only Claude on Bedrock supports cache_control.
46
+ // Other model families silently drop the markers.
47
+ if (!BEDROCK_CLAUDE_RE.test(req.model)) {
48
+ return { request: req, markersApplied: [] };
49
+ }
50
+ const markersApplied = candidates.length <= BEDROCK_MAX_MARKERS
51
+ ? candidates
52
+ : candidates.slice(0, BEDROCK_MAX_MARKERS);
53
+ return {
54
+ request: { ...req, cacheMarkers: markersApplied },
55
+ markersApplied,
56
+ };
57
+ }
58
+ extractMetrics(usage) {
59
+ // Bedrock returns the SAME usage shape as Anthropic for Claude
60
+ // models — same cache_creation_input_tokens / cache_read_input_tokens
61
+ // fields. Reuse identical extraction.
62
+ if (!usage || typeof usage !== 'object')
63
+ return undefined;
64
+ const u = usage;
65
+ const cacheRead = u.cache_read_input_tokens ?? 0;
66
+ const cacheWrite = u.cache_creation_input_tokens ?? 0;
67
+ if (cacheRead === 0 && cacheWrite === 0)
68
+ return undefined;
69
+ return {
70
+ cacheReadTokens: cacheRead,
71
+ cacheWriteTokens: cacheWrite,
72
+ freshInputTokens: u.input_tokens ?? 0,
73
+ };
74
+ }
75
+ }
76
+ registerCacheStrategy(new BedrockCacheStrategy());
77
+ //# sourceMappingURL=BedrockCacheStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BedrockCacheStrategy.js","sourceRoot":"","sources":["../../../../src/cache/strategies/BedrockCacheStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAUH,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,6DAA6D;AAC7D,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEhD,gEAAgE;AAChE,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,MAAM,oBAAoB,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC5D,kEAAkE;IAClE,iEAAiE;IACjE,oEAAoE;IACpE,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,mBAAmB;IAC/B,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,CAAkC;IACxD,MAAM,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAiD;IACvF,SAAS,EAAE,KAAK;CACjB,CAAC,CAAC;AAEH,MAAM,OAAO,oBAAoB;IACtB,YAAY,GAAG,SAAS,CAAC;IACzB,YAAY,GAAG,oBAAoB,CAAC;IAE7C,KAAK,CAAC,cAAc,CAClB,GAAe,EACf,UAAkC,EAClC,GAAyB;QAKzB,IAAI,GAAG,CAAC,eAAe,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;QAC9C,CAAC;QACD,8DAA8D;QAC9D,kDAAkD;QAClD,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACvC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;QAC9C,CAAC;QACD,MAAM,cAAc,GAClB,UAAU,CAAC,MAAM,IAAI,mBAAmB;YACtC,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;QAC/C,OAAO;YACL,OAAO,EAAE,EAAE,GAAG,GAAG,EAAE,YAAY,EAAE,cAAc,EAAE;YACjD,cAAc;SACf,CAAC;IACJ,CAAC;IAED,cAAc,CAAC,KAAc;QAC3B,+DAA+D;QAC/D,sEAAsE;QACtE,sCAAsC;QACtC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,CAAC,GAAG,KAIT,CAAC;QACF,MAAM,SAAS,GAAG,CAAC,CAAC,uBAAuB,IAAI,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,CAAC,CAAC,2BAA2B,IAAI,CAAC,CAAC;QACtD,IAAI,SAAS,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAC1D,OAAO;YACL,eAAe,EAAE,SAAS;YAC1B,gBAAgB,EAAE,UAAU;YAC5B,gBAAgB,EAAE,CAAC,CAAC,YAAY,IAAI,CAAC;SACtC,CAAC;IACJ,CAAC;CACF;AAED,qBAAqB,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * NoOpCacheStrategy — fallback strategy for providers without cache
3
+ * support (Mock, unknown providers, intentional opt-out).
4
+ *
5
+ * Returns the request unchanged; reports no metrics. The
6
+ * `capabilities.enabled` flag is `false` so the CacheDecision subflow
7
+ * could choose to skip emitting markers entirely (potential v2.7
8
+ * optimization), though current Phase 4+5 always emit markers and
9
+ * let the strategy decide what to do with them.
10
+ *
11
+ * Always-available default. Registered against the special wildcard
12
+ * `'*'` so any unrecognized provider name falls back to NoOp.
13
+ */
14
+ const NOOP_CAPABILITIES = Object.freeze({
15
+ enabled: false,
16
+ maxMarkers: 0,
17
+ ttls: [],
18
+ fields: [],
19
+ automatic: false,
20
+ });
21
+ export class NoOpCacheStrategy {
22
+ /**
23
+ * Wildcard provider name. The strategy registry treats this as the
24
+ * fallback for any provider that doesn't have a specific strategy
25
+ * registered.
26
+ */
27
+ providerName = '*';
28
+ capabilities = NOOP_CAPABILITIES;
29
+ async prepareRequest(req, _candidates, _ctx) {
30
+ return { request: req, markersApplied: [] };
31
+ }
32
+ extractMetrics(_usage) {
33
+ return undefined;
34
+ }
35
+ }
36
+ //# sourceMappingURL=NoOpCacheStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoOpCacheStrategy.js","sourceRoot":"","sources":["../../../../src/cache/strategies/NoOpCacheStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAWH,MAAM,iBAAiB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACzD,OAAO,EAAE,KAAK;IACd,UAAU,EAAE,CAAC;IACb,IAAI,EAAE,EAAmC;IACzC,MAAM,EAAE,EAAkD;IAC1D,SAAS,EAAE,KAAK;CACjB,CAAC,CAAC;AAEH,MAAM,OAAO,iBAAiB;IAC5B;;;;OAIG;IACM,YAAY,GAAG,GAAG,CAAC;IACnB,YAAY,GAAG,iBAAiB,CAAC;IAE1C,KAAK,CAAC,cAAc,CAClB,GAAe,EACf,WAAmC,EACnC,IAA0B;QAK1B,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;IAC9C,CAAC;IAED,cAAc,CAAC,MAAe;QAC5B,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * OpenAICacheStrategy — metrics-only strategy for OpenAI providers.
3
+ *
4
+ * OpenAI auto-caches request prefixes ≥1024 tokens at 50% off.
5
+ * No client-side opt-in markers needed (and no way to influence
6
+ * cache behavior from the client). The strategy:
7
+ *
8
+ * - **prepareRequest**: pass-through. We can't tell OpenAI what
9
+ * to cache; they decide automatically. Markers are silently
10
+ * dropped (the 80% case for OpenAI consumers is "I declared
11
+ * cache: 'always' for my injections" — that's still meaningful
12
+ * because (a) it's portable across providers, (b) for OpenAI
13
+ * the auto-cache may still hit on stable prefixes regardless).
14
+ * - **extractMetrics**: reads `prompt_tokens_details.cached_tokens`
15
+ * from OpenAI's usage response so cacheRecorder can surface
16
+ * hit rates / dollar savings.
17
+ *
18
+ * Auto-registers on module import for: 'openai', 'browser-openai'.
19
+ *
20
+ * Documentation note for consumers (Phase 12 docs): the `cache:`
21
+ * directive on injection definitions is portable but has NO LOCAL
22
+ * EFFECT on OpenAI runs — the provider auto-caches based on prefix
23
+ * length. The directive still ships correctly with the agent and
24
+ * lights up automatically when you swap to Anthropic / Bedrock.
25
+ */
26
+ import { registerCacheStrategy } from '../strategyRegistry.js';
27
+ const OPENAI_CAPABILITIES = Object.freeze({
28
+ // `enabled: true` because metrics ARE extracted (cacheRecorder shows
29
+ // hit rates). The `automatic: true` flag tells consumers the markers
30
+ // are inert here — OpenAI decides what to cache, not us.
31
+ enabled: true,
32
+ maxMarkers: 0,
33
+ ttls: ['short'],
34
+ fields: [],
35
+ automatic: true,
36
+ });
37
+ export class OpenAICacheStrategy {
38
+ providerName = 'openai';
39
+ capabilities = OPENAI_CAPABILITIES;
40
+ async prepareRequest(req, _candidates, _ctx) {
41
+ // Pass-through. OpenAI auto-caches; no opt-in needed.
42
+ return { request: req, markersApplied: [] };
43
+ }
44
+ extractMetrics(usage) {
45
+ if (!usage || typeof usage !== 'object')
46
+ return undefined;
47
+ const u = usage;
48
+ const cached = u.prompt_tokens_details?.cached_tokens ?? 0;
49
+ if (cached === 0)
50
+ return undefined;
51
+ const totalPrompt = u.prompt_tokens ?? cached;
52
+ return {
53
+ cacheReadTokens: cached,
54
+ cacheWriteTokens: 0, // OpenAI doesn't charge a write premium
55
+ freshInputTokens: Math.max(0, totalPrompt - cached),
56
+ };
57
+ }
58
+ }
59
+ // Auto-register for both server-side and browser variants.
60
+ {
61
+ const strategy = new OpenAICacheStrategy();
62
+ registerCacheStrategy(strategy);
63
+ const browserStrategy = {
64
+ providerName: 'browser-openai',
65
+ capabilities: strategy.capabilities,
66
+ prepareRequest: strategy.prepareRequest.bind(strategy),
67
+ extractMetrics: strategy.extractMetrics.bind(strategy),
68
+ };
69
+ registerCacheStrategy(browserStrategy);
70
+ }
71
+ //# sourceMappingURL=OpenAICacheStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenAICacheStrategy.js","sourceRoot":"","sources":["../../../../src/cache/strategies/OpenAICacheStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAUH,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,mBAAmB,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC3D,qEAAqE;IACrE,qEAAqE;IACrE,yDAAyD;IACzD,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,CAAC;IACb,IAAI,EAAE,CAAC,OAAO,CAAkC;IAChD,MAAM,EAAE,EAAkD;IAC1D,SAAS,EAAE,IAAI;CAChB,CAAC,CAAC;AAEH,MAAM,OAAO,mBAAmB;IACrB,YAAY,GAAG,QAAQ,CAAC;IACxB,YAAY,GAAG,mBAAmB,CAAC;IAE5C,KAAK,CAAC,cAAc,CAClB,GAAe,EACf,WAAmC,EACnC,IAA0B;QAK1B,sDAAsD;QACtD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;IAC9C,CAAC;IAED,cAAc,CAAC,KAAc;QAC3B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,CAAC,GAAG,KAGT,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,CAAC,qBAAqB,EAAE,aAAa,IAAI,CAAC,CAAC;QAC3D,IAAI,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACnC,MAAM,WAAW,GAAG,CAAC,CAAC,aAAa,IAAI,MAAM,CAAC;QAC9C,OAAO;YACL,eAAe,EAAE,MAAM;YACvB,gBAAgB,EAAE,CAAC,EAAE,wCAAwC;YAC7D,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;SACpD,CAAC;IACJ,CAAC;CACF;AAED,2DAA2D;AAC3D,CAAC;IACC,MAAM,QAAQ,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAC3C,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,eAAe,GAAkB;QACrC,YAAY,EAAE,gBAAgB;QAC9B,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;QACtD,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;KACvD,CAAC;IACF,qBAAqB,CAAC,eAAe,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Strategy registry — maps provider name → CacheStrategy.
3
+ *
4
+ * Auto-resolution at agent build time: agentfootprint inspects
5
+ * `provider.name` and looks up the registered strategy for that
6
+ * name. Falls back to `NoOpCacheStrategy` (registered under wildcard
7
+ * `'*'`) when the provider isn't recognized.
8
+ *
9
+ * Phases shipping registered strategies:
10
+ * - v2.6 Phase 6 (this phase): NoOp
11
+ * - v2.6 Phase 7: AnthropicCacheStrategy ('anthropic',
12
+ * 'browser-anthropic')
13
+ * - v2.6 Phase 8: OpenAICacheStrategy ('openai', 'browser-openai'),
14
+ * BedrockCacheStrategy ('bedrock')
15
+ * - v2.7+ : GeminiCacheStrategy (handle-based, async, deferred)
16
+ *
17
+ * Consumers can register their own strategy via
18
+ * `registerCacheStrategy(strategy)`. Useful for in-house LLM proxies
19
+ * or test mocks.
20
+ */
21
+ import { NoOpCacheStrategy } from './strategies/NoOpCacheStrategy.js';
22
+ /**
23
+ * Registry singleton. Populated by individual strategy modules
24
+ * importing this and calling `registerCacheStrategy` at module load
25
+ * time, OR by the consumer at agent build time.
26
+ *
27
+ * Contains the wildcard `'*'` → NoOp entry by default; never empty.
28
+ */
29
+ const REGISTRY = new Map([['*', new NoOpCacheStrategy()]]);
30
+ /**
31
+ * Look up a CacheStrategy by provider name. Falls back to the
32
+ * wildcard NoOp strategy if no match.
33
+ *
34
+ * Lookup is case-insensitive on the provider name.
35
+ */
36
+ export function getDefaultCacheStrategy(providerName) {
37
+ const exact = REGISTRY.get(providerName);
38
+ if (exact !== undefined)
39
+ return exact;
40
+ const lower = providerName.toLowerCase();
41
+ if (lower !== providerName) {
42
+ const lowercased = REGISTRY.get(lower);
43
+ if (lowercased !== undefined)
44
+ return lowercased;
45
+ }
46
+ // Fallback wildcard always present
47
+ return REGISTRY.get('*');
48
+ }
49
+ /**
50
+ * Register (or replace) a strategy for a provider name. Called by
51
+ * strategy modules (v2.6 Phase 7+) at module load OR by consumers
52
+ * needing a custom backend. Replacing an existing strategy is allowed
53
+ * — the most-recent registration wins.
54
+ */
55
+ export function registerCacheStrategy(strategy) {
56
+ REGISTRY.set(strategy.providerName, strategy);
57
+ }
58
+ /**
59
+ * Read-only view of registered strategy names. Useful for diagnostics
60
+ * (e.g., logging "we have strategies for: anthropic, openai, *").
61
+ */
62
+ export function listRegisteredStrategies() {
63
+ return [...REGISTRY.keys()];
64
+ }
65
+ /**
66
+ * Internal helper for tests: reset the registry to the default
67
+ * (wildcard → NoOp only). Not exported from the public barrel.
68
+ */
69
+ export function _resetRegistryForTests() {
70
+ REGISTRY.clear();
71
+ REGISTRY.set('*', new NoOpCacheStrategy());
72
+ }
73
+ //# sourceMappingURL=strategyRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategyRegistry.js","sourceRoot":"","sources":["../../../src/cache/strategyRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAwB,CAAC,CAAC,GAAG,EAAE,IAAI,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;AAElF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,YAAoB;IAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IACzC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,KAAK,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;IACzC,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;QAC3B,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,UAAU,CAAC;IAClD,CAAC;IACD,mCAAmC;IACnC,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAuB;IAC3D,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB;IACpC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACjB,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,iBAAiB,EAAE,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Cache layer — public types.
3
+ *
4
+ * Three layers, each with one responsibility:
5
+ *
6
+ * 1. CONSUMER DSL — `CachePolicy` field on every injection factory.
7
+ * Declarative, like GraphQL schema input. Says WHAT should be
8
+ * cacheable. Examples: `cache: 'always'`, `cache: 'while-active'`.
9
+ *
10
+ * 2. AGNOSTIC MARKERS — `CacheMarker[]` produced by the
11
+ * `CacheDecision` subflow at runtime. Provider-independent
12
+ * identification of "cacheable prefix in field X up to index Y".
13
+ *
14
+ * 3. PROVIDER STRATEGY — one `CacheStrategy` implementation per
15
+ * provider (Anthropic / OpenAI / Bedrock / NoOp). Translates
16
+ * agnostic markers to provider-specific wire format AND extracts
17
+ * cache metrics from the provider's response.
18
+ *
19
+ * The interfaces are read-only / immutable by convention. Strategies
20
+ * MUST be stateless across runs; per-run state lives in the
21
+ * `CacheStrategyContext` passed into `prepareRequest`.
22
+ */
23
+ export {};
24
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/cache/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
@@ -28,6 +28,9 @@ export const SUBFLOW_IDS = {
28
28
  MERGE: 'sf-merge',
29
29
  /** Final-answer composition inside Agent. */
30
30
  FINAL: 'sf-final',
31
+ /** Cache decision subflow (v2.6). Walks activeInjections, emits
32
+ * agnostic CacheMarker[]. Provider-independent. */
33
+ CACHE_DECISION: 'sf-cache-decision',
31
34
  };
32
35
  /** Stage IDs — plain function stages that builders mount. */
33
36
  export const STAGE_IDS = {
@@ -37,6 +40,21 @@ export const STAGE_IDS = {
37
40
  FORMAT_MERGE: 'format-merge',
38
41
  MERGE_LLM: 'merge-llm',
39
42
  EXTRACT_MERGE: 'extract-merge',
43
+ /** Updates the rolling skill-history window before CacheGate
44
+ * evaluates skill-churn (v2.6). */
45
+ UPDATE_SKILL_HISTORY: 'update-skill-history',
46
+ /** CacheGate decider stage — routes to apply-markers / no-markers
47
+ * based on kill switch / hit rate / skill churn (v2.6). */
48
+ CACHE_GATE: 'cache-gate',
49
+ /** CacheGate branch (routing key) when markers SHOULD be applied
50
+ * this iteration. Pass-through stage; markers stay in scope. (v2.6) */
51
+ APPLY_MARKERS: 'apply-markers',
52
+ /** CacheGate branch (routing key) when markers should be SKIPPED
53
+ * this iteration. Stage clears scope.cacheMarkers. (v2.6) */
54
+ SKIP_CACHING: 'no-markers',
55
+ /** BuildLLMRequest stage — calls strategy.prepareRequest to apply
56
+ * markers to the wire request (v2.6). */
57
+ BUILD_LLM_REQUEST: 'build-llm-request',
40
58
  };
41
59
  // ─── Type guards ─────────────────────────────────────────────────────
42
60
  /** True when a subflow id corresponds to one of the 3 context slots. */
@@ -1 +1 @@
1
- {"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../src/conventions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB;0EACsE;IACtE,gBAAgB,EAAE,qBAAqB;IACvC,+DAA+D;IAC/D,aAAa,EAAE,kBAAkB;IACjC,6BAA6B;IAC7B,QAAQ,EAAE,aAAa;IACvB,0BAA0B;IAC1B,KAAK,EAAE,UAAU;IACjB,2CAA2C;IAC3C,KAAK,EAAE,UAAU;IACjB,uDAAuD;IACvD,UAAU,EAAE,eAAe;IAC3B,kCAAkC;IAClC,KAAK,EAAE,UAAU;IACjB,6CAA6C;IAC7C,KAAK,EAAE,UAAU;CACT,CAAC;AAIX,6DAA6D;AAC7D,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,OAAO;IACd,YAAY,EAAE,cAAc;IAC5B,SAAS,EAAE,WAAW;IACtB,aAAa,EAAE,eAAe;CACtB,CAAC;AAIX,wEAAwE;AAExE,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAC3B,EAAU;IAEV,OAAO,CACL,EAAE,KAAK,WAAW,CAAC,aAAa,IAAI,EAAE,KAAK,WAAW,CAAC,QAAQ,IAAI,EAAE,KAAK,WAAW,CAAC,KAAK,CAC5F,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,iEAAiE;IACjE,mEAAmE;IACnE,kEAAkE;IAClE,6CAA6C;IAC7C,MAAM,WAAW,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,QAAQ,WAAW,EAAE,CAAC;QACpB,KAAK,WAAW,CAAC,aAAa;YAC5B,OAAO,eAAe,CAAC;QACzB,KAAK,WAAW,CAAC,QAAQ;YACvB,OAAO,UAAU,CAAC;QACpB,KAAK,WAAW,CAAC,KAAK;YACpB,OAAO,OAAO,CAAC;QACjB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,OAAQ,MAAM,CAAC,MAAM,CAAC,WAAW,CAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,OAAQ,MAAM,CAAC,MAAM,CAAC,SAAS,CAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,aAAa,EAAE,wBAAwB;IACvC,QAAQ,EAAE,oBAAoB;IAC9B,KAAK,EAAE,iBAAiB;CAChB,CAAC;AAIX,6CAA6C;AAC7C,MAAM,UAAU,mBAAmB,CAAC,IAA4C;IAC9E,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,eAAe;YAClB,OAAO,cAAc,CAAC,aAAa,CAAC;QACtC,KAAK,UAAU;YACb,OAAO,cAAc,CAAC,QAAQ,CAAC;QACjC,KAAK,OAAO;YACV,OAAO,cAAc,CAAC,KAAK,CAAC;IAChC,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAQ,MAAM,CAAC,MAAM,CAAC,cAAc,CAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACnE,CAAC"}
1
+ {"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../src/conventions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB;0EACsE;IACtE,gBAAgB,EAAE,qBAAqB;IACvC,+DAA+D;IAC/D,aAAa,EAAE,kBAAkB;IACjC,6BAA6B;IAC7B,QAAQ,EAAE,aAAa;IACvB,0BAA0B;IAC1B,KAAK,EAAE,UAAU;IACjB,2CAA2C;IAC3C,KAAK,EAAE,UAAU;IACjB,uDAAuD;IACvD,UAAU,EAAE,eAAe;IAC3B,kCAAkC;IAClC,KAAK,EAAE,UAAU;IACjB,6CAA6C;IAC7C,KAAK,EAAE,UAAU;IACjB;wDACoD;IACpD,cAAc,EAAE,mBAAmB;CAC3B,CAAC;AAIX,6DAA6D;AAC7D,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,OAAO;IACd,YAAY,EAAE,cAAc;IAC5B,SAAS,EAAE,WAAW;IACtB,aAAa,EAAE,eAAe;IAC9B;wCACoC;IACpC,oBAAoB,EAAE,sBAAsB;IAC5C;gEAC4D;IAC5D,UAAU,EAAE,YAAY;IACxB;4EACwE;IACxE,aAAa,EAAE,eAAe;IAC9B;kEAC8D;IAC9D,YAAY,EAAE,YAAY;IAC1B;8CAC0C;IAC1C,iBAAiB,EAAE,mBAAmB;CAC9B,CAAC;AAIX,wEAAwE;AAExE,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAC3B,EAAU;IAEV,OAAO,CACL,EAAE,KAAK,WAAW,CAAC,aAAa,IAAI,EAAE,KAAK,WAAW,CAAC,QAAQ,IAAI,EAAE,KAAK,WAAW,CAAC,KAAK,CAC5F,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,iEAAiE;IACjE,mEAAmE;IACnE,kEAAkE;IAClE,6CAA6C;IAC7C,MAAM,WAAW,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,QAAQ,WAAW,EAAE,CAAC;QACpB,KAAK,WAAW,CAAC,aAAa;YAC5B,OAAO,eAAe,CAAC;QACzB,KAAK,WAAW,CAAC,QAAQ;YACvB,OAAO,UAAU,CAAC;QACpB,KAAK,WAAW,CAAC,KAAK;YACpB,OAAO,OAAO,CAAC;QACjB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,OAAQ,MAAM,CAAC,MAAM,CAAC,WAAW,CAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,OAAQ,MAAM,CAAC,MAAM,CAAC,SAAS,CAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,aAAa,EAAE,wBAAwB;IACvC,QAAQ,EAAE,oBAAoB;IAC9B,KAAK,EAAE,iBAAiB;CAChB,CAAC;AAIX,6CAA6C;AAC7C,MAAM,UAAU,mBAAmB,CAAC,IAA4C;IAC9E,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,eAAe;YAClB,OAAO,cAAc,CAAC,aAAa,CAAC;QACtC,KAAK,UAAU;YACb,OAAO,cAAc,CAAC,QAAQ,CAAC;QACjC,KAAK,OAAO;YACV,OAAO,cAAc,CAAC,KAAK,CAAC;IAChC,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAQ,MAAM,CAAC,MAAM,CAAC,cAAc,CAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACnE,CAAC"}
@@ -21,6 +21,9 @@ import { FlowChartExecutor, flowChart, } from 'footprintjs';
21
21
  // with it (default behavior re-introduces duplicate tool names that
22
22
  // LLM providers reject).
23
23
  import { ArrayMergeMode } from 'footprintjs/advanced';
24
+ import { cacheDecisionSubflow } from '../cache/CacheDecisionSubflow.js';
25
+ import { cacheGateDecide, updateSkillHistory as updateSkillHistoryStage, } from '../cache/CacheGateDecider.js';
26
+ import { getDefaultCacheStrategy } from '../cache/strategyRegistry.js';
24
27
  import { isPauseRequest } from './pause.js';
25
28
  import { emitCostTick } from './cost.js';
26
29
  import { STAGE_IDS, SUBFLOW_IDS } from '../conventions.js';
@@ -55,6 +58,27 @@ export class Agent extends RunnerBase {
55
58
  maxTokens;
56
59
  maxIterations;
57
60
  systemPromptValue;
61
+ /**
62
+ * Cache policy for the base system prompt (set via
63
+ * `.system(text, { cache })`). Default `'always'` — base prompt is
64
+ * stable per-turn, ideal cache anchor. CacheDecision subflow reads
65
+ * this when computing the SystemPrompt slot's cache markers.
66
+ */
67
+ systemPromptCachePolicy;
68
+ /**
69
+ * Global cache kill switch from `Agent.create({ caching: 'off' })`.
70
+ * Threaded into agent scope at seed-time as `scope.cachingDisabled`;
71
+ * read by the CacheGate decider every iteration (highest-priority rule).
72
+ */
73
+ cachingDisabledValue;
74
+ /**
75
+ * Provider-specific CacheStrategy. Auto-resolved from
76
+ * `getDefaultCacheStrategy(provider.name)` at agent build time
77
+ * unless the consumer explicitly passes one via builder option.
78
+ * Phase 7+ implementations (Anthropic, OpenAI, Bedrock) register
79
+ * themselves in the strategyRegistry on import.
80
+ */
81
+ cacheStrategy;
58
82
  registry;
59
83
  /**
60
84
  * The Injection list — Skills, Steering, Instructions, Facts (and
@@ -119,7 +143,7 @@ export class Agent extends RunnerBase {
119
143
  * dispatch correctly when their visible-set changes mid-turn.
120
144
  */
121
145
  externalToolProvider;
122
- constructor(opts, systemPromptValue, registry, voice, injections = [], memories = [], outputSchemaParser, toolProvider) {
146
+ constructor(opts, systemPromptValue, registry, voice, injections = [], memories = [], outputSchemaParser, toolProvider, systemPromptCachePolicy = 'always', cachingDisabled = false, cacheStrategy) {
123
147
  super();
124
148
  this.provider = opts.provider;
125
149
  this.name = opts.name ?? 'Agent';
@@ -129,6 +153,11 @@ export class Agent extends RunnerBase {
129
153
  this.maxTokens = opts.maxTokens;
130
154
  this.maxIterations = clampIterations(opts.maxIterations ?? 10);
131
155
  this.systemPromptValue = systemPromptValue;
156
+ this.systemPromptCachePolicy = systemPromptCachePolicy;
157
+ this.cachingDisabledValue = cachingDisabled;
158
+ // Auto-resolve strategy from provider.name unless caller overrides.
159
+ // NoOp is the wildcard fallback so unknown providers stay safe.
160
+ this.cacheStrategy = cacheStrategy ?? getDefaultCacheStrategy(opts.provider.name);
132
161
  this.registry = registry;
133
162
  this.injections = injections;
134
163
  this.memories = memories;
@@ -158,6 +187,15 @@ export class Agent extends RunnerBase {
158
187
  toFlowChart() {
159
188
  return this.buildChart();
160
189
  }
190
+ /**
191
+ * Cache policy for the base system prompt. Read by the CacheDecision
192
+ * subflow (v2.6 Phase 4) to know how to treat the SystemPrompt slot's
193
+ * cache markers. Exposed as a method (not direct field access) so
194
+ * the Agent's encapsulation boundary stays clean.
195
+ */
196
+ getSystemPromptCachePolicy() {
197
+ return this.systemPromptCachePolicy;
198
+ }
161
199
  /**
162
200
  * The footprintjs `RuntimeSnapshot` from the most recent `run()` /
163
201
  * `resume()`. Feeds Lens's Trace tab (ExplainableShell `runtimeSnapshot`
@@ -310,6 +348,15 @@ export class Agent extends RunnerBase {
310
348
  const pricingTable = this.pricingTable;
311
349
  const costBudget = this.costBudget;
312
350
  const permissionChecker = this.permissionChecker;
351
+ // Cache layer (v2.6) — capture for the seed + chart-build closures.
352
+ // `systemPromptCachePolicy` is fed into the CacheDecision subflow's
353
+ // inputMapper. `cacheStrategy` is consulted by BuildLLMRequest at
354
+ // run-time (Phase 7+ for the actual prepareRequest call). For
355
+ // Phase 6b the chart mounts the stages but BuildLLMRequest is a
356
+ // pass-through; Phase 7 lights up the strategy call.
357
+ const systemPromptCachePolicy = this.systemPromptCachePolicy;
358
+ const cachingDisabled = this.cachingDisabledValue;
359
+ const cacheStrategy = this.cacheStrategy;
313
360
  const seed = (scope) => {
314
361
  const args = scope.$getArgs();
315
362
  scope.userMessage = args.message;
@@ -347,6 +394,17 @@ export class Agent extends RunnerBase {
347
394
  scope.activeInjections = [];
348
395
  scope.activatedInjectionIds = [];
349
396
  scope.dynamicToolSchemas = toolSchemas;
397
+ // Cache layer state (v2.6) — initialized to inert defaults.
398
+ // CacheDecision subflow populates `cacheMarkers` per iteration;
399
+ // UpdateSkillHistory + CacheGate consume `cachingDisabled`,
400
+ // `recentHitRate`, `skillHistory`. Empty defaults mean the
401
+ // CacheGate falls through to 'apply-markers' on iter 1 (no
402
+ // history yet → no churn detected; recentHitRate undefined →
403
+ // hit-rate floor doesn't fire).
404
+ scope.cacheMarkers = [];
405
+ scope.cachingDisabled = cachingDisabled;
406
+ scope.recentHitRate = undefined;
407
+ scope.skillHistory = [];
350
408
  typedEmit(scope, 'agentfootprint.agent.turn_start', {
351
409
  turnIndex: 0,
352
410
  userPrompt: args.message,
@@ -501,7 +559,7 @@ export class Agent extends RunnerBase {
501
559
  // Falls back to the static schemas at startup before the tools
502
560
  // slot has run for the first time.
503
561
  const activeToolSchemas = scope.dynamicToolSchemas ?? toolSchemas;
504
- const llmRequest = {
562
+ const baseRequest = {
505
563
  ...(systemPrompt.length > 0 && { systemPrompt }),
506
564
  messages,
507
565
  ...(activeToolSchemas.length > 0 && { tools: activeToolSchemas }),
@@ -509,6 +567,18 @@ export class Agent extends RunnerBase {
509
567
  ...(temperature !== undefined && { temperature }),
510
568
  ...(maxTokens !== undefined && { maxTokens }),
511
569
  };
570
+ // v2.6+ — call cache strategy to attach provider-specific cache
571
+ // hints. CacheGate has already routed (apply-markers / no-markers)
572
+ // and populated scope.cacheMarkers accordingly. Strategy.prepareRequest
573
+ // is a pass-through for empty markers.
574
+ const cacheMarkers = scope.cacheMarkers ?? [];
575
+ const cachePrepared = await cacheStrategy.prepareRequest(baseRequest, cacheMarkers, {
576
+ iteration,
577
+ iterationsRemaining: Math.max(0, maxIterations - iteration),
578
+ recentHitRate: scope.recentHitRate,
579
+ cachingDisabled: scope.cachingDisabled ?? false,
580
+ });
581
+ const llmRequest = cachePrepared.request;
512
582
  // Streaming-first: when the provider implements `stream()` we
513
583
  // consume chunk-by-chunk so consumers (Lens commentary, chat
514
584
  // UIs) see tokens as they arrive instead of waiting for the
@@ -960,6 +1030,44 @@ export class Agent extends RunnerBase {
960
1030
  // concatenate.
961
1031
  arrayMerge: ArrayMergeMode.Replace,
962
1032
  })
1033
+ // ── Cache layer (v2.6) ─────────────────────────────────────
1034
+ // CacheDecision subflow walks `activeInjections` + evaluates
1035
+ // each `cache:` directive, emits provider-agnostic
1036
+ // `CacheMarker[]` to scope. Pure transform; no IO.
1037
+ //
1038
+ // CRITICAL: arrayMerge: ArrayMergeMode.Replace — same lesson
1039
+ // as the v2.5.1 InjectionEngine fix. The default footprintjs
1040
+ // behavior CONCATENATES arrays from child to parent;
1041
+ // `cacheMarkers` MUST replace each iteration, not accumulate.
1042
+ .addSubFlowChartNext(SUBFLOW_IDS.CACHE_DECISION, cacheDecisionSubflow, 'CacheDecision', {
1043
+ inputMapper: (parent) => ({
1044
+ activeInjections: parent.activeInjections ?? [],
1045
+ iteration: parent.iteration ?? 1,
1046
+ maxIterations: parent.maxIterations ?? maxIterations,
1047
+ userMessage: parent.userMessage ?? '',
1048
+ ...(parent.lastToolResult !== undefined && {
1049
+ lastToolName: parent.lastToolResult?.toolName,
1050
+ }),
1051
+ cumulativeInputTokens: parent.totalInputTokens ?? 0,
1052
+ systemPromptCachePolicy,
1053
+ cachingDisabled: parent.cachingDisabled ?? false,
1054
+ }),
1055
+ outputMapper: (sf) => ({ cacheMarkers: sf.cacheMarkers }),
1056
+ arrayMerge: ArrayMergeMode.Replace,
1057
+ })
1058
+ .addFunction('UpdateSkillHistory', updateSkillHistoryStage, STAGE_IDS.UPDATE_SKILL_HISTORY, 'Update skill-history rolling window for CacheGate churn detection')
1059
+ .addDeciderFunction('CacheGate', cacheGateDecide, STAGE_IDS.CACHE_GATE, 'Gate cache-marker application: kill switch / hit-rate / skill-churn')
1060
+ .addFunctionBranch(STAGE_IDS.APPLY_MARKERS, 'ApplyMarkers',
1061
+ // Pass-through stage — markers stay in scope as-is.
1062
+ // BuildLLMRequest (Phase 7+) reads them on the next stage.
1063
+ () => undefined, 'Proceed with cache markers from CacheDecision')
1064
+ .addFunctionBranch(STAGE_IDS.SKIP_CACHING, 'SkipCaching',
1065
+ // Clear markers so BuildLLMRequest sees an empty list and
1066
+ // makes the request unmodified.
1067
+ (scope) => {
1068
+ scope.cacheMarkers = [];
1069
+ }, 'Skip caching this iteration')
1070
+ .end()
963
1071
  .addFunction('IterationStart', iterationStart, 'iteration-start', 'Iteration begin marker')
964
1072
  .addFunction('CallLLM', callLLM, STAGE_IDS.CALL_LLM, 'LLM invocation')
965
1073
  .addDeciderFunction('Route', routeDecider, SUBFLOW_IDS.ROUTE, 'ReAct routing')
@@ -1006,6 +1114,26 @@ export class Agent extends RunnerBase {
1006
1114
  export class AgentBuilder {
1007
1115
  opts;
1008
1116
  systemPromptValue = '';
1117
+ /**
1118
+ * Cache policy for the base system prompt. Set via the optional
1119
+ * 2nd argument to `.system(text, { cache })`. Default `'always'` —
1120
+ * the base prompt is stable per-turn and an ideal cache anchor.
1121
+ */
1122
+ systemPromptCachePolicy = 'always';
1123
+ /**
1124
+ * Global cache kill switch. Set via `Agent.create({ caching: 'off' })`
1125
+ * (handled in `AgentOptions` propagation). Defaults to `false`
1126
+ * (caching enabled). When `true`, the CacheGate decider routes to
1127
+ * `'no-markers'` every iteration regardless of other rules.
1128
+ */
1129
+ cachingDisabledValue = false;
1130
+ /**
1131
+ * Optional explicit CacheStrategy override. Default: undefined,
1132
+ * which means the agent auto-resolves from
1133
+ * `getDefaultCacheStrategy(provider.name)` at construction. Power
1134
+ * users override here for custom backends or test mocks.
1135
+ */
1136
+ cacheStrategyOverride;
1009
1137
  registry = [];
1010
1138
  injectionList = [];
1011
1139
  memoryList = [];
@@ -1042,9 +1170,34 @@ export class AgentBuilder {
1042
1170
  thinkingOverrides = {};
1043
1171
  constructor(opts) {
1044
1172
  this.opts = opts;
1173
+ // Cache layer: opts.caching === 'off' propagates to scope's
1174
+ // `cachingDisabled` kill switch read by CacheGate. opts.cacheStrategy
1175
+ // overrides the registry-resolved default.
1176
+ if (opts.caching === 'off')
1177
+ this.cachingDisabledValue = true;
1178
+ if (opts.cacheStrategy !== undefined)
1179
+ this.cacheStrategyOverride = opts.cacheStrategy;
1045
1180
  }
1046
- system(prompt) {
1181
+ /**
1182
+ * Set the base system prompt.
1183
+ *
1184
+ * @param prompt - The system prompt text. Stable per-turn.
1185
+ * @param options - Optional config. `cache` controls how the
1186
+ * CacheDecision subflow treats this prompt block:
1187
+ * - `'always'` (default) — cache the base prompt as a stable
1188
+ * prefix anchor. Highest cache-hit rate; recommended for
1189
+ * production agents whose system prompt rarely changes.
1190
+ * - `'never'` — skip caching. Use if the prompt contains volatile
1191
+ * content (timestamps, per-request user IDs).
1192
+ * - `'while-active'` — semantically equivalent to `'always'` for
1193
+ * the base prompt (it's always active by definition).
1194
+ * - `{ until }` — conditional invalidation (e.g., flush after iter 5).
1195
+ */
1196
+ system(prompt, options) {
1047
1197
  this.systemPromptValue = prompt;
1198
+ if (options?.cache !== undefined) {
1199
+ this.systemPromptCachePolicy = options.cache;
1200
+ }
1048
1201
  return this;
1049
1202
  }
1050
1203
  tool(tool) {
@@ -1358,7 +1511,7 @@ export class AgentBuilder {
1358
1511
  const opts = this.maxIterationsOverride !== undefined
1359
1512
  ? { ...this.opts, maxIterations: this.maxIterationsOverride }
1360
1513
  : this.opts;
1361
- const agent = new Agent(opts, this.systemPromptValue, this.registry, voice, this.injectionList, this.memoryList, this.outputSchemaParser, this.toolProviderRef);
1514
+ const agent = new Agent(opts, this.systemPromptValue, this.registry, voice, this.injectionList, this.memoryList, this.outputSchemaParser, this.toolProviderRef, this.systemPromptCachePolicy, this.cachingDisabledValue, this.cacheStrategyOverride);
1362
1515
  // Attach builder-collected recorders so they receive events from
1363
1516
  // the very first run. Mirrors what consumers would do post-build
1364
1517
  // via `agent.attach(rec)`; the builder method is purely sugar.