agentfootprint 9.13.0 → 9.14.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 (42) hide show
  1. package/AGENTS.md +3 -2
  2. package/dist/core/agent/AgentBuilder.js +14 -5
  3. package/dist/core/agent/AgentBuilder.js.map +1 -1
  4. package/dist/esm/core/agent/AgentBuilder.d.ts +5 -2
  5. package/dist/esm/core/agent/AgentBuilder.js +14 -5
  6. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  7. package/dist/esm/memory/define.d.ts +1 -1
  8. package/dist/esm/memory/define.js +113 -14
  9. package/dist/esm/memory/define.js.map +1 -1
  10. package/dist/esm/memory/define.types.d.ts +69 -4
  11. package/dist/esm/memory/define.types.js.map +1 -1
  12. package/dist/esm/memory/pipeline/default.d.ts +22 -0
  13. package/dist/esm/memory/pipeline/default.js +19 -3
  14. package/dist/esm/memory/pipeline/default.js.map +1 -1
  15. package/dist/esm/memory/stages/summarize.d.ts +193 -54
  16. package/dist/esm/memory/stages/summarize.js +370 -34
  17. package/dist/esm/memory/stages/summarize.js.map +1 -1
  18. package/dist/esm/memory/strategies.d.ts +7 -1
  19. package/dist/esm/memory/strategies.js +25 -11
  20. package/dist/esm/memory/strategies.js.map +1 -1
  21. package/dist/memory/define.js +113 -14
  22. package/dist/memory/define.js.map +1 -1
  23. package/dist/memory/define.types.js.map +1 -1
  24. package/dist/memory/pipeline/default.js +19 -3
  25. package/dist/memory/pipeline/default.js.map +1 -1
  26. package/dist/memory/stages/summarize.js +374 -35
  27. package/dist/memory/stages/summarize.js.map +1 -1
  28. package/dist/memory/strategies.js +25 -11
  29. package/dist/memory/strategies.js.map +1 -1
  30. package/dist/types/core/agent/AgentBuilder.d.ts +5 -2
  31. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  32. package/dist/types/memory/define.d.ts +1 -1
  33. package/dist/types/memory/define.d.ts.map +1 -1
  34. package/dist/types/memory/define.types.d.ts +69 -4
  35. package/dist/types/memory/define.types.d.ts.map +1 -1
  36. package/dist/types/memory/pipeline/default.d.ts +22 -0
  37. package/dist/types/memory/pipeline/default.d.ts.map +1 -1
  38. package/dist/types/memory/stages/summarize.d.ts +193 -54
  39. package/dist/types/memory/stages/summarize.d.ts.map +1 -1
  40. package/dist/types/memory/strategies.d.ts +7 -1
  41. package/dist/types/memory/strategies.d.ts.map +1 -1
  42. package/package.json +1 -1
@@ -145,16 +145,64 @@ export interface BudgetStrategy {
145
145
  readonly maxEntries?: number;
146
146
  }
147
147
  /**
148
- * Summarize — when the conversation grows long, an LLM compresses older
149
- * turns into a paragraph; recent N turns stay raw. The standard answer
150
- * to "long conversations blow context."
148
+ * Summarize — when recall grows long, one LLM call compresses the older
149
+ * entries into a summary that is STORED, while the most recent `recent`
150
+ * entries stay raw. The standard answer to "long conversations blow context".
151
+ *
152
+ * Wired in 9.14.0. Through 9.13.0 this type existed, `llm` was required, and
153
+ * the pipeline `defineMemory` built loaded the last `recent` entries and
154
+ * stopped — a strategy that named a summarizer and never called it. What runs
155
+ * now:
156
+ *
157
+ * 1. load up to `size` entries (default 20);
158
+ * 2. keep the last `recent` verbatim, seam rounded outward to a whole turn;
159
+ * 3. fold everything older into ONE summary entry — one call, over the span;
160
+ * 4. WRITE that entry back to the same store under
161
+ * `msg-summary-{fromTurn}-{toTurn}`, so the next turn reads it instead of
162
+ * paying for it again;
163
+ * 5. recall is `[summary, ...recent verbatim]`.
164
+ *
165
+ * The folded originals are NOT deleted. They stay in the store and are
166
+ * excluded from recall by the summary's coverage metadata — delete the
167
+ * summary entry and recall is verbatim again.
151
168
  */
152
169
  export interface SummarizeStrategy {
153
170
  readonly kind: typeof MEMORY_STRATEGIES.SUMMARIZE;
154
- /** Keep this many most-recent turns uncompressed. */
171
+ /**
172
+ * Keep this many most-recent entries uncompressed. The seam is rounded
173
+ * outward to a whole turn, so a question is never summarized while its
174
+ * answer stays raw.
175
+ */
155
176
  readonly recent: number;
177
+ /**
178
+ * How much history to load per turn — the pool the fold is taken from.
179
+ * Default 20 (`loadRecent`'s own default). Must be greater than `recent`:
180
+ * a window no larger than the verbatim tail never has anything older to
181
+ * compress, which is `window` with a summarizer bolted on.
182
+ */
183
+ readonly size?: number;
156
184
  /** LLM that does the compression — recommend a cheap model (haiku). */
157
185
  readonly llm: LLMProvider;
186
+ /**
187
+ * Which model `llm` is called with. REQUIRED (9.14.0), and there is no
188
+ * fall back to the agent's own model — the same law `.compaction()` has
189
+ * enforced since 8.14.0. A default here had no correct case: the same
190
+ * provider family quietly bills your MAIN model for compression, and a
191
+ * different vendor is sent a model id it has never heard of.
192
+ *
193
+ * `Agent.memory()` additionally refuses a summarizer that is the agent's own
194
+ * provider INSTANCE at the agent's own model: those two calls look identical
195
+ * and are not — the agent's runs through reliability, decorators and the
196
+ * cache, and this one runs through none of them.
197
+ */
198
+ readonly model: string;
199
+ /**
200
+ * Override the instruction the summarizer is given (the transcript
201
+ * delimiters and the authored label on the stored summary stay the
202
+ * library's). Use it for domain summaries: "preserve every refund-related
203
+ * number".
204
+ */
205
+ readonly systemPrompt?: string;
158
206
  }
159
207
  /**
160
208
  * Top-K — embed the user's query, retrieve top-K by cosine similarity.
@@ -333,6 +381,23 @@ export interface MemoryDefinition<T = unknown> {
333
381
  readonly store?: MemoryStore;
334
382
  /** When `read` runs. Default `TURN_START`. */
335
383
  readonly timing: MemoryTiming;
384
+ /**
385
+ * What this memory's OWN LLM work bills to (9.14.0) — present only for a
386
+ * strategy that calls a model on the host's behalf, which today is
387
+ * `SUMMARIZE`.
388
+ *
389
+ * Carried in the open for one reason: `Agent.memory()` is where the agent's
390
+ * own provider and model are known, and it refuses a summarizer that is that
391
+ * exact instance at that exact model (the 8.14.0 rule `.compaction()` and
392
+ * `.window()` already enforce). `defineMemory` cannot make that check — it
393
+ * has never heard of an agent — so it declares the billing and the builder
394
+ * checks it. Same field name and same shape as `WindowStrategy.billing`, so
395
+ * one refusal serves both.
396
+ */
397
+ readonly billing?: {
398
+ readonly provider: LLMProvider;
399
+ readonly model: string;
400
+ };
336
401
  /** Reserved for a future release — patterns to redact before write. */
337
402
  readonly redact?: MemoryRedactionPolicy;
338
403
  /** Snapshot projection — only meaningful when `type === CAUSAL`. */
@@ -1 +1 @@
1
- {"version":3,"file":"define.types.js","sourceRoot":"","sources":["../../../src/memory/define.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAQH,uEAAuE;AAEvE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;CACR,CAAC;AAGX;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,WAAW;IACtB,KAAK,EAAE,MAAM;IACb,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,QAAQ;CACR,CAAC;AAGX;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,eAAe,EAAE,iBAAiB;IAClC,UAAU,EAAE,YAAY;CAChB,CAAC;AAGX;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,MAAM;CACJ,CAAC;AAqXX,uEAAuE;AAEvE,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAQ,MAAM,CAAC,MAAM,CAAC,YAAY,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAQ,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAQ,MAAM,CAAC,MAAM,CAAC,aAAa,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAQ,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AAED,uEAAuE;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,kBAA2B,CAAC;AAEvE,MAAM,UAAU,kBAAkB,CAAC,EAAU;IAC3C,OAAO,GAAG,2BAA2B,GAAG,EAAE,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,OAAO,GAAG,CAAC,UAAU,CAAC,2BAA2B,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,oBAA6B,CAAC;AAE3E,MAAM,UAAU,oBAAoB,CAAC,EAAU;IAC7C,OAAO,GAAG,6BAA6B,GAAG,EAAE,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,OAAO,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,CAAC;AACvD,CAAC"}
1
+ {"version":3,"file":"define.types.js","sourceRoot":"","sources":["../../../src/memory/define.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAQH,uEAAuE;AAEvE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;CACR,CAAC;AAGX;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,WAAW;IACtB,KAAK,EAAE,MAAM;IACb,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,QAAQ;CACR,CAAC;AAGX;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,eAAe,EAAE,iBAAiB;IAClC,UAAU,EAAE,YAAY;CAChB,CAAC;AAGX;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,MAAM;CACJ,CAAC;AAubX,uEAAuE;AAEvE,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAQ,MAAM,CAAC,MAAM,CAAC,YAAY,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAQ,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAQ,MAAM,CAAC,MAAM,CAAC,aAAa,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAQ,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AAED,uEAAuE;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,kBAA2B,CAAC;AAEvE,MAAM,UAAU,kBAAkB,CAAC,EAAU;IAC3C,OAAO,GAAG,2BAA2B,GAAG,EAAE,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,OAAO,GAAG,CAAC,UAAU,CAAC,2BAA2B,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,oBAA6B,CAAC;AAE3E,MAAM,UAAU,oBAAoB,CAAC,EAAU;IAC7C,OAAO,GAAG,6BAA6B,GAAG,EAAE,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,OAAO,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,CAAC;AACvD,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { type FilterByDecayConfig } from '../stages/filterByDecay.js';
2
+ import { type SummarizeConfig } from '../stages/summarize.js';
2
3
  import type { MemoryStore } from '../store/index.js';
3
4
  import type { MemoryPipeline } from './types.js';
4
5
  export interface DefaultPipelineConfig {
@@ -50,6 +51,27 @@ export interface DefaultPipelineConfig {
50
51
  * pipeline level so it composes with `loadCount` / the budget knobs.
51
52
  */
52
53
  readonly decay?: FilterByDecayConfig;
54
+ /**
55
+ * Compress what falls out of the verbatim tail (9.14.0). When present, a
56
+ * `Summarize` stage is composed directly after the load: the oldest loaded
57
+ * entries become ONE summary entry written back to this same store, and
58
+ * recall becomes `[summary, ...recent verbatim]`.
59
+ *
60
+ * The stage's `store` and `ttlMs` are filled from THIS config — the summary
61
+ * lands in the same namespace as the turns it stands for, and under the same
62
+ * retention window, because a summary that outlived the messages it
63
+ * compressed is the leak `writeTtlMs` exists to prevent.
64
+ *
65
+ * Absent — the historical behaviour — means no summarize stage is compiled
66
+ * at all, not a summarize stage that never fires. What is not in the chart
67
+ * cannot cost anything or appear in the narrative.
68
+ *
69
+ * `defineMemory({ strategy: { kind: MEMORY_STRATEGIES.SUMMARIZE, recent,
70
+ * llm, model } })` is the door most consumers use; this is the same thing,
71
+ * spelled at the pipeline level so it composes with `loadCount` and the
72
+ * budget knobs.
73
+ */
74
+ readonly summarize?: Omit<SummarizeConfig, 'store' | 'ttlMs'>;
53
75
  /**
54
76
  * Override for the formatter's header text. Omit to use the default
55
77
  * "Relevant context from prior conversations..." phrasing.
@@ -4,9 +4,14 @@
4
4
  * Composes Layer 2-3 stages into two flowchart subflows that the wire
5
5
  * layer mounts inside the agent's main flowchart:
6
6
  *
7
- * READ : LoadRecent → PickByBudget → FormatDefault
7
+ * READ : LoadRecent → [Summarize] → [FilterByDecay] → PickByBudget →
8
+ * FormatDefault
8
9
  * WRITE : WriteMessages
9
10
  *
11
+ * The two bracketed stages are compiled only when their config is present
12
+ * (`summarize`, `decay`) — an absent option means an absent stage, never a
13
+ * stage that runs and does nothing.
14
+ *
10
15
  * Why this particular composition?
11
16
  * - Load-then-pick-then-format matches the cognitive sequence:
12
17
  * retrieve candidates, choose what fits, present it.
@@ -50,6 +55,7 @@
50
55
  import { flowChart } from 'footprintjs';
51
56
  import { loadRecent } from '../stages/loadRecent.js';
52
57
  import { filterByDecay } from '../stages/filterByDecay.js';
58
+ import { summarize } from '../stages/summarize.js';
53
59
  import { pickByBudget } from '../stages/pickByBudget.js';
54
60
  import { formatDefault } from '../stages/formatDefault.js';
55
61
  import { writeMessages } from '../stages/writeMessages.js';
@@ -79,14 +85,24 @@ export function defaultPipeline(config) {
79
85
  ...(config.writeTier && { tier: config.writeTier }),
80
86
  ...(config.writeTtlMs !== undefined && { ttlMs: config.writeTtlMs }),
81
87
  };
82
- // Compose: LoadRecent → [FilterByDecay] → [PickDecider → skip-empty |
83
- // skip-no-budget | pick] → Format
88
+ // Compose: LoadRecent → [Summarize] → [FilterByDecay] → [PickDecider →
89
+ // skip-empty | skip-no-budget | pick] → Format
84
90
  // pickByBudget is a builder-extension — it appends a decider + 3
85
91
  // branches to the pipeline so "why did / didn't we inject memory?" is
86
92
  // answerable via FlowRecorder.onDecision evidence, not just emit events.
87
93
  let readBuilder = flowChart('LoadRecent', loadRecent(loadConfig), 'load-recent', {
88
94
  description: 'Read N most-recent entries from storage into scope.loaded',
89
95
  });
96
+ if (config.summarize !== undefined) {
97
+ // Directly after the load, and before every stage that trims: decay and
98
+ // the budget picker both decide against what recall CONTAINS, and what
99
+ // recall contains is not settled until the fold has happened.
100
+ readBuilder = readBuilder.addFunction('Summarize', summarize({
101
+ ...config.summarize,
102
+ store: config.store,
103
+ ...(config.writeTtlMs !== undefined && { ttlMs: config.writeTtlMs }),
104
+ }), 'summarize', 'Fold entries older than the verbatim tail into one stored summary entry');
105
+ }
90
106
  if (config.decay !== undefined) {
91
107
  // Before the picker: what has faded is not a budget question, and the
92
108
  // budget is better spent on entries that are still worth something.
@@ -1 +1 @@
1
- {"version":3,"file":"default.js","sourceRoot":"","sources":["../../../../src/memory/pipeline/default.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"default.js","sourceRoot":"","sources":["../../../../src/memory/pipeline/default.ts"],"names":[],"mappings":"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"}
@@ -1,87 +1,226 @@
1
1
  /**
2
- * summarize — stage that compresses old loaded entries into a single
3
- * summary entry, preserving the most-recent N verbatim.
2
+ * summarize — the stage that makes the SUMMARIZE strategy summarize (9.14.0).
4
3
  *
5
- * Reads from scope: `loaded`
6
- * Writes to scope: `loaded` (mutated: oldest entries replaced by one
7
- * synthetic summary entry)
4
+ * Reads from scope: `loaded`, `identity`
5
+ * Writes to scope: `loaded` (older entries replaced by ONE summary entry)
6
+ * Writes to store: the summary entry, under a deterministic id
7
+ * Emits: agentfootprint.memory.strategy_applied (every visit that
8
+ * changes recall or decides not to), carrying the reason,
9
+ * the model, and the token usage the call reported.
10
+ *
11
+ * NOT `cost.tick`, and that is a limit worth knowing: the
12
+ * USD channel needs a `pricingTable` and the run's
13
+ * cumulative counters, both of which live on the AGENT's
14
+ * scope, and a memory pipeline is a subflow with neither.
15
+ * Emitting a tick with `estimatedUsd: 0` would be a
16
+ * cheaper-looking lie than saying nothing, so the tokens
17
+ * ride the memory event instead and the fold is never
18
+ * silent.
8
19
  *
9
20
  * Where this fits in the pipeline:
10
21
  *
11
- * loadRecent → summarize → pickByBudget → formatDefault
22
+ * loadRecent → summarize → [filterByDecay] → pickByBudget → formatDefault
23
+ *
24
+ * ## What it does, and what that costs
25
+ *
26
+ * Recall arrives from `loadRecent` oldest-first. This stage keeps the last
27
+ * `preserveRecent` entries VERBATIM and folds everything older into a single
28
+ * summary entry — **one LLM call over the span**, not one per entry and not
29
+ * one per recall. The summary is then WRITTEN BACK to the store under the id
30
+ * `msg-summary-{fromTurn}-{toTurn}`, so the next turn LOADS it instead of
31
+ * paying for it again. That write-back is the whole cost model: a span is
32
+ * summarized once in the life of a conversation, by whichever turn first saw
33
+ * it fall out of the verbatim tail.
12
34
  *
13
- * After `summarize` runs, `loaded` is smaller (fewer entries, one of
14
- * which is a synthetic summary). The picker then selects from this
15
- * reduced set using the standard budget logic.
35
+ * ## The originals are never deleted
16
36
  *
17
- * ## Determinism contract (Anthropic-reviewer ask)
37
+ * A summary is a CLAIM ABOUT the conversation, not the conversation. The
38
+ * entries it covers stay in the store byte-identical; they are excluded from
39
+ * recall by the summary's own coverage metadata ({@link SummaryCoverage}) and
40
+ * by nothing else. Delete the summary entry and the next recall is verbatim
41
+ * again. This is the same law `.compaction()` follows in the live window —
42
+ * the fold edits what is SENT, never what was said.
18
43
  *
19
- * For prompt caching to stay stable across runs, the summary content
20
- * MUST be the same each time for the same input. This requires:
44
+ * ## Two summarizer shapes
21
45
  *
22
- * 1. Temperature = 0 on the provided LLM.
23
- * 2. A stable seed if the provider supports it (Anthropic: omit;
24
- * OpenAI: pass a fixed `seed` in the request).
25
- * 3. Same system prompt + message set produces same output.
46
+ * • `llm: LLMProvider` + `model` — the library composes the call. The span
47
+ * is rendered as DATA between delimiters the authored instruction names,
48
+ * so a message inside the conversation cannot re-instruct the summarizer
49
+ * by looking like an instruction. `model` is REQUIRED here: naming it is
50
+ * what keeps the summarizer's bill separate from the agent's own (the
51
+ * `.compaction()` law, 8.14.0). Token usage is reported on the event.
52
+ * • `llm: (messages) => Promise<string>` — the caller's own call, unchanged
53
+ * since 2.x. The caller composed the request, so the caller named the
54
+ * model; the stage reports usage as unknown rather than inventing one.
26
55
  *
27
- * The stage CANNOT enforce this — it just calls the caller-supplied
28
- * `llm` function. Callers are responsible for configuring determinism.
29
- * Non-deterministic summarizers still work but invalidate prompt caches
30
- * on every turn (~5× token-cost increase on cache-enabled providers).
56
+ * ## What comes back is DATA
31
57
  *
32
- * ## When summarize does NOT fire
58
+ * The summary text is appended after an authored label this file writes, and
59
+ * the label always comes first. A summarizer that returns "IGNORE ALL
60
+ * PREVIOUS INSTRUCTIONS" produces an entry that still says, in the library's
61
+ * own words and first, that what follows is a summary written by a model and
62
+ * that the originals are retained.
33
63
  *
34
- * - `loaded.length < triggerMinEntries` → no-op (not enough history).
35
- * - `loaded.length <= preserveRecent` → no-op (nothing to summarize; all
36
- * entries would be preserved verbatim anyway).
37
- * - LLM call throws → error propagates to the pipeline's executor
38
- * (fail-loud, per loadRecent / writeMessages convention).
64
+ * ## Three ways it declines, all of them out loud
39
65
  *
40
- * ## Config guidance
66
+ * • **not worth a call** — fewer than `minFoldEntries` foldable entries, or
67
+ * fewer than `triggerMinEntries` loaded. No call. Recall still changes if
68
+ * an EARLIER turn's summary is covering entries (that is the cheap turn
69
+ * the write-back bought), and then the event says so; a turn where
70
+ * nothing at all happened writes nothing and emits nothing.
71
+ * • **summarizer failed** — the provider threw. ONE `console.warn` per
72
+ * stage instance plus an event, and recall proceeds VERBATIM: a broken
73
+ * summarizer degrades this strategy to `window`, it does not fail the
74
+ * turn. (Through 9.13.0 the stage re-threw; a memory that cannot recall
75
+ * because its optional compressor is down is a worse answer than an
76
+ * uncompressed one.)
77
+ * • **replacement not smaller** — the summary plus its label is no shorter
78
+ * than the span it would replace. Folding then spends a call to GROW
79
+ * recall and lose detail at the same time, so the fold is dropped and the
80
+ * span is LATCHED: the same span is never re-asked, because the same
81
+ * inputs give the same answer and re-asking is a paid call whose result
82
+ * is already known (the 8.14.0 latch, keyed by the span's own ids). A
83
+ * span that has GROWN is a different key and is asked again on purpose.
41
84
  *
42
- * Choose `triggerMinEntries - preserveRecent >= 2`. Below that, a firing
43
- * summarizer would compress just one entry — wasted LLM call with no
44
- * real compression. Defaults (trigger 20, preserve 5) summarize 15
45
- * entries when firing, which is a meaningful compression ratio.
85
+ * ## Determinism contract
46
86
  *
47
- * ## Summary entry shape
87
+ * For prompt caching to stay stable, the same span should produce the same
88
+ * summary. Configure `temperature: 0` (and a seed where the provider has
89
+ * one) on the summarizer you pass. The stage cannot enforce it — but note
90
+ * that write-back makes it matter far less than it did: a span is summarized
91
+ * once and then read back from the store.
48
92
  *
49
- * The synthetic entry replaces the summarized range. It has:
50
- * - `id`: `summary-{earliest_turn}-to-{latest_turn}`
51
- * - `value`: a `{role: 'system', content: summaryText}` message
52
- * - `source.turn`: the LATEST turn that was summarized (for sorting)
53
- * - `tier`: 'cold' (marks it as "condensed, may not be recent")
54
- * - `source.identity`: carried over from the summarized range's first entry
93
+ * @see ../define.ts the `SUMMARIZE` strategy arm that wires this
94
+ * @see ../../core/agent/window/strategies/summarizeOldest.ts the same laws
95
+ * applied to the LIVE window instead of to recall
55
96
  */
56
97
  import type { TypedScope } from 'footprintjs';
57
- import type { LLMMessage as Message } from '../../adapters/types.js';
98
+ import type { MemoryEntry } from '../entry/index.js';
99
+ import type { LLMMessage as Message, LLMProvider } from '../../adapters/types.js';
100
+ import type { MemoryStore } from '../store/index.js';
58
101
  import type { MemoryState } from './types.js';
102
+ /**
103
+ * The 2.x summarizer shape: the caller makes the call and returns the text.
104
+ * Kept because a hand-composed pipeline that already owns its provider does
105
+ * not need the library to own it too.
106
+ */
107
+ export type SummarizeCallback = (messages: readonly Message[]) => Promise<string>;
59
108
  export interface SummarizeConfig {
60
109
  /**
61
- * LLM callback. Receives the chronological messages to summarize;
62
- * must return the summary text. Caller is responsible for configuring
63
- * the underlying model (temperature=0, seed, system prompt, etc.) to
64
- * keep the output deterministic — see "Determinism contract" above.
110
+ * Who writes the summary — a provider (preferred; pair with {@link model})
111
+ * or the legacy callback that makes its own call.
112
+ *
113
+ * Recommend a cheap model. The point of naming a summarizer at all is that
114
+ * compression is not worth your main model's price.
65
115
  */
66
- readonly llm: (messages: readonly Message[]) => Promise<string>;
116
+ readonly llm: LLMProvider | SummarizeCallback;
67
117
  /**
68
- * Minimum `loaded.length` before summarization triggers. Below this,
69
- * no-op — the conversation is short enough to keep verbatim. Default 20.
118
+ * The model id the summarizer is called with. REQUIRED when `llm` is a
119
+ * provider, refused as meaningless when `llm` is a callback (the callback
120
+ * composed its own request and named its own model there).
121
+ *
122
+ * There is no `?? agentModel` fallback, by the same reasoning
123
+ * `.compaction()` uses: the default had no correct case — same family and
124
+ * it quietly bills your main model for compression, different vendor and it
125
+ * sends a model id nobody has heard of.
126
+ */
127
+ readonly model?: string;
128
+ /**
129
+ * Where the summary is written back to. Omit and the fold is computed for
130
+ * THIS recall only and thrown away — correct, and paid for again every
131
+ * turn. `defaultPipeline` passes its own store, so the factory path always
132
+ * writes back.
133
+ */
134
+ readonly store?: MemoryStore;
135
+ /**
136
+ * Minimum `loaded.length` before a fold is considered. Below this, no-op —
137
+ * the conversation is short enough to keep whole. Default 20.
70
138
  */
71
139
  readonly triggerMinEntries?: number;
72
140
  /**
73
- * Number of most-recent entries to preserve verbatim (NOT summarized).
74
- * The oldest `loaded.length - preserveRecent` entries become a single
75
- * summary entry. Default 5 — keeps recent turns intact so the agent
76
- * can reference specific phrasing.
141
+ * How many most-recent entries stay VERBATIM. The older ones become the
142
+ * summary. Default 5 — recent turns keep their exact phrasing so the agent
143
+ * can quote them.
144
+ *
145
+ * The seam is rounded OUTWARD to a whole turn: if the cut would land in the
146
+ * middle of turn 7, turn 7 goes to the verbatim side entirely. A question
147
+ * summarized while its answer stayed raw reads like an answer to nothing.
77
148
  */
78
149
  readonly preserveRecent?: number;
79
150
  /**
80
- * Optional custom system prompt for the summarizer. Default is a
81
- * neutral "summarize the following conversation..." instruction.
82
- * Override for domain-specific summaries (e.g., "preserve all
83
- * refund-related details").
151
+ * Do not spend a call to fold fewer than this many entries. Default 2 — one
152
+ * entry "compressed" into a summary plus a label is a paid call that makes
153
+ * recall longer. `defineMemory` raises this to the size of the verbatim
154
+ * tail, which is the cost policy: never fold less than you keep.
155
+ */
156
+ readonly minFoldEntries?: number;
157
+ /**
158
+ * Override the authored instruction the summarizer is given. Domain
159
+ * summaries want this ("preserve every refund-related number"). Only the
160
+ * INSTRUCTION is yours — the transcript delimiters and the label written
161
+ * onto the summary entry are the library's and stay put.
84
162
  */
85
163
  readonly systemPrompt?: string;
164
+ /**
165
+ * TTL in milliseconds applied to the summary entry, counted from the SPAN's
166
+ * clock (the newest entry it stands for), not from the fold.
167
+ * `defaultPipeline` passes its `writeTtlMs` here, so a compliance retention
168
+ * window expires the summary WITH the turns it compressed — a summary that
169
+ * outlived them is exactly the leak the retention window exists to prevent.
170
+ */
171
+ readonly ttlMs?: number;
172
+ /**
173
+ * Tier for the summary entry. Default `'cold'` — condensed, not recent.
174
+ *
175
+ * Worth pairing with the load's `tiers` filter if you set one: a filter that
176
+ * excludes this tier hides the summary from every later recall, and a
177
+ * summary that is never loaded is a span that is folded (and billed) again
178
+ * every turn. `defineMemory` sets no tier filter, so the factory path is
179
+ * safe by construction.
180
+ */
181
+ readonly summaryTier?: 'hot' | 'warm' | 'cold';
182
+ /** `strategyId` on the emitted event. Default `'memory-summarize'`. */
183
+ readonly strategyId?: string;
86
184
  }
185
+ /** Id prefix for every entry this stage writes. */
186
+ export declare const SUMMARY_ID_PREFIX = "msg-summary-";
187
+ /** Metadata key carrying {@link SummaryCoverage} on a summary entry. */
188
+ export declare const SUMMARY_COVERAGE_KEY = "summarizes";
189
+ /**
190
+ * What a summary entry stands for — the only thing that excludes an original
191
+ * from recall.
192
+ *
193
+ * `coveredIds` is the operative field and it is exact: recall drops an entry
194
+ * because a loaded summary NAMES it, never because a turn number falls inside
195
+ * a range. A turn that was half-folded (possible only for hand-composed
196
+ * configs; the stage rounds the seam outward) keeps the half nobody claimed.
197
+ */
198
+ export interface SummaryCoverage {
199
+ /** Earliest turn represented. */
200
+ readonly fromTurn: number;
201
+ /** Latest turn represented. Also the entry's `source.turn`. */
202
+ readonly toTurn: number;
203
+ /** How many entries were folded. */
204
+ readonly entryCount: number;
205
+ /** The exact entries this summary stands for. */
206
+ readonly coveredIds: readonly string[];
207
+ /** Which model wrote it. Absent for the callback form — the caller knows. */
208
+ readonly model?: string;
209
+ /** When the fold happened (unix ms). */
210
+ readonly summarizedAtMs: number;
211
+ }
212
+ /** The id a fold over `[fromTurn, toTurn]` always gets. Deterministic. */
213
+ export declare function summaryEntryId(fromTurn: number, toTurn: number): string;
214
+ /**
215
+ * The coverage a summary entry carries, or `undefined` for an ordinary entry.
216
+ *
217
+ * Read through the METADATA, not the id: an id is a name and metadata is the
218
+ * claim. An entry named like a summary that carries no coverage stands for
219
+ * nothing and is treated as an ordinary entry.
220
+ */
221
+ export declare function summaryCoverage(entry: MemoryEntry<unknown>): SummaryCoverage | undefined;
222
+ /** True when this entry is a summary written by this stage. */
223
+ export declare function isSummaryEntry(entry: MemoryEntry<unknown>): boolean;
224
+ /** Opening of the authored label. Stable — readers and tests match on it. */
225
+ export declare const SUMMARY_FRAME_PREFIX = "[summary of earlier turns";
87
226
  export declare function summarize(config: SummarizeConfig): (scope: TypedScope<MemoryState>) => Promise<void>;