@orkestrel/agent 0.0.36 → 0.0.38

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.
@@ -2755,6 +2755,7 @@ var AgentJudge = class {
2755
2755
  *
2756
2756
  * @remarks
2757
2757
  * The caller supplies the server origin and model. Every question travels in one request.
2758
+ * Wire keys are zero-based positions in question entry order; answers return under the caller's ids.
2758
2759
  * Server measures are ignored; the response model is preserved.
2759
2760
  * `headers` supplies authentication through the shared judge engine.
2760
2761
  * The example readings are those of the response Ollama 0.40.0 returned for that request on 2026-10-07.
@@ -2811,7 +2812,7 @@ var SystemOneJudge = class extends AgentJudge {
2811
2812
  /** Identifies the System One protocol. */
2812
2813
  name = "systemone";
2813
2814
  /**
2814
- * Projects the state and questions onto the System One request with the configured model.
2815
+ * Projects the state and questions onto the System One request with the configured model and positional keys.
2815
2816
  *
2816
2817
  * @param request - The state and questions keyed by caller id
2817
2818
  * @returns The System One request body
@@ -2820,11 +2821,11 @@ var SystemOneJudge = class extends AgentJudge {
2820
2821
  return {
2821
2822
  state: request.state,
2822
2823
  model: this.model,
2823
- questions: Object.fromEntries(Object.entries(request.questions).map(([id, question]) => [id, questionToSystemOne(question)]))
2824
+ questions: Object.fromEntries(Object.values(request.questions).map((question, index) => [String(index), questionToSystemOne(question)]))
2824
2825
  };
2825
2826
  }
2826
2827
  /**
2827
- * Decodes requested System One answers and reports the server model and available usage.
2828
+ * Decodes positional System One answers under caller ids and reports the server model and available usage.
2828
2829
  *
2829
2830
  * @param value - The parsed response body
2830
2831
  * @param request - The questions defining the expected answers
@@ -2834,9 +2835,10 @@ var SystemOneJudge = class extends AgentJudge {
2834
2835
  */
2835
2836
  read(value, request) {
2836
2837
  if (!isSystemOneResponse(value)) throw new JudgeError("PROTOCOL", `judge error: invalid System One response envelope; received ${preview(value)}`);
2837
- const entries = Object.entries(request.questions).map(([id, question]) => {
2838
- if (!Object.hasOwn(value.answers, id)) throw new JudgeError("PROTOCOL", `judge error: question ${id} has no System One answer`);
2839
- const wire = value.answers[id];
2838
+ const entries = Object.entries(request.questions).map(([id, question], index) => {
2839
+ const position = String(index);
2840
+ if (!Object.hasOwn(value.answers, position)) throw new JudgeError("PROTOCOL", `judge error: question ${id} has no System One answer`);
2841
+ const wire = value.answers[position];
2840
2842
  if (!isSystemOneAnswer(wire)) throw new JudgeError("PROTOCOL", `judge error: question ${id} has an invalid System One answer; received ${preview(wire)}`);
2841
2843
  const answer = extractSystemOneAnswer(wire, question);
2842
2844
  if (answer === void 0) throw new JudgeError("PROTOCOL", `judge error: question ${id} has a mismatched or incomplete System One answer`);
@@ -8805,6 +8807,11 @@ var PLACED_CATEGORIES = Object.freeze(["rule", "correction"]);
8805
8807
  var LEDGER_RULES_KEY = "rules";
8806
8808
  /** Prefixes the key of an owner's record, which the owner's id follows. */
8807
8809
  var LEDGER_OWNER_PREFIX = "owner:";
8810
+ /** Labels recalled conversation text by its origin. */
8811
+ var LEDGER_RECALL_LABELS = Object.freeze({
8812
+ request: "Earlier request:",
8813
+ reply: "Earlier reply:"
8814
+ });
8808
8815
  /**
8809
8816
  * Supplies the wording of every question the ledger asks its judge.
8810
8817
  *
@@ -8952,6 +8959,108 @@ function isLedgerError(value) {
8952
8959
  }
8953
8960
  // src/core/ledgers/helpers.ts
8954
8961
  /**
8962
+ * Digests held messages, lookup call context, stored copies, and sentence receipts using canonical text.
8963
+ * This non-cryptographic checksum detects host edits and makes no security claim.
8964
+ * @param exchanges - The stored exchange entries
8965
+ * @param messages - The conversation holding their messages
8966
+ * @returns The hexadecimal checksum, or undefined when a required message is absent or canonical encoding fails
8967
+ * @example
8968
+ * ```ts
8969
+ * digestLedgerWindow([], []) // a deterministic hexadecimal checksum
8970
+ * ```
8971
+ */
8972
+ function digestLedgerWindow(exchanges, messages) {
8973
+ const byId = new Map(messages.map((message) => [message.id, message]));
8974
+ const held = [...new Set(exchanges.flatMap((entry) => [
8975
+ entry.request,
8976
+ entry.reply,
8977
+ ...entry.lines.map((line) => line.source)
8978
+ ]))].map((id) => byId.get(id));
8979
+ const callers = messages.filter((message) => message.role === "tool" || message.calls !== void 0);
8980
+ const encoded = attempt(() => {
8981
+ const values = [...held, ...callers].map((message) => canonicalStringify(message));
8982
+ return values.some((value) => value === void 0) ? void 0 : canonicalStringify([values, exchanges.map((entry) => [entry.copy ?? null, entry.lines])]);
8983
+ });
8984
+ if (!encoded.success || encoded.value === void 0) return void 0;
8985
+ const hash = [...encoded.value].reduce((prior, character) => {
8986
+ const code = character.codePointAt(0) ?? 0;
8987
+ return {
8988
+ first: Math.imul(prior.first ^ code, 16777619) >>> 0,
8989
+ second: Math.imul(prior.second ^ code, 16777619) >>> 0
8990
+ };
8991
+ }, {
8992
+ first: 2166136261,
8993
+ second: 3335557771
8994
+ });
8995
+ return hash.first.toString(16).padStart(8, "0") + hash.second.toString(16).padStart(8, "0");
8996
+ }
8997
+ /**
8998
+ * Collects stored messages eligible to open the recall offer.
8999
+ * @remarks
9000
+ * Notes and findings without a result do not open the offer. User statements, including quiet
9001
+ * statements available through recall's word route, can open it; this helper does not filter results.
9002
+ * @param input - The messages, findings, and classification
9003
+ * @param requests - The marked request ids
9004
+ * @param notes - The annotation ids to exclude
9005
+ * @returns User statements, lookup findings with results, marked requests, and nonblank call-free replies
9006
+ * @example
9007
+ * ```ts
9008
+ * collectRecallMessages(input, new Set(), new Set()) // eligible stored messages
9009
+ * ```
9010
+ */
9011
+ function collectRecallMessages(input, requests, notes) {
9012
+ const findings = new Set(input.findings.filter((finding) => finding.result !== void 0).map((finding) => finding.id));
9013
+ return input.messages.filter((message) => !notes.has(message.id) && (requests.has(message.id) || message.role === "assistant" && !hasCalls(message) && message.content.trim() !== "" || message.role === "user" && !hasCalls(message) || message.role === "tool" && findings.has(message.id)));
9014
+ }
9015
+ /**
9016
+ * Checks whether an eligible earlier message is absent from the prompt or incompletely covered.
9017
+ * A successful finding is held when a finding with the same canonical identity and text is fully received.
9018
+ * @param input - The messages, findings, and classification
9019
+ * @param request - The current request id; only messages before it count
9020
+ * @param requests - The marked request ids
9021
+ * @param notes - The annotation ids to exclude
9022
+ * @param messages - The seed tail and held exchange messages; lookup stubs retain their rendered text
9023
+ * @param lines - The receipts held by the window and the request copy together
9024
+ * @returns True if an eligible earlier message is absent or only partly covered; false otherwise
9025
+ * @example
9026
+ * ```ts
9027
+ * hasHiddenRecall(input, request.id, requests, notes, tail, lines) // whether to offer recall
9028
+ * ```
9029
+ */
9030
+ function hasHiddenRecall(input, request, requests, notes, messages, lines) {
9031
+ const start = input.messages.findIndex((message) => message.id === request);
9032
+ const before = new Set(input.messages.slice(0, Math.max(0, start)).map((message) => message.id));
9033
+ const present = new Map(messages.map((message) => [message.id, message]));
9034
+ const receipts = Map.groupBy(lines, (line) => line.source);
9035
+ return collectRecallMessages(input, requests, notes).some((message) => {
9036
+ if (!before.has(message.id)) return false;
9037
+ if (requests.has(message.id) || message.role === "assistant") return !present.has(message.id);
9038
+ if (present.get(message.id)?.content === message.content) return false;
9039
+ const finding = input.findings.find((one) => one.id === message.id);
9040
+ return !(finding === void 0 ? [message.id] : input.findings.filter((one) => before.has(one.id) && matchesLookup(finding, one)).map((one) => one.id)).some((id) => {
9041
+ if (present.get(id)?.content === message.content) return true;
9042
+ const held = new Set((receipts.get(id) ?? []).map((line) => line.sentence));
9043
+ return splitSentences(message.content).every((_, at) => held.has(at));
9044
+ });
9045
+ });
9046
+ }
9047
+ /**
9048
+ * Checks whether two successful findings carry the same text for the same canonical lookup.
9049
+ * @param left - The finding to compare
9050
+ * @param right - The candidate copy
9051
+ * @returns True for the same finding or equal canonical identities and text; unkeyable findings match only themselves
9052
+ * @example
9053
+ * ```ts
9054
+ * matchesLookup(finding, finding) // true when it has a result
9055
+ * ```
9056
+ */
9057
+ function matchesLookup(left, right) {
9058
+ if (left.result === void 0 || right.result === void 0 || left.text !== right.text) return false;
9059
+ if (left.id === right.id) return true;
9060
+ const identity = identifyLookup(left.name, left.arguments);
9061
+ return identity !== void 0 && identity === identifyLookup(right.name, right.arguments);
9062
+ }
9063
+ /**
8955
9064
  * Collects the live message ids placed in records or left without a record.
8956
9065
  * @param projection - The projected records and unplaced ids
8957
9066
  * @returns The distinct live message ids
@@ -9507,17 +9616,17 @@ function matchEntities(index, text, partial) {
9507
9616
  * Projects the owner records and the rules record from a conversation's messages.
9508
9617
  *
9509
9618
  * @remarks
9510
- * Each line is a sentence of a live message, verbatim except that a sentence that opens with a pronoun opens with its party and a colon. A message is live when it is a user message,
9511
- * or a tool message whose finding is the last finding of its call, and the input neither excludes it nor
9512
- * files it quiet or replaced. A finding with an undefined `result` still replaces the earlier
9513
- * finding of the same call, so the earlier result leaves every record.
9619
+ * Each line is a sentence of a live message, verbatim except that a sentence that opens with a
9620
+ * pronoun opens with its party and a colon. {@link collectLive} retains user messages and the
9621
+ * earliest finding in each call identity's last run of identical text, subject to exclusions,
9622
+ * quiet classification, and supersessions. An undefined result still replaces the earlier finding.
9514
9623
  *
9515
9624
  * A live message joins the owner records its entities name, directly or through a linked lookup
9516
9625
  * argument. A message that names no owner joins where its earlier side of an amendment pair joins,
9517
9626
  * or on the rules record when it is filed as a rule or a correction, and is an orphan otherwise.
9518
9627
  *
9519
- * Every live sentence is retained. Accepted direct edges are listed in `amendments`. An amending message
9520
- * that is itself superseded retains its accepted edge without restoring its text.
9628
+ * Every live sentence is retained. Accepted edges are listed in `amendments`. An amender that is
9629
+ * superseded points to its newest live successor, or retains its endpoint when none exists.
9521
9630
  *
9522
9631
  * A sentence that opens with a pronoun takes the party named in the sentence before it. The reading
9523
9632
  * trusts capitals, so a capitalized word that is no person, such as a carrier named mid-sentence,
@@ -9725,12 +9834,19 @@ function splitTopic(topic) {
9725
9834
  * Cuts entries to a room and names how many it left out.
9726
9835
  *
9727
9836
  * @remarks
9728
- * The cut keeps at least one entry. When it leaves entries out, its last line reads
9837
+ * By default the cut keeps at least one entry. With `first: false`, it keeps whole entries only
9838
+ * when the joined result, tool framing, and omission line fit, and considers later entries after
9839
+ * an oversized group. When it leaves some entries out, its last line reads
9729
9840
  * `N older items not shown; name a narrower topic to narrow the recall`, which
9730
- * {@link matchesCutLine} recognizes.
9841
+ * {@link matchesCutLine} recognizes. Pass `more` when the entries aren't in age order, so the line
9842
+ * doesn't claim an omitted entry is older than a kept one. A fitted cut also uses `more` when it
9843
+ * skips an entry before a kept one. With no kept entry it reads
9844
+ * `N older items didn't fit in this request; answer from the messages you can see`.
9731
9845
  *
9732
9846
  * @param entries - The entries in the order they are kept
9733
9847
  * @param room - The estimate units the joined entries can take
9848
+ * @param kind - What the cut line calls an omitted entry. Default: `older`
9849
+ * @param first - If `true`, keeps the first entry regardless of room; if `false`, fits every entry and the omission line. Default: `true`
9734
9850
  * @returns The kept entries followed by the cut line when any were left out, joined by newlines
9735
9851
  *
9736
9852
  * @example
@@ -9738,7 +9854,30 @@ function splitTopic(topic) {
9738
9854
  * cutListing(['first entry', 'second entry'], 1) // 'first entry\n1 older item not shown; name a narrower topic to narrow the recall'
9739
9855
  * ```
9740
9856
  */
9741
- function cutListing(entries, room) {
9857
+ function cutListing(entries, room, kind = "older", first = true) {
9858
+ if (!first) {
9859
+ if (estimateMessages([{
9860
+ id: "recall",
9861
+ role: "tool",
9862
+ content: entries.join("\n")
9863
+ }]) <= room) return entries.join("\n");
9864
+ const kept = entries.reduce((accepted, entry, at) => {
9865
+ const left = entries.length - accepted.length - 1;
9866
+ const omitted = accepted.length < at ? "more" : kind;
9867
+ return estimateMessages([{
9868
+ id: "recall",
9869
+ role: "tool",
9870
+ content: [
9871
+ ...accepted.map((index) => entries[index]),
9872
+ entry,
9873
+ ...left > 0 ? [`${left} ${omitted} item${left === 1 ? "" : "s"} not shown; name a narrower topic to narrow the recall`] : []
9874
+ ].join("\n")
9875
+ }]) <= room ? [...accepted, at] : accepted;
9876
+ }, []);
9877
+ const left = entries.length - kept.length;
9878
+ const omitted = kept.some((at, index) => at !== index) ? "more" : kind;
9879
+ return [...kept.map((at) => entries[at]), ...left > 0 ? [`${left} ${omitted} item${left === 1 ? "" : "s"} ${kept.length === 0 ? "didn't fit in this request; answer from the messages you can see" : "not shown; name a narrower topic to narrow the recall"}`] : []].join("\n");
9880
+ }
9742
9881
  const stop = [...entries.keys()].findIndex((at) => at > 0 && estimateMessages([{
9743
9882
  id: "recall",
9744
9883
  role: "tool",
@@ -9746,7 +9885,7 @@ function cutListing(entries, room) {
9746
9885
  }]) > room);
9747
9886
  const kept = entries.slice(0, stop === -1 ? void 0 : stop);
9748
9887
  const left = entries.length - kept.length;
9749
- return [...kept, ...left > 0 ? [`${left} older item${left === 1 ? "" : "s"} not shown; name a narrower topic to narrow the recall`] : []].join("\n");
9888
+ return [...kept, ...left > 0 ? [`${left} ${kind} item${left === 1 ? "" : "s"} not shown; name a narrower topic to narrow the recall`] : []].join("\n");
9750
9889
  }
9751
9890
  /**
9752
9891
  * Checks whether a line is the cut line {@link cutListing} writes.
@@ -9760,7 +9899,7 @@ function cutListing(entries, room) {
9760
9899
  * ```
9761
9900
  */
9762
9901
  function matchesCutLine(line) {
9763
- return /^\d+ older items? not shown; /.test(line);
9902
+ return /^\d+ (?:older|more) items? (?:not shown; |didn't fit in this request; answer from the messages you can see$)/.test(line);
9764
9903
  }
9765
9904
  /**
9766
9905
  * Renders the tail stub of a lookup result from an earlier request.
@@ -9821,11 +9960,12 @@ function fitSlope(groups) {
9821
9960
  * Collects the ids of the live messages in conversation order.
9822
9961
  *
9823
9962
  * @remarks
9824
- * A message is live when it is a user message, or a tool message whose finding is the last finding of its
9825
- * call, and the input neither excludes it nor files it quiet or replaced. A finding replaces any
9826
- * earlier finding of the same call, an empty one included, so an empty lookup leaves the earlier
9827
- * result out of every record. A finding with no canonical identity stays live as its own source
9828
- * and participates in no replacement.
9963
+ * A message is live when it is a user message or the first unsuperseded finding in its canonical
9964
+ * call identity's last run of identical text, and the input neither excludes it nor files it quiet
9965
+ * or superseded. When the whole run is superseded, its first finding reaches that final filter.
9966
+ * Different text or an undefined result replaces the earlier run, so an empty lookup leaves the
9967
+ * earlier result out of every record. A thrown lookup leaves the earlier result live. A finding
9968
+ * with no canonical identity participates in no replacement.
9829
9969
  *
9830
9970
  * @param input - The messages, findings, and classification to read
9831
9971
  * @returns The live message ids
@@ -9841,7 +9981,14 @@ function collectLive(input) {
9841
9981
  const position = new Map(input.messages.map((message, at) => [message.id, at]));
9842
9982
  const findings = input.findings.filter((finding) => position.has(finding.id));
9843
9983
  const identities = new Map(findings.map((finding) => [finding.id, identifyLookup(finding.name, finding.arguments)]));
9844
- const current = new Set(findings.filter((finding) => finding.result !== void 0 && !findings.some((later) => identities.get(finding.id) !== void 0 && (position.get(later.id) ?? -1) > (position.get(finding.id) ?? -1) && identities.get(later.id) === identities.get(finding.id))).map((finding) => finding.id));
9984
+ const current = new Set([...Map.groupBy(findings, (finding) => identities.get(finding.id) ?? finding).values()].flatMap((group) => {
9985
+ const ordered = group.toSorted((left, right) => (position.get(left.id) ?? -1) - (position.get(right.id) ?? -1));
9986
+ const last = ordered.at(-1);
9987
+ if (last?.result === void 0) return [];
9988
+ const boundary = ordered.findLastIndex((finding) => finding.result === void 0 || finding.text !== last.text);
9989
+ const first = ordered.slice(boundary + 1).find((finding) => (classification.supersessions.get(finding.id) ?? []).length === 0) ?? ordered[boundary + 1];
9990
+ return first === void 0 ? [] : [first.id];
9991
+ }));
9845
9992
  return [...new Map(input.messages.map((message) => [message.id, message])).values()].filter((message) => message.role === "user" || message.role === "tool" && current.has(message.id)).map((message) => message.id).filter((id) => !excluded.has(id) && !classification.quiet.has(id) && (classification.supersessions.get(id) ?? []).length === 0);
9846
9993
  }
9847
9994
  /**
@@ -9896,17 +10043,18 @@ function placeMember(input, links, amended, id, seen, settled) {
9896
10043
  return keys;
9897
10044
  }
9898
10045
  /**
9899
- * Collects accepted direct amendments of live messages.
10046
+ * Collects accepted amendments of live messages, following superseded amenders to live successors.
9900
10047
  *
9901
10048
  * @remarks
9902
10049
  * An accepted edge relates an earlier live message to an amender without claiming which sentences
9903
- * changed. An amender takes effect while live and retains its edge after another message supersedes
9904
- * it, without restoring the superseded text. An excluded or quiet user or tool message has no effect.
10050
+ * changed. A superseded amender points to its newest reachable live successor in conversation order,
10051
+ * with cycles guarded and duplicate endpoints removed. With no live successor, its endpoint stays.
10052
+ * This never restores superseded text. An excluded or quiet user or tool message has no effect.
9905
10053
  *
9906
10054
  * @param input - The exclusions and classification to read
9907
10055
  * @param byId - The messages by id
9908
10056
  * @param live - The live message ids from {@link collectLive}
9909
- * @returns The distinct accepted direct edges in conversation order
10057
+ * @returns The distinct accepted edges in conversation order
9910
10058
  *
9911
10059
  * @example
9912
10060
  * ```ts
@@ -9920,7 +10068,10 @@ function collectAmendments(input, byId, live) {
9920
10068
  const ordered = [...new Set(input.messages.map((message) => message.id))];
9921
10069
  const positions = new Map(ordered.map((id, index) => [id, index]));
9922
10070
  const effective = /* @__PURE__ */ new Set([...live, ...[...byId.values()].filter((message) => message.role !== "assistant" && !excluded.has(message.id) && !classification.quiet.has(message.id) && (classification.supersessions.get(message.id) ?? []).length > 0).map((message) => message.id)]);
9923
- return ordered.filter((id) => living.has(id)).flatMap((earlier) => [...new Set(classification.amendments.get(earlier) ?? [])].filter((later) => effective.has(later) && positions.has(later)).sort((a, b) => (positions.get(a) ?? 0) - (positions.get(b) ?? 0)).map((later) => ({
10071
+ return ordered.filter((id) => living.has(id)).flatMap((earlier) => [...new Set((classification.amendments.get(earlier) ?? []).filter((later) => effective.has(later) && positions.has(later)).map((later) => {
10072
+ const successors = new Set(scanAmendments(classification.supersessions, later, () => true));
10073
+ return ordered.findLast((id) => living.has(id) && successors.has(id)) ?? later;
10074
+ }))].sort((a, b) => (positions.get(a) ?? 0) - (positions.get(b) ?? 0)).map((later) => ({
9924
10075
  earlier,
9925
10076
  later
9926
10077
  })));
@@ -9966,6 +10117,83 @@ function buildLines(input, byId, id, holders, system) {
9966
10117
  }];
9967
10118
  });
9968
10119
  }
10120
+ /**
10121
+ * Encodes a sentence's record placement without comparing rendered text.
10122
+ * @param line - The record key, title, source, and zero-based sentence index
10123
+ * @returns The serialized placement identity
10124
+ * @example
10125
+ * ```ts
10126
+ * buildLedgerLineKey({ key: 'rules', title: 'Rules', source: 'one', sentence: 0 })
10127
+ * ```
10128
+ */
10129
+ function buildLedgerLineKey(line) {
10130
+ return JSON.stringify([
10131
+ line.key,
10132
+ line.title,
10133
+ line.source,
10134
+ line.sentence
10135
+ ]);
10136
+ }
10137
+ /**
10138
+ * Collects the record's sentence receipts not already held by a window.
10139
+ * @param record - The selected record with its original sentence identities
10140
+ * @param held - Placement identities already rendered in the window
10141
+ * @returns The remaining sentences with their record placement
10142
+ * @example
10143
+ * ```ts
10144
+ * collectLedgerLines({ key: 'rules', title: 'Rules', members: [], lines: [] }, new Set()) // []
10145
+ * ```
10146
+ */
10147
+ function collectLedgerLines(record, held) {
10148
+ return record.lines.map((line) => ({
10149
+ ...line,
10150
+ key: record.key,
10151
+ title: record.title
10152
+ })).filter((line) => !held.has(buildLedgerLineKey(line)));
10153
+ }
10154
+ /**
10155
+ * Selects each marked request's unambiguous final call-free reply.
10156
+ * @param messages - Stored conversation messages in order
10157
+ * @param requests - Marked request ids delimiting reply intervals
10158
+ * @param notes - Ledger notes that separate answer passes
10159
+ * @returns Eligible final replies keyed by request id
10160
+ * @example
10161
+ * ```ts
10162
+ * collectLedgerReplies([], new Set(), new Set()).size // 0
10163
+ * ```
10164
+ */
10165
+ function collectLedgerReplies(messages, requests, notes) {
10166
+ const positions = messages.flatMap((message, at) => requests.has(message.id) ? [at] : []);
10167
+ return new Map(positions.flatMap((position, index) => {
10168
+ const request = messages[position];
10169
+ const turns = messages.slice(position + 1, positions[index + 1]);
10170
+ const reply = turns.findLast((message) => message.role === "assistant" && !hasCalls(message));
10171
+ const answers = turns.flatMap((message, at) => message.role === "assistant" && !hasCalls(message) && message.content.trim() !== "" ? [at] : []);
10172
+ const certain = answers.every((at, offset) => offset === 0 || turns.slice((answers[offset - 1] ?? -1) + 1, at).some((message) => notes.has(message.id)));
10173
+ return request !== void 0 && certain && reply !== void 0 && reply.content.trim() !== "" ? [[request.id, reply]] : [];
10174
+ }));
10175
+ }
10176
+ /**
10177
+ * Captures source bytes and direct amendment edges for a held window.
10178
+ * @param messages - Current stored messages
10179
+ * @param sources - Requests, replies, and record sources the window holds
10180
+ * @param amendments - Accepted direct amendment edges from the current projection
10181
+ * @returns Source and edge identities for stale detection
10182
+ * @example
10183
+ * ```ts
10184
+ * collectLedgerEvidence([], new Set(), []).messages.size // 0
10185
+ * ```
10186
+ */
10187
+ function collectLedgerEvidence(messages, sources, amendments) {
10188
+ return {
10189
+ messages: new Set(messages.filter((message) => sources.has(message.id)).map((message) => JSON.stringify(["message", message]))),
10190
+ amendments: new Map(amendments.filter((edge) => sources.has(edge.earlier) || sources.has(edge.later)).map((edge) => [JSON.stringify([
10191
+ "edge",
10192
+ edge.earlier,
10193
+ edge.later
10194
+ ]), { ...edge }]))
10195
+ };
10196
+ }
9969
10197
  // src/core/ledgers/Classifier.ts
9970
10198
  /**
9971
10199
  * Files messages through the conversation's judgment manager and reads their categories and corrections.
@@ -10010,7 +10238,7 @@ var Classifier = class {
10010
10238
  /**
10011
10239
  * Reads the category a message is filed under.
10012
10240
  * @param id - The message id
10013
- * @returns The assigned category, the recorded category at the cutoff, or `undefined`
10241
+ * @returns The assigned category, the recorded category at the cutoff, or `undefined`; a quiet winner suppressed by the user literal floor remains undecided
10014
10242
  */
10015
10243
  category(id) {
10016
10244
  const message = this.#options.conversation.message(id);
@@ -10019,25 +10247,29 @@ var Classifier = class {
10019
10247
  if (assigned !== void 0) return assigned;
10020
10248
  const answer = this.#readCategories(id);
10021
10249
  const cutoff = this.#options.thresholds.category;
10022
- return LEDGER_CATEGORIES.flatMap((category) => {
10250
+ const winner = LEDGER_CATEGORIES.flatMap((category) => {
10023
10251
  const probability = answer?.probabilities[category];
10024
10252
  return probability !== void 0 && probability >= cutoff ? [{
10025
10253
  category,
10026
10254
  probability
10027
10255
  }] : [];
10028
10256
  }).reduce((best, candidate) => best === void 0 || candidate.probability > best.probability ? candidate : best, void 0)?.category;
10257
+ return winner !== void 0 && QUIET_CATEGORIES.includes(winner) && !this.quiet(id) ? void 0 : winner;
10029
10258
  }
10030
10259
  /**
10031
10260
  * Reports whether a message files under a quiet category.
10032
10261
  * @param id - The message id
10033
- * @returns True if the assigned or recorded category is quiet; false otherwise.
10262
+ * @returns True if the assigned category is quiet or the recorded quiet weight reaches the cutoff, except for a user message carrying an id or number; false otherwise
10034
10263
  */
10035
10264
  quiet(id) {
10036
10265
  const message = this.#options.conversation.message(id);
10037
10266
  if (message === void 0) return false;
10038
10267
  const assigned = this.#options.assign(message);
10039
10268
  if (assigned !== void 0) return QUIET_CATEGORIES.includes(assigned);
10040
- return this.#weigh(id, QUIET_CATEGORIES) >= this.#options.thresholds.category;
10269
+ if (this.#weigh(id, QUIET_CATEGORIES) < this.#options.thresholds.category) return false;
10270
+ if (message.role !== "user") return true;
10271
+ const literals = extractLiterals(message.content);
10272
+ return literals.ids.size + literals.numbers.size === 0;
10041
10273
  }
10042
10274
  /**
10043
10275
  * Reports whether a message files under a decisive category.
@@ -10076,7 +10308,7 @@ var Classifier = class {
10076
10308
  if (head !== "amends" && head !== "supersedes" || !isString(earlier) || !isString(later)) return [];
10077
10309
  const before = this.#options.conversation.message(earlier);
10078
10310
  const after = this.#options.conversation.message(later);
10079
- if (before === void 0 || after === void 0 || (this.#readNoul(this.#buildPair(head, earlier, later)) ?? 0) < this.#options.thresholds[head]) return [];
10311
+ if (before === void 0 || after === void 0 || (this.#readNoul(this.#buildPair(head, earlier, later)) ?? 0) < this.#options.thresholds[head] || this.quiet(later)) return [];
10080
10312
  return head === "amends" && !this.#sharesLiteral(extractLiterals(before.content), extractLiterals(after.content)) ? [] : [{
10081
10313
  head,
10082
10314
  earlier,
@@ -10328,8 +10560,9 @@ var Classifier = class {
10328
10560
  * has left.
10329
10561
  *
10330
10562
  * @remarks
10331
- * `observe` rescales from the first call of each finished request with the overhead cost taken out,
10332
- * and keeps that request's calls for the marginal rate and the longest final completion for the reply
10563
+ * `observe` rescales from the first call only when `spared` is absent, zero, or at least `overhead`,
10564
+ * with `max(0, overhead - spared)` taken out. A partially spared call keeps the existing scale.
10565
+ * It keeps that request's calls for the marginal rate and the longest final completion for the reply
10333
10566
  * reserve after subtracting its thinking. `overhead` never changes after construction.
10334
10567
  * {@link GaugeOptions} defines the measured use, reply reserve, and recall-room formulas.
10335
10568
  * {@link LedgerOptions} defines the ledger's plan budget and recall close rule.
@@ -10387,7 +10620,7 @@ var Gauge = class {
10387
10620
  }
10388
10621
  remainder(calls) {
10389
10622
  const call = calls.at(-1);
10390
- const used = isFiniteNumber(call?.prompt) ? call.prompt + (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : this.#overhead + this.#scale * (call?.estimate ?? 0);
10623
+ const used = isFiniteNumber(call?.prompt) ? call.prompt + (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : Math.max(0, this.#overhead - (call?.spared ?? 0)) + this.#scale * (call?.estimate ?? 0);
10391
10624
  return Math.max(0, this.#capacity - used);
10392
10625
  }
10393
10626
  reserve(calls, longest) {
@@ -10407,8 +10640,8 @@ var Gauge = class {
10407
10640
  }
10408
10641
  observe(calls, reply) {
10409
10642
  const first = calls[0];
10410
- if (first !== void 0 && isFiniteNumber(first.prompt) && first.estimate > 0) {
10411
- const priced = first.prompt - this.#overhead;
10643
+ if (first !== void 0 && isFiniteNumber(first.prompt) && first.estimate > 0 && ((first.spared ?? 0) === 0 || (first.spared ?? 0) >= this.#overhead)) {
10644
+ const priced = first.prompt - Math.max(0, this.#overhead - (first.spared ?? 0));
10412
10645
  if (priced > 0) this.#scale = priced / first.estimate;
10413
10646
  }
10414
10647
  this.#history = [...this.#history, [...calls]];
@@ -10585,10 +10818,13 @@ var LedgerRequest = class {
10585
10818
  #faults = /* @__PURE__ */ new Set();
10586
10819
  #message;
10587
10820
  #plan;
10821
+ #scope;
10822
+ #draft;
10823
+ #sent;
10588
10824
  #passes = [];
10589
10825
  #attempts = [];
10590
10826
  #admitted = /* @__PURE__ */ new Set();
10591
- #listed = /* @__PURE__ */ new Map();
10827
+ #listed = [];
10592
10828
  #recalls = 0;
10593
10829
  #closed = false;
10594
10830
  /**
@@ -10619,6 +10855,16 @@ var LedgerRequest = class {
10619
10855
  get plan() {
10620
10856
  return this.#plan;
10621
10857
  }
10858
+ get scope() {
10859
+ return this.#scope;
10860
+ }
10861
+ apply(scope) {
10862
+ this.#scope = scope;
10863
+ }
10864
+ /** Returns the copy and receipts adopted by the last provider turn. */
10865
+ get sent() {
10866
+ return this.#sent;
10867
+ }
10622
10868
  /**
10623
10869
  * Returns the selection the current pass's run adopted last.
10624
10870
  *
@@ -10674,6 +10920,8 @@ var LedgerRequest = class {
10674
10920
  this.#passes.at(-1)?.adopt(selection, boundary);
10675
10921
  }
10676
10922
  observe(call, position) {
10923
+ const draft = this.#draft;
10924
+ this.#sent = draft !== void 0 && this.selection?.messages.some((message) => message.id === draft.copy.id && message.content === draft.copy.content) ? draft : void 0;
10677
10925
  this.#passes.at(-1)?.observe(call, position);
10678
10926
  }
10679
10927
  spend(usage) {
@@ -10694,8 +10942,9 @@ var LedgerRequest = class {
10694
10942
  faulted(error) {
10695
10943
  return this.#faults.has(error);
10696
10944
  }
10697
- file(attempts, plan) {
10945
+ file(attempts, plan, draft) {
10698
10946
  this.#attempts = [...this.#attempts, ...attempts];
10947
+ this.#draft = draft;
10699
10948
  if (plan !== void 0) this.#plan = plan;
10700
10949
  }
10701
10950
  admit(name, args) {
@@ -10712,11 +10961,24 @@ var LedgerRequest = class {
10712
10961
  close() {
10713
10962
  this.#closed = true;
10714
10963
  }
10715
- list(result, lines) {
10716
- this.#listed = new Map([...this.#listed, [result, lines]]);
10964
+ list(result, lines, groups) {
10965
+ const accepted = groups.reduce((kept, group) => {
10966
+ const offset = kept.length === 0 ? 0 : kept.join("\n").length + 1;
10967
+ return result.slice(offset) === group || result.slice(offset).startsWith(`${group}\n`) ? [...kept, group] : kept;
10968
+ }, []);
10969
+ this.#listed = [...this.#listed, {
10970
+ result,
10971
+ lines,
10972
+ groups: accepted,
10973
+ turn: this.calls.length
10974
+ }];
10975
+ }
10976
+ /** Returns recall results with their accepted whole groups and provider-turn positions. */
10977
+ get listings() {
10978
+ return this.#listed;
10717
10979
  }
10718
10980
  lines(result) {
10719
- return this.#listed.get(result);
10981
+ return this.#listed.findLast((listing) => listing.result === result)?.lines;
10720
10982
  }
10721
10983
  };
10722
10984
  // src/core/ledgers/LedgerScan.ts
@@ -10827,6 +11089,29 @@ var LedgerScan = class {
10827
11089
  }
10828
11090
  };
10829
11091
  // src/core/ledgers/shapers.ts
11092
+ /** Describes the minimal serialized evidence of a nonempty exchange window. */
11093
+ var ledgerWindowShape = objectShape({
11094
+ digest: stringShape(),
11095
+ exchanges: arrayShape(objectShape({
11096
+ request: stringShape(),
11097
+ copy: optionalShape(stringShape()),
11098
+ reply: stringShape(),
11099
+ lines: arrayShape(objectShape({
11100
+ key: stringShape(),
11101
+ title: stringShape(),
11102
+ source: stringShape(),
11103
+ sentence: numberShape({
11104
+ integer: true,
11105
+ min: 0,
11106
+ max: Number.MAX_SAFE_INTEGER
11107
+ })
11108
+ }))
11109
+ }), { min: 1 }),
11110
+ amendments: arrayShape(objectShape({
11111
+ earlier: stringShape(),
11112
+ later: stringShape()
11113
+ }))
11114
+ });
10830
11115
  /**
10831
11116
  * Describes a ledger snapshot exactly: an unknown key refuses, so a key a later version adds never reads
10832
11117
  * as absent.
@@ -10844,13 +11129,36 @@ var ledgerSnapshotShape = objectShape({
10844
11129
  gauge: optionalShape(objectShape({
10845
11130
  scale: numberShape(),
10846
11131
  overhead: numberShape()
10847
- }))
11132
+ })),
11133
+ window: optionalShape(ledgerWindowShape)
10848
11134
  });
10849
11135
  // src/core/ledgers/contracts.ts
10850
- /** Reads the faults of a refused ledger snapshot. */
11136
+ /** Reads structural faults of a serialized exchange window. */
11137
+ var ledgerWindowContract = createContract(ledgerWindowShape);
11138
+ /** Reads structural faults of a ledger snapshot, including its optional held exchange window. */
10851
11139
  var ledgerSnapshotContract = createContract(ledgerSnapshotShape);
10852
11140
  // src/core/ledgers/validators.ts
10853
11141
  /**
11142
+ * Checks whether a value has the exact shape of a nonempty exchange window.
11143
+ * @param value - The untrusted window
11144
+ * @returns True if the window and its members are well shaped; false otherwise
11145
+ * @example
11146
+ * ```ts
11147
+ * isLedgerWindowSnapshot({ digest: 'checksum', exchanges: [{ request: 'request', reply: 'reply', lines: [] }], amendments: [] }) // true
11148
+ * ```
11149
+ */
11150
+ function isLedgerWindowSnapshot(value) {
11151
+ return holds(() => {
11152
+ if (!isRecord(value) || Object.keys(value).length !== 3 || !isString(value.digest)) return false;
11153
+ const { exchanges, amendments } = value;
11154
+ if (!arrayOf(isRecord)(exchanges) || exchanges.length === 0 || !arrayOf(isRecord)(amendments)) return false;
11155
+ return exchanges.every((entry) => {
11156
+ const { request, reply, copy, lines } = entry;
11157
+ return Object.keys(entry).length === 3 + Number(copy !== void 0) && isString(request) && isString(reply) && (copy === void 0 || isString(copy)) && arrayOf(isRecord)(lines) && lines.every((line) => Object.keys(line).length === 4 && isString(line.key) && isString(line.title) && isString(line.source) && isNonNegativeSafeInteger(line.sentence));
11158
+ }) && amendments.every((edge) => Object.keys(edge).length === 2 && isString(edge.earlier) && isString(edge.later));
11159
+ });
11160
+ }
11161
+ /**
10854
11162
  * Checks whether a value is finite and lies in the interval (0, 1].
10855
11163
  * @param value - The value to inspect
10856
11164
  * @returns True if the value is a valid ledger fraction; false otherwise
@@ -10901,6 +11209,8 @@ function isLedgerQuestion(value) {
10901
11209
  * guard is structural: it admits a conversation holding a stored `system` role
10902
11210
  * or a repeated id, which `requireConversationSnapshot` and the `Conversation` constructor then
10903
11211
  * refuse as the stored-data check a ledger's hydrate runs.
11212
+ * A present `window` must be nonempty and exactly shaped. This guard does not audit its references
11213
+ * or sentence ranges. Hydration discards the window when its digest disagrees or cannot be computed.
10904
11214
  *
10905
11215
  * @param value - The value to inspect, such as a parsed checkpoint
10906
11216
  * @returns True if the value has the shape of a ledger snapshot whose marked ids name messages of its own conversation; false otherwise
@@ -10914,8 +11224,8 @@ function isLedgerQuestion(value) {
10914
11224
  function isLedgerSnapshot(value) {
10915
11225
  return holds(() => {
10916
11226
  if (!isRecord(value)) return false;
10917
- const { conversation, requests, notes, failures, gauge } = value;
10918
- if (Object.keys(value).length !== (gauge === void 0 ? 4 : 5)) return false;
11227
+ const { conversation, requests, notes, failures, gauge, window } = value;
11228
+ if (Object.keys(value).length !== 4 + Number(gauge !== void 0) + Number(window !== void 0)) return false;
10919
11229
  if (!isConversationSnapshot(conversation)) return false;
10920
11230
  const isIds = arrayOf(isString);
10921
11231
  if (!isIds(requests) || !isIds(notes) || !isIds(failures)) return false;
@@ -10925,7 +11235,7 @@ function isLedgerSnapshot(value) {
10925
11235
  if (!isFiniteNumber(scale) || scale <= 0 || !isFiniteNumber(overhead) || overhead < 0) return false;
10926
11236
  }
10927
11237
  const roles = new Map(conversation.messages.map((message) => [message.id, message.role]));
10928
- return requests.every((id) => roles.get(id) === "user") && notes.every((id) => roles.get(id) === "user") && failures.every((id) => roles.get(id) === "tool");
11238
+ return requests.every((id) => roles.get(id) === "user") && notes.every((id) => roles.get(id) === "user") && failures.every((id) => roles.get(id) === "tool") && (window === void 0 || isLedgerWindowSnapshot(window));
10929
11239
  });
10930
11240
  }
10931
11241
  // src/core/ledgers/Ledger.ts
@@ -10955,6 +11265,8 @@ var Ledger = class {
10955
11265
  #scans = /* @__PURE__ */ new Set();
10956
11266
  #calibrations = /* @__PURE__ */ new Set();
10957
11267
  #gauge;
11268
+ #window = [];
11269
+ #evidence = collectLedgerEvidence([], /* @__PURE__ */ new Set(), []);
10958
11270
  /**
10959
11271
  * Composes the conversation, classifier, tools, and agent.
10960
11272
  * @param provider - The provider that serves the conversation
@@ -10988,7 +11300,8 @@ var Ledger = class {
10988
11300
  if (!isLedgerQuestion(options.questions)) throw new LedgerError("QUESTION", "questions must hold a choice category question whose criteria describe every category, a non-empty topic string, and noul amends and supersedes questions; pass the LEDGER_QUESTIONS constant for the measured wording");
10989
11301
  const snapshot = options.snapshot;
10990
11302
  if (snapshot !== void 0 && !isLedgerSnapshot(snapshot)) {
10991
- const faults = ledgerSnapshotContract.audit(snapshot);
11303
+ const candidate = options.snapshot;
11304
+ const faults = ledgerSnapshotContract.audit(candidate);
10992
11305
  throw new LedgerError("SNAPSHOT", `snapshot must be well formed and mark only messages of its conversation; pass what snapshot() returned${faults.length === 0 ? "" : `\n${describeFaults(faults, ledgerSnapshotContract.schema)}`}`, { context: { faults } });
10993
11306
  }
10994
11307
  this.#provider = provider;
@@ -11036,7 +11349,7 @@ var Ledger = class {
11036
11349
  }));
11037
11350
  tools.add(createTool({
11038
11351
  name: "recall",
11039
- description: `Recall what the full conversation record holds on a topic: an owner name, an id, or one of the topics ${renderTopicNames(options.topics)}. Returns source lines, newest first.`,
11352
+ description: `Find something said earlier that isn't in the messages you can see, by an owner name, an id, or one of the topics ${renderTopicNames(options.topics)}. Returns statements and lookup findings first, then earlier requests and replies labeled by origin; newest first within each group.`,
11040
11353
  contract: objectShape({ topic: stringShape({
11041
11354
  min: 1,
11042
11355
  description: "An owner name, an id, or a topic"
@@ -11120,8 +11433,8 @@ var Ledger = class {
11120
11433
  }
11121
11434
  }
11122
11435
  /**
11123
- * Serializes the conversation, the marked ids, and the gauge into a snapshot a later ledger hydrates from.
11124
- * @returns The snapshot, each id list in conversation order and `gauge` present after the ledger holds one
11436
+ * Serializes the conversation, marked ids, gauge, and completed exchange window for a later ledger.
11437
+ * @returns The snapshot, each id list in conversation order, with a gauge and window only when held
11125
11438
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
11126
11439
  */
11127
11440
  snapshot() {
@@ -11130,12 +11443,31 @@ var Ledger = class {
11130
11443
  const conversation = this.#conversation.snapshot();
11131
11444
  const ids = conversation.messages.map((message) => message.id);
11132
11445
  const gauge = this.gauge;
11446
+ const window = {
11447
+ exchanges: this.#window.map((entry) => ({
11448
+ request: entry.request.id,
11449
+ ...entry.request.content === this.#conversation.message(entry.request.id)?.content ? {} : { copy: entry.request.content },
11450
+ reply: entry.reply.id,
11451
+ lines: entry.lines.map(({ key, title, source, sentence }) => ({
11452
+ key,
11453
+ title,
11454
+ source,
11455
+ sentence
11456
+ }))
11457
+ })),
11458
+ amendments: [...this.#evidence.amendments.values()].map((edge) => ({ ...edge }))
11459
+ };
11460
+ const digest = window.exchanges.length === 0 ? void 0 : digestLedgerWindow(window.exchanges, conversation.messages);
11133
11461
  return {
11134
11462
  conversation,
11135
11463
  requests: ids.filter((id) => this.#requests.has(id)),
11136
11464
  notes: ids.filter((id) => this.#annotations.has(id)),
11137
11465
  failures: ids.filter((id) => this.#results.get(id)?.success === false),
11138
- ...gauge === void 0 ? {} : { gauge }
11466
+ ...gauge === void 0 ? {} : { gauge },
11467
+ ...window.exchanges.length === 0 || digest === void 0 ? {} : { window: {
11468
+ ...window,
11469
+ digest
11470
+ } }
11139
11471
  };
11140
11472
  }
11141
11473
  /**
@@ -11153,6 +11485,7 @@ var Ledger = class {
11153
11485
  if (this.#busy()) throw new AgentError("CONCURRENCY", "a ledger request is already active");
11154
11486
  const caller = createAbort({ ...signal === void 0 ? {} : { signal } }).signal;
11155
11487
  const request = new LedgerRequest(content, this.#options.recall?.limit);
11488
+ const scope = this.#agent.context.scope;
11156
11489
  this.#live.add(request);
11157
11490
  try {
11158
11491
  if (this.#gauge === void 0) await this.#measureGauge(caller);
@@ -11168,17 +11501,18 @@ var Ledger = class {
11168
11501
  });
11169
11502
  const passes = [first];
11170
11503
  if (!caller.aborted && (first.partial || first.content.trim() === "")) {
11171
- const digest = this.#buildDigest(request, entered);
11172
- if (digest !== void 0) this.#annotations.add(this.#conversation.add({
11504
+ const recovery = this.#buildDigest(request, entered);
11505
+ if (recovery.digest !== void 0) this.#annotations.add(this.#conversation.add({
11173
11506
  role: "user",
11174
- content: digest
11507
+ content: recovery.digest
11175
11508
  }).id);
11176
11509
  this.#annotations.add(this.#conversation.add({
11177
11510
  role: "user",
11178
11511
  content: LEDGER_NOTES.cue
11179
11512
  }).id);
11180
11513
  const previous = this.#agent.context.scope;
11181
- this.#agent.context.apply(createScope({
11514
+ const answer = createScope({
11515
+ ...previous,
11182
11516
  name: "answer",
11183
11517
  tools: [],
11184
11518
  select: async (conversation, message, runSignal) => {
@@ -11186,14 +11520,15 @@ var Ledger = class {
11186
11520
  if (selection.fault !== void 0) return selection;
11187
11521
  return {
11188
11522
  ...selection,
11189
- messages: selection.messages.filter((one) => one.role !== "tool" && !hasCalls(one))
11523
+ messages: selection.messages.filter((one) => one.role !== "tool" && !hasCalls(one) && !recovery.omitted.has(one.id))
11190
11524
  };
11191
11525
  }
11192
- }));
11526
+ });
11527
+ this.#agent.context.apply(answer);
11193
11528
  try {
11194
11529
  passes.push(await this.#generatePass(request, caller, false));
11195
11530
  } finally {
11196
- this.#agent.context.apply(previous);
11531
+ if (this.#agent.context.scope === answer) this.#agent.context.apply(previous);
11197
11532
  }
11198
11533
  }
11199
11534
  const last = passes.at(-1) ?? first;
@@ -11212,7 +11547,9 @@ var Ledger = class {
11212
11547
  ...last.stop === void 0 ? {} : { stop: last.stop }
11213
11548
  };
11214
11549
  } finally {
11550
+ if (request.scope !== void 0 && this.#agent.context.scope === request.scope) this.#agent.context.apply(scope);
11215
11551
  this.#flush();
11552
+ this.#commitExchange(request);
11216
11553
  this.#live.delete(request);
11217
11554
  }
11218
11555
  }
@@ -11275,6 +11612,37 @@ var Ledger = class {
11275
11612
  for (const id of snapshot.requests) this.#requests.add(id);
11276
11613
  for (const id of snapshot.notes) this.#annotations.add(id);
11277
11614
  const messages = this.#conversation.messages();
11615
+ if (snapshot.window !== void 0 && digestLedgerWindow(snapshot.window.exchanges, messages) === snapshot.window.digest) {
11616
+ const window = snapshot.window.exchanges.map((entry) => {
11617
+ const request = this.#conversation.message(entry.request);
11618
+ const reply = this.#conversation.message(entry.reply);
11619
+ if (request === void 0 || reply === void 0) throw new LedgerError("SNAPSHOT", "window pair must belong to its conversation");
11620
+ const lines = entry.lines.map((line) => {
11621
+ const source = this.#conversation.message(line.source);
11622
+ if (source === void 0) throw new LedgerError("SNAPSHOT", "window source must belong to its conversation");
11623
+ return {
11624
+ ...line,
11625
+ text: splitSentences(source.content)[line.sentence] ?? "",
11626
+ topics: [],
11627
+ role: source.role
11628
+ };
11629
+ });
11630
+ return {
11631
+ request: {
11632
+ ...request,
11633
+ content: entry.copy ?? request.content
11634
+ },
11635
+ reply,
11636
+ lines
11637
+ };
11638
+ });
11639
+ this.#window = window;
11640
+ this.#evidence = collectLedgerEvidence(messages, new Set(this.#window.flatMap((entry) => [
11641
+ entry.request.id,
11642
+ entry.reply.id,
11643
+ ...entry.lines.map((line) => line.source)
11644
+ ])), snapshot.window.amendments);
11645
+ }
11278
11646
  const groups = collectToolGroups(messages);
11279
11647
  for (const message of messages) {
11280
11648
  if (!snapshot.failures.includes(message.id)) continue;
@@ -11405,20 +11773,26 @@ var Ledger = class {
11405
11773
  const { attempts, failure, ...filed } = classified;
11406
11774
  const planned = attempt(() => {
11407
11775
  if (filed.fault !== void 0) throw filed.fault;
11776
+ const { draft, ...plan } = this.#plan(selected, this.#rescan());
11408
11777
  return {
11409
- ...this.#plan(selected, this.#rescan()),
11410
- ...filed,
11411
- ...failure === void 0 ? {} : { fault: failure }
11778
+ draft,
11779
+ selection: {
11780
+ ...plan,
11781
+ ...filed,
11782
+ ...failure === void 0 ? {} : { fault: failure }
11783
+ }
11412
11784
  };
11413
11785
  });
11414
- request.file(attempts, planned.success ? planned.value : void 0);
11786
+ request.file(attempts, planned.success ? planned.value.selection : void 0, planned.success ? planned.value.draft : void 0);
11415
11787
  if (!planned.success) return this.#refuse(planned.error, filed, this.#conversation);
11788
+ const selection = planned.value.selection;
11416
11789
  return this.#annotations.has(message.id) ? {
11417
- ...planned.value,
11418
- messages: [...planned.value.messages, ...this.#collectAfter(selected)]
11419
- } : planned.value;
11790
+ ...selection,
11791
+ messages: [...selection.messages, ...this.#collectAfter(selected)]
11792
+ } : selection;
11420
11793
  }
11421
11794
  #refuse(error, filed, conversation) {
11795
+ if (conversation === this.#conversation && !(isLedgerError(error) && error.code === "REQUEST")) this.#current()?.file([]);
11422
11796
  return {
11423
11797
  ...filed,
11424
11798
  messages: conversation.view(),
@@ -11445,7 +11819,11 @@ var Ledger = class {
11445
11819
  topics: [...near]
11446
11820
  });
11447
11821
  const total = Math.max(0, (this.#options.capacity - this.#predict) * this.#share.prompt - (this.#gauge?.overhead ?? 0)) / (1 + LEDGER_SCALE_DRIFT);
11448
- const tail = this.#selectTail(request, total * this.#share.tail, input, scan);
11822
+ const built = this.#agent.context.build({
11823
+ messages: [request],
11824
+ judgments: []
11825
+ });
11826
+ const tail = this.#selectTail(request, total, input, scan);
11449
11827
  const tailIds = new Set(tail.map((message) => message.id));
11450
11828
  const scoped = records.some((record) => record.key !== LEDGER_RULES_KEY);
11451
11829
  const held = /* @__PURE__ */ new Set([...projection.records.filter((record) => scoped || record.key === "rules").flatMap((record) => record.members), ...records.length > 0 ? projection.records.flatMap((record) => record.members.filter((id) => this.#conversation.message(id)?.role === "user" && this.#classifier.category(id) === "rule" && this.#classifier.decisive(id))) : []]);
@@ -11493,25 +11871,121 @@ var Ledger = class {
11493
11871
  source: unit.source,
11494
11872
  record: this.#buildUnitRecord(unit.source, unit.category, input, projection, held)
11495
11873
  }));
11496
- const trimmed = this.#trim(steps, records, mapped, new Set(ordered.map((unit) => unit.source)), request, tail, total, projection.amendments, scan);
11497
- const shown = /* @__PURE__ */ new Set([...trimmed.included, ...records.flatMap((record) => record.members.filter((source) => {
11498
- const full = record.lines.filter((line) => line.source === source);
11499
- const visible = trimmed.records.find((one) => one.key === record.key)?.lines.filter((line) => line.source === source) ?? [];
11500
- return full.length > 0 && full.length === visible.length;
11501
- }))]);
11502
- return { messages: [...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown) : message), buildRequestMessage(request, trimmed.rendered)] };
11503
- }
11504
- #trim(steps, records, mapped, included, request, tail, total, amendments, scan) {
11505
- const built = this.#agent.context.build({
11506
- messages: [request],
11507
- judgments: []
11874
+ const trimmed = this.#trim(steps, records, mapped, new Set(ordered.map((unit) => unit.source)), built, tail, total, projection.amendments, scan);
11875
+ const replies = collectLedgerReplies(scan.messages, this.#requests, this.#annotations);
11876
+ const sources = new Set(this.#window.flatMap((entry) => [
11877
+ entry.request.id,
11878
+ entry.reply.id,
11879
+ ...entry.lines.map((line) => line.source)
11880
+ ]));
11881
+ const evidence = collectLedgerEvidence(scan.messages, sources, projection.amendments);
11882
+ const prefix = this.#window.flatMap((entry) => entry.lines);
11883
+ const prior = buildLedgerRenderContext(scan.messages, prefix, projection.amendments);
11884
+ const combined = buildLedgerRenderContext(scan.messages, [...prefix, ...trimmed.rendered.lines], projection.amendments);
11885
+ const stale = evidence.messages.size !== this.#evidence.messages.size || [...evidence.messages].some((receipt) => !this.#evidence.messages.has(receipt)) || evidence.amendments.size !== this.#evidence.amendments.size || [...evidence.amendments.keys()].some((receipt) => !this.#evidence.amendments.has(receipt)) || this.#window.some((entry) => replies.get(entry.request.id)?.id !== entry.reply.id || entry.lines.some((line) => !live.has(line.source))) || projection.amendments.some((edge) => prior.first.has(edge.earlier) && prior.complete.has(edge.later) !== combined.complete.has(edge.later) || prior.first.has(edge.later) && combined.complete.has(edge.later) && !prior.first.has(edge.earlier) && combined.first.has(edge.earlier));
11886
+ const last = stale ? this.#window.at(-1) : void 0;
11887
+ const retained = last === void 0 || replies.get(last.request.id)?.id !== last.reply.id ? void 0 : this.#conversation.message(last.request.id);
11888
+ if (stale) this.#window = [];
11889
+ const [previous, reply] = this.#window.length === 0 ? this.#selectExchange(request, input) : [];
11890
+ const window = this.#window.length > 0 ? this.#window : previous !== void 0 && reply !== void 0 ? [{
11891
+ request: previous,
11892
+ reply,
11893
+ lines: []
11894
+ }] : retained !== void 0 && last !== void 0 ? [{
11895
+ request: retained,
11896
+ reply: last.reply,
11897
+ lines: []
11898
+ }] : [];
11899
+ if (this.#window.length === 0) {
11900
+ const derived = collectLedgerEvidence(scan.messages, new Set(window.flatMap((entry) => [entry.request.id, entry.reply.id])), projection.amendments);
11901
+ this.#window = window;
11902
+ this.#evidence = derived;
11903
+ }
11904
+ const carried = window.flatMap((entry) => entry.lines);
11905
+ const delta = this.#render(trimmed.records, mapped, trimmed.included, projection.amendments, scan, new Set(carried.map(buildLedgerLineKey)), carried);
11906
+ const newest = window.at(-1);
11907
+ const plain = newest === void 0 ? void 0 : this.#conversation.message(newest.request.id);
11908
+ const fallback = newest === void 0 || plain === void 0 ? [] : [{
11909
+ request: plain,
11910
+ reply: newest.reply,
11911
+ lines: []
11912
+ }];
11913
+ const empty = {
11914
+ window: [],
11915
+ rendered: trimmed.rendered
11916
+ };
11917
+ const rungs = [
11918
+ {
11919
+ window,
11920
+ rendered: delta
11921
+ },
11922
+ {
11923
+ window: fallback,
11924
+ rendered: trimmed.rendered
11925
+ },
11926
+ empty
11927
+ ];
11928
+ const probes = rungs.map((rung) => [...built.slice(0, -1), ...stripThinking([
11929
+ ...tail.slice(0, -1),
11930
+ ...rung.window.flatMap((entry) => [entry.request, entry.reply]),
11931
+ buildRequestMessage(built.at(-1) ?? request, rung.rendered.text)
11932
+ ], this.#replay)]);
11933
+ const names = this.#agent.context.scope?.tools;
11934
+ const definitions = this.#agent.context.tools.definitions();
11935
+ const offered = rungs.map((rung) => hasHiddenRecall(input, request.id, this.#requests, this.#annotations, [...tail.slice(0, -1), ...rung.window.flatMap((entry) => [entry.request, entry.reply])], [...rung.window.flatMap((entry) => entry.lines), ...rung.rendered.lines]));
11936
+ const longest = this.#findLongest();
11937
+ const closed = probes.map((messages, at) => {
11938
+ const tools = definitions.filter((tool) => (names === void 0 || names.includes(tool.name)) && (tool.name !== "recall" || offered[at]));
11939
+ const calls = [{
11940
+ estimate: estimateMessages(messages),
11941
+ tools: tools.length,
11942
+ spared: this.#spared(tools)
11943
+ }];
11944
+ return offered[at] === true && this.#gauge !== void 0 && this.#gauge.remainder(calls) - this.#predict < 2 * this.#gauge.reserve(calls, longest);
11508
11945
  });
11946
+ const prices = probes.map((messages) => this.#price(messages));
11947
+ const admitted = rungs.find((_rung, at) => (prices[at] ?? Infinity) <= total && (closed[2] === true || closed[at] === false)) ?? empty;
11948
+ if (offered[rungs.indexOf(admitted)] === false && (names === void 0 || names.includes("recall"))) {
11949
+ const tools = definitions.filter((tool) => tool.name !== "recall").map((tool) => tool.name);
11950
+ const narrowed = this.#agent.context.scope?.narrow({ tools }) ?? createScope({
11951
+ name: "ledger",
11952
+ tools
11953
+ });
11954
+ this.#current()?.apply(narrowed);
11955
+ this.#agent.context.apply(narrowed);
11956
+ }
11957
+ const copy = buildRequestMessage(request, admitted.rendered.text);
11958
+ const kept = admitted.window.flatMap((entry) => entry.lines);
11959
+ const origins = /* @__PURE__ */ new Set([
11960
+ request.id,
11961
+ ...admitted.window.flatMap((entry) => [entry.request.id, entry.reply.id]),
11962
+ ...kept.map((line) => line.source),
11963
+ ...admitted.rendered.lines.map((line) => line.source)
11964
+ ]);
11965
+ const draft = {
11966
+ copy,
11967
+ lines: admitted.rendered.lines,
11968
+ evidence: collectLedgerEvidence(scan.messages, origins, projection.amendments),
11969
+ window: admitted.window
11970
+ };
11971
+ const shown = buildLedgerRenderContext(scan.messages, [...kept, ...admitted.rendered.lines], []).complete;
11972
+ return {
11973
+ draft,
11974
+ messages: [
11975
+ ...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown) : message),
11976
+ ...admitted.window.flatMap((entry) => [entry.request, entry.reply]),
11977
+ copy
11978
+ ]
11979
+ };
11980
+ }
11981
+ #trim(steps, records, mapped, included, built, tail, total, amendments, scan) {
11509
11982
  const head = built.slice(0, -1);
11510
- const priced = built.at(-1) ?? request;
11983
+ const priced = built.at(-1);
11984
+ if (priced === void 0) throw new Error("a built plan holds its request");
11511
11985
  const found = findLedgerCut(0, steps.length, (count) => {
11512
11986
  const cut = buildLedgerCut(steps.slice(0, count), records, included);
11513
11987
  const rendered = this.#render(cut.records, mapped, cut.included, amendments, scan);
11514
- const copy = buildRequestMessage(priced, rendered);
11988
+ const copy = buildRequestMessage(priced, rendered.text);
11515
11989
  return this.#price([...head, ...stripThinking([...tail.slice(0, -1), copy], this.#replay)]) <= total ? {
11516
11990
  ...cut,
11517
11991
  rendered
@@ -11536,36 +12010,48 @@ var Ledger = class {
11536
12010
  const live = collectProjectionIds(projection);
11537
12011
  return scanAmendments(input.classification.amendments, source, (id) => id === source || !held.has(id) && live.has(id)).flatMap((id) => this.#projectLines(input, id));
11538
12012
  }
11539
- #render(records, mapped, included, amendments, scan) {
11540
- const entries = mapped.filter((unit) => included.has(unit.source)).flatMap(({ record: unit }) => [...Map.groupBy(unit.lines, (line) => line.source)].map(([source, lines]) => ({
12013
+ #render(records, mapped, included, amendments, scan, held = /* @__PURE__ */ new Set(), prefix = []) {
12014
+ const population = records.map((record) => ({
12015
+ ...record,
12016
+ lines: collectLedgerLines(record, held)
12017
+ }));
12018
+ const entries = mapped.filter((unit) => included.has(unit.source)).flatMap(({ record: unit }) => [...Map.groupBy(collectLedgerLines(unit, /* @__PURE__ */ new Set()), (line) => line.source)].map(([source, lines]) => ({
11541
12019
  unit,
11542
12020
  source,
11543
12021
  lines
11544
12022
  })));
11545
12023
  const first = new Map(entries.toReversed().map((entry) => [entry.source, entry]));
11546
- const unique = entries.filter((entry) => first.get(entry.source) === entry);
11547
- const record = records.find((candidate) => candidate.key === "rules" && candidate.lines.length > 0);
11548
- const ownerRecords = records.filter((candidate) => candidate.key !== "rules" && candidate.lines.length > 0);
11549
- const context = buildLedgerRenderContext(this.#conversation.messages(), [
12024
+ const unique = entries.filter((entry) => first.get(entry.source) === entry).map((entry) => ({
12025
+ ...entry,
12026
+ lines: entry.lines.filter((line) => !prefix.some((prior) => prior.source === line.source && prior.sentence === line.sentence))
12027
+ })).filter((entry) => entry.lines.length > 0);
12028
+ const record = population.find((candidate) => candidate.key === "rules" && candidate.lines.length > 0);
12029
+ const ownerRecords = population.filter((candidate) => candidate.key !== "rules" && candidate.lines.length > 0);
12030
+ const lines = [
11550
12031
  ...ownerRecords.flatMap((candidate) => candidate.lines),
11551
12032
  ...unique.filter((entry) => entry.unit.key !== LEDGER_RULES_KEY).flatMap((entry) => entry.lines),
11552
12033
  ...record?.lines ?? [],
11553
12034
  ...unique.filter((entry) => entry.unit.key === LEDGER_RULES_KEY).flatMap((entry) => entry.lines)
11554
- ], amendments);
12035
+ ];
12036
+ const context = buildLedgerRenderContext(scan.messages, [...prefix, ...lines], amendments);
11555
12037
  const owners = ownerRecords.map((owner) => renderLedgerPinned(owner, context)).join("\n\n");
11556
- const rendered = unique.map(({ unit, source, lines }) => {
12038
+ const rendered = unique.map(({ unit, source, lines: retained }) => {
11557
12039
  const sentences = splitSentences(this.#conversation.message(source)?.content ?? "");
11558
12040
  return {
11559
12041
  rules: unit.key === LEDGER_RULES_KEY,
11560
- texts: unit.key === "rules" && source === unit.members[0] ? lines.map((line) => this.#renderSource(source, sentences, [line], context, scan)) : [this.#renderSource(source, sentences, lines, context, scan)]
12042
+ texts: unit.key === "rules" && source === unit.members[0] ? retained.map((line) => this.#renderSource(source, sentences, [line], context, scan)) : [this.#renderSource(source, sentences, retained, context, scan)]
11561
12043
  };
11562
12044
  });
11563
12045
  const pinned = rendered.filter((one) => !one.rules).flatMap((one) => one.texts);
11564
12046
  const rules = rendered.filter((one) => one.rules).flatMap((one) => one.texts);
11565
12047
  const body = [owners, pinned.join("\n")].filter((text) => text !== "").join("\n\n");
11566
- return [body === "" ? "" : `## Records\n${body}`, record === void 0 ? rules.length === 0 ? "" : ["## Rules", ...rules].join("\n") : [renderLedgerRecord(record, context), ...rules].join("\n")].filter((text) => text !== "").join("\n\n");
12048
+ return {
12049
+ text: [body === "" ? "" : `## Records\n${body}`, record === void 0 ? rules.length === 0 ? "" : ["## Rules", ...rules].join("\n") : [renderLedgerRecord(record, context), ...rules].join("\n")].filter((part) => part !== "").join("\n\n"),
12050
+ lines
12051
+ };
11567
12052
  }
11568
- #selectTail(request, cap, input, scan) {
12053
+ #selectTail(request, total, input, scan) {
12054
+ const cap = total * this.#share.tail;
11569
12055
  const first = input.messages.findIndex((message) => this.#requests.has(message.id));
11570
12056
  const seed = input.messages.slice(0, first < 0 ? input.messages.findIndex((message) => message.id === request.id) : first);
11571
12057
  const groups = collectToolGroups(seed).flatMap((group) => {
@@ -11596,6 +12082,29 @@ var Ledger = class {
11596
12082
  const stop = exchanges.findIndex((_exchange, index) => this.#price(stripThinking([...exchanges.slice(0, index + 1).toReversed().flat(), request], this.#replay)) > cap);
11597
12083
  return [...exchanges.slice(0, stop === -1 ? exchanges.length : stop).toReversed().flat(), request];
11598
12084
  }
12085
+ #selectExchange(request, input) {
12086
+ const before = input.messages.slice(0, input.messages.findIndex((message) => message.id === request.id));
12087
+ const previous = before.findLast((message) => this.#requests.has(message.id));
12088
+ const reply = previous === void 0 ? void 0 : collectLedgerReplies(before, this.#requests, this.#annotations).get(previous.id);
12089
+ return previous !== void 0 && reply !== void 0 ? [previous, reply] : [];
12090
+ }
12091
+ #commitExchange(request) {
12092
+ const draft = request.sent;
12093
+ const message = request.message;
12094
+ if (message === void 0) return;
12095
+ const reply = collectLedgerReplies(this.#conversation.messages(), this.#requests, this.#annotations).get(message.id);
12096
+ if (reply === void 0) return;
12097
+ this.#window = [...draft?.window ?? this.#window, {
12098
+ request: draft?.copy ?? message,
12099
+ reply,
12100
+ lines: draft?.lines ?? []
12101
+ }];
12102
+ const evidence = draft?.evidence ?? this.#evidence;
12103
+ this.#evidence = {
12104
+ messages: /* @__PURE__ */ new Set([...evidence.messages, ...collectLedgerEvidence([message, reply], /* @__PURE__ */ new Set([message.id, reply.id]), []).messages]),
12105
+ amendments: evidence.amendments
12106
+ };
12107
+ }
11599
12108
  #renderTailStub(message, scan, shown) {
11600
12109
  const group = scan.group(message.id);
11601
12110
  const call = group === void 0 ? void 0 : findLedgerCall(group, message);
@@ -11604,7 +12113,7 @@ var Ledger = class {
11604
12113
  const visible = renderStub(call?.name ?? "tool", call?.arguments ?? {}, "shown");
11605
12114
  return {
11606
12115
  ...message,
11607
- content: renderStub(call?.name ?? "tool", call?.arguments ?? {}, this.#results.get(message.id)?.success === false ? "failed" : finding?.result === void 0 ? "empty" : shown === void 0 ? hidden.length > visible.length ? "hidden" : "shown" : shown.has(message.id) ? "shown" : "hidden")
12116
+ content: renderStub(call?.name ?? "tool", call?.arguments ?? {}, this.#results.get(message.id)?.success === false ? "failed" : finding?.result === void 0 ? "empty" : shown === void 0 ? hidden.length > visible.length ? "hidden" : "shown" : scan.findings.some((other) => shown.has(other.id) && matchesLookup(finding, other)) ? "shown" : "hidden")
11608
12117
  };
11609
12118
  }
11610
12119
  #observeTurn() {
@@ -11615,11 +12124,19 @@ var Ledger = class {
11615
12124
  const additions = this.#conversation.messages().slice(boundary);
11616
12125
  const messages = this.#agent.context.build(request.selection);
11617
12126
  const names = this.#agent.context.scope?.tools;
12127
+ const tools = this.#agent.context.tools.definitions().filter((tool) => names === void 0 || names.includes(tool.name));
11618
12128
  request.observe({
11619
12129
  estimate: estimateMessages(stripThinking([...messages, ...additions], this.#replay)),
11620
- tools: this.#agent.context.tools.definitions().filter((tool) => names === void 0 || names.includes(tool.name)).length
12130
+ tools: tools.length,
12131
+ spared: this.#spared(tools)
11621
12132
  }, this.#conversation.messages().length);
11622
12133
  }
12134
+ #spared(tools) {
12135
+ const overhead = this.#gauge?.overhead ?? 0;
12136
+ if (tools.length === 0) return overhead;
12137
+ const missing = this.#agent.context.tools.definitions().filter((tool) => !tools.some((one) => one.name === tool.name));
12138
+ return missing.length === 0 ? 0 : Math.min(overhead, estimateTokens(JSON.stringify(missing)) * (this.#gauge?.scale ?? 1));
12139
+ }
11623
12140
  #findLongest() {
11624
12141
  return this.#conversation.messages().filter((message) => message.role === "assistant").reduce((longest, message) => message.content.length > longest.length ? message.content : longest, "");
11625
12142
  }
@@ -11636,11 +12153,21 @@ var Ledger = class {
11636
12153
  request?.admit("recall", { topic });
11637
12154
  const gauge = this.#gauge;
11638
12155
  const calls = request?.calls ?? [];
11639
- if (gauge !== void 0 && calls.length > 0 && gauge.remainder(calls) - this.#predict < 2 * gauge.reserve(calls, this.#findLongest())) request?.close();
11640
12156
  request?.recall();
11641
12157
  const guidance = `an owner name, an id, or one of ${renderTopicNames(this.#options.topics)}`;
11642
12158
  if (topic === "") throw new Error(`recall needs a topic: ${guidance}`);
11643
- const room = (gauge?.room(calls, this.#findLongest()) ?? 0) - estimateMessages([buildRecallMessage(topic)]);
12159
+ const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
12160
+ const call = calls.at(-1);
12161
+ const carried = request === void 0 ? 0 : estimateMessages(stripThinking([...this.#agent.context.build(request.selection), ...this.#conversation.messages().slice(request.boundary)], this.#replay));
12162
+ const completion = isFiniteNumber(call?.prompt) ? (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : 0;
12163
+ const extra = Math.max(0, (carried - (call?.estimate ?? carried)) * rate - completion);
12164
+ const drift = isFiniteNumber(call?.prompt) ? 0 : Math.max(0, (call?.estimate ?? 0) * (rate - (gauge?.scale ?? rate)));
12165
+ const siblings = estimateMessages((request?.listings ?? []).filter((listing) => listing.turn === calls.length).map((listing) => ({
12166
+ id: "result",
12167
+ role: "tool",
12168
+ content: listing.result
12169
+ })));
12170
+ const room = gauge === void 0 ? -estimateMessages([buildRecallMessage(topic)]) : (gauge.remainder(calls) - this.#predict - extra - drift) / rate - siblings;
11644
12171
  const scan = this.#scan(this.#conversation.messages());
11645
12172
  const added = this.#scans.size === 0;
11646
12173
  if (added) this.#hold(scan);
@@ -11672,11 +12199,12 @@ var Ledger = class {
11672
12199
  const source = listable.get(id);
11673
12200
  if (source === void 0) return [];
11674
12201
  const lookup = scan.finding(id);
11675
- const text = lookup === void 0 ? source.content : `${renderLookupCall(lookup.name, lookup.arguments)}: ${source.content}`;
12202
+ const content = source.content.replace(/(^|[\n\v\f\r\u0085\u2028\u2029])(?=Earlier (?:request|reply):)/g, "$1 ");
12203
+ const text = lookup === void 0 ? content : `${renderLookupCall(lookup.name, lookup.arguments)}: ${content}`;
11676
12204
  return [{
11677
12205
  id,
11678
12206
  text,
11679
- stored: lookup === void 0 ? [] : [[splitResultLines(text)[0] ?? "", splitResultLines(source.content)[0] ?? ""]]
12207
+ stored: lookup === void 0 ? [] : [[splitResultLines(text)[0] ?? "", splitResultLines(content)[0] ?? ""]]
11680
12208
  }];
11681
12209
  });
11682
12210
  return {
@@ -11689,8 +12217,15 @@ var Ledger = class {
11689
12217
  listed: [],
11690
12218
  recalled: /* @__PURE__ */ new Map()
11691
12219
  });
11692
- const result = gathered.listed.length === 0 ? `nothing on "${topic}"; recall ${guidance}` : cutListing(gathered.listed, room);
11693
- request?.list(result, gathered.recalled);
12220
+ const earlier = input.messages.slice(0, Math.max(0, start)).reverse().flatMap((message) => {
12221
+ if (gathered.done.has(message.id) || this.#annotations.has(message.id)) return [];
12222
+ const label = this.#requests.has(message.id) ? LEDGER_RECALL_LABELS.request : message.role === "assistant" && !hasCalls(message) && message.content.trim() !== "" ? LEDGER_RECALL_LABELS.reply : void 0;
12223
+ if (label === void 0 || !searches.some((search) => search.words.length > 0 && search.words.every((word) => message.content.toLowerCase().includes(word)))) return [];
12224
+ return [`${label} ${message.content.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, "\n ")}`];
12225
+ });
12226
+ const listed = [...gathered.listed, ...earlier];
12227
+ const result = listed.length === 0 ? `nothing earlier on "${topic}"; answer from the messages you can see` : cutListing(listed, room, earlier.length > 0 ? "more" : "older", gauge === void 0);
12228
+ request?.list(result, gathered.recalled, listed);
11694
12229
  return result;
11695
12230
  } finally {
11696
12231
  if (added) this.#scans.delete(scan);
@@ -11698,15 +12233,58 @@ var Ledger = class {
11698
12233
  }
11699
12234
  #buildDigest(request, entered) {
11700
12235
  this.#flush();
11701
- const lines = [...new Set(this.#collectAfter(entered).flatMap((message) => {
12236
+ const groups = [...new Set(this.#collectAfter(entered).flatMap((message) => {
11702
12237
  const result = this.#results.get(message.id);
11703
12238
  if (message.role !== "tool" || result?.success !== true) return [];
11704
12239
  const recalled = result.name === "recall";
11705
- if (recalled && /^nothing on /.test(message.content)) return [];
11706
- const stored = recalled ? request.lines(message.content) : void 0;
11707
- return splitResultLines(message.content).filter((line) => !recalled || !matchesCutLine(line)).map((line) => stored?.get(line) ?? line);
12240
+ if (recalled && /^nothing earlier on /.test(message.content)) return [];
12241
+ const listing = recalled ? request.listings.findLast((one) => one.result === message.content) : void 0;
12242
+ return (listing?.groups ?? [message.content]).map((group) => splitResultLines(group).map((line) => listing?.lines.get(line) ?? line).join("\n")).filter((group) => group !== "");
11708
12243
  }))];
11709
- return lines.length === 0 ? void 0 : `${LEDGER_NOTES.results}\n${lines.join("\n")}`;
12244
+ const unique = groups.filter((group, at) => !splitResultLines(group).every((line) => groups.slice(0, at).some((earlier) => splitResultLines(earlier).includes(line))));
12245
+ const digest = unique.length === 0 ? void 0 : `${LEDGER_NOTES.results}\n${unique.join("\n")}`;
12246
+ const omitted = /* @__PURE__ */ new Set();
12247
+ const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
12248
+ const allowance = (this.#options.capacity - this.#predict) / rate;
12249
+ const after = this.#collectAfter(entered);
12250
+ const messages = [
12251
+ ...request.plan?.messages ?? request.selection?.messages ?? [],
12252
+ ...after,
12253
+ {
12254
+ id: "cue",
12255
+ role: "user",
12256
+ content: LEDGER_NOTES.cue
12257
+ }
12258
+ ].filter((message) => message.role !== "tool" && !hasCalls(message));
12259
+ const note = digest === void 0 ? [] : [{
12260
+ id: "results",
12261
+ role: "user",
12262
+ content: digest
12263
+ }];
12264
+ if (this.#gauge === void 0 || estimateMessages(stripThinking(this.#agent.context.build({
12265
+ messages: [...messages, ...note],
12266
+ judgments: []
12267
+ }), this.#replay)) <= allowance) return {
12268
+ digest,
12269
+ omitted
12270
+ };
12271
+ const partials = new Set(after.filter((message) => message.role === "assistant" && !hasCalls(message)).map((message) => message.id));
12272
+ const fitted = cutListing(unique, allowance - estimateMessages(stripThinking(this.#agent.context.build({
12273
+ messages: messages.filter((message) => !partials.has(message.id)),
12274
+ judgments: []
12275
+ }), this.#replay)) - estimateTokens(`${LEDGER_NOTES.results}\n`), "more", false);
12276
+ return {
12277
+ digest: fitted === "" ? void 0 : `${LEDGER_NOTES.results}\n${fitted}`,
12278
+ omitted: partials
12279
+ };
12280
+ }
12281
+ #recallRate(request) {
12282
+ const gauge = this.#gauge;
12283
+ if (gauge === void 0) return 1;
12284
+ const calls = request?.calls ?? [];
12285
+ const last = calls.findLast((call) => isFiniteNumber(call.prompt));
12286
+ const measured = last !== void 0 && isFiniteNumber(last.prompt) && last.estimate > 0 && ((last.spared ?? 0) === 0 || (last.spared ?? 0) >= gauge.overhead) ? (last.prompt - Math.max(0, gauge.overhead - (last.spared ?? 0))) / last.estimate : 0;
12287
+ return Math.max(gauge.rate(calls), measured);
11710
12288
  }
11711
12289
  };
11712
12290
  // src/core/ledgers/factories.ts
@@ -11725,6 +12303,6 @@ var Ledger = class {
11725
12303
  function createLedger(provider, options) {
11726
12304
  return new Ledger(provider, options);
11727
12305
  }
11728
- export { AUTHORITY_NOTES, Agent, AgentContext, AgentError, AgentFanout, AgentJobError, AgentJudge, AgentProvider, AgentRegistry, Approver, Authority, CONVERSATION_RECAP_PREFIX, CONVERSATION_ROLES, Channel, Classifier, ContextError, Conversation, ConversationError, ConversationManager, DECISIVE_CATEGORIES, DEFAULT_AGENT_LIMIT, DEFAULT_CONVERSATION_KEEP, DEFAULT_LEDGER_LIMIT, DEFAULT_LEDGER_SHARE, DEFAULT_PROVIDER_TIMEOUT, DEFAULT_RECALL_LIMIT, DEFAULT_RELAY_LIMIT, DETERMINISTIC_JUDGE_ERROR, DatabaseConversationStore, DatabaseMemoStore, Gauge, IMAGE_TOKEN_ESTIMATE, INVALID_RELAY_STATUS, Instruction, InstructionManager, JudgeAbortError, JudgeError, JudgmentManager, LEDGER_CATEGORIES, LEDGER_NOTES, LEDGER_OWNER_PREFIX, LEDGER_QUESTIONS, LEDGER_RULES_KEY, LEDGER_SCALE_DRIFT, LEDGER_SLICE, LEDGER_WORD, Ledger, LedgerError, LedgerRequest, MAX_ERROR_BODY_LENGTH, MEMO_SECTION_HEADER, MESSAGE_ROLES, MESSAGE_TOKEN_OVERHEAD, MemoManager, MemoryConversationStore, MemoryMemoStore, OVERSIZED_RELAY_STATUS, PLACED_CATEGORIES, ProviderAbortError, ProviderError, QUIET_CATEGORIES, REFUSED_RELAY_CONTENT_TYPE, REFUSED_RELAY_STATUS, RELAY_CONTENT_TYPE, RELAY_PROVIDER_MESSAGE, RelayProvider, RelayStream, SCOPE_NOTE, SKILL_SECTION_HEADER, SYSTEM_ONE_PATH, Scope, ScopeManager, SkillManager, SystemOneJudge, THINK_CLOSE, THINK_OPEN, ThinkSplitter, UNAUTHORIZED_RELAY_STATUS, UPSTREAM_RELAY_STATUS, WORKSPACE_SECTION_HEADER, agentAnswerContract, agentAnswerShape, agentChunkToFrame, agentFrameContract, agentFrameShape, agentJobContract, agentJobShape, agentPromptContract, agentPromptShape, agentResultToFrame, agentResultToJSON, agentStreamToFrames, armReaderAbort, assembleResult, attachImages, attachUserImages, buildJudgeResult, buildJudgments, buildLedgerCut, buildLedgerIndex, buildLedgerRenderContext, buildLines, buildProviderHeaders, buildProviderResult, buildRecallMessage, buildRecapMessage, buildRecords, buildRelayRequest, buildRequestMessage, buildSummaryMessage, chargeUsage, collectAmendments, collectExchanges, collectImageData, collectLive, collectNames, collectProjectionIds, collectRegistry, collectToolGroups, computeThinking, conversationSnapshotContract, conversationSnapshotShape, copyJSON, createAgent, createAgentContext, createAgentQueue, createAgentRegistry, createAgentRunner, createApprover, createAuthority, createChannel, createConversation, createConversationManager, createDatabaseConversationStore, createDatabaseMemoStore, createInstruction, createInstructionManager, createLedger, createMemoManager, createMemoryConversationStore, createMemoryMemoStore, createRelay, createRelayProvider, createScope, createScopeManager, createSkillManager, createSystemOneJudge, createThinkSplitter, cutListing, denyCall, describeQuestionRefusal, describeRelayRefusal, describeSnapshotRefusal, estimateMessages, estimateRequest, estimateTokens, extractLiterals, extractQueueOptions, extractSystemOneAnswer, extractSystemOneUsage, extractWords, filterAllowList, findLedgerCall, findLedgerCut, fitSlope, freezeJSON, handleAgentQueueJob, hasCalls, identifyLookup, inferStop, intersectKeys, isAgentError, isAgentJobError, isAuthorityDecision, isAuthorityVerdict, isContextError, isConversation, isConversationError, isConversationSnapshot, isFraction, isJudgeAbortError, isJudgeAnswer, isJudgeEntry, isJudgeError, isJudgeQuestion, isJudgment, isLedgerError, isLedgerQuestion, isLedgerSnapshot, isMemo, isMemoOrigin, isMemoSnapshot, isMessage, isMessageInput, isNonNegativeSafeInteger, isPositiveSafeInteger, isProviderAbortError, isProviderError, isProviderResult, isSection, isSystemOneAnswer, isSystemOneResponse, joinThinking, judgeAnswerContract, judgeAnswerShape, judgeEntryShape, judgeQuestionContract, judgeQuestionShape, judgmentContract, judgmentShape, ledgerSnapshotContract, ledgerSnapshotShape, linkOwners, matchEntities, matchesCutLine, matchesJSON, matchesJudgment, measureAnswer, measureMessage, measureRequest, memoContract, memoShape, memoSnapshotContract, memoSnapshotShape, messageContract, messageInputContract, messageInputShape, messageShape, normalizeArgument, normalizeWord, placeMember, providerRequestContract, providerRequestShape, providerResultContract, providerResultShape, questionToSystemOne, raceSignal, rankLedgerCut, readBodyText, readChunks, readFailure, readJudgeOutcome, readProviderDelta, relayFrameContract, relayFrameShape, releaseReader, removeEntries, renderArguments, renderCauseChain, renderFencedFile, renderIndexEntry, renderJSON, renderLedgerAnnotation, renderLedgerPinned, renderLedgerRecord, renderLookupCall, renderSection, renderStub, renderToolValue, renderTopicNames, requestToRelay, requireAnswer, requireBudget, requireConversation, requireConversationSnapshot, requireCumulative, requireEntry, requireFallback, requireJudgeResult, requireJudgment, requireKeep, requireLimit, requireMessageInput, requireProviderResult, requireRole, requireRules, requireSectionsCap, requireSummary, resolveDecision, resolvePredict, sanitizeToken, sanitizeUsage, scanAmendments, scanJudgmentTies, scanProbability, sectionContract, sectionShape, selectRecords, settleAgentJob, settleToolResult, splitResultLines, splitSentences, splitTopic, splitWords, stripThinking, sumUsage, tokenUsageShape, tokenUsageToJSON, toolCallShape };
12306
+ export { AUTHORITY_NOTES, Agent, AgentContext, AgentError, AgentFanout, AgentJobError, AgentJudge, AgentProvider, AgentRegistry, Approver, Authority, CONVERSATION_RECAP_PREFIX, CONVERSATION_ROLES, Channel, Classifier, ContextError, Conversation, ConversationError, ConversationManager, DECISIVE_CATEGORIES, DEFAULT_AGENT_LIMIT, DEFAULT_CONVERSATION_KEEP, DEFAULT_LEDGER_LIMIT, DEFAULT_LEDGER_SHARE, DEFAULT_PROVIDER_TIMEOUT, DEFAULT_RECALL_LIMIT, DEFAULT_RELAY_LIMIT, DETERMINISTIC_JUDGE_ERROR, DatabaseConversationStore, DatabaseMemoStore, Gauge, IMAGE_TOKEN_ESTIMATE, INVALID_RELAY_STATUS, Instruction, InstructionManager, JudgeAbortError, JudgeError, JudgmentManager, LEDGER_CATEGORIES, LEDGER_NOTES, LEDGER_OWNER_PREFIX, LEDGER_QUESTIONS, LEDGER_RECALL_LABELS, LEDGER_RULES_KEY, LEDGER_SCALE_DRIFT, LEDGER_SLICE, LEDGER_WORD, Ledger, LedgerError, LedgerRequest, MAX_ERROR_BODY_LENGTH, MEMO_SECTION_HEADER, MESSAGE_ROLES, MESSAGE_TOKEN_OVERHEAD, MemoManager, MemoryConversationStore, MemoryMemoStore, OVERSIZED_RELAY_STATUS, PLACED_CATEGORIES, ProviderAbortError, ProviderError, QUIET_CATEGORIES, REFUSED_RELAY_CONTENT_TYPE, REFUSED_RELAY_STATUS, RELAY_CONTENT_TYPE, RELAY_PROVIDER_MESSAGE, RelayProvider, RelayStream, SCOPE_NOTE, SKILL_SECTION_HEADER, SYSTEM_ONE_PATH, Scope, ScopeManager, SkillManager, SystemOneJudge, THINK_CLOSE, THINK_OPEN, ThinkSplitter, UNAUTHORIZED_RELAY_STATUS, UPSTREAM_RELAY_STATUS, WORKSPACE_SECTION_HEADER, agentAnswerContract, agentAnswerShape, agentChunkToFrame, agentFrameContract, agentFrameShape, agentJobContract, agentJobShape, agentPromptContract, agentPromptShape, agentResultToFrame, agentResultToJSON, agentStreamToFrames, armReaderAbort, assembleResult, attachImages, attachUserImages, buildJudgeResult, buildJudgments, buildLedgerCut, buildLedgerIndex, buildLedgerLineKey, buildLedgerRenderContext, buildLines, buildProviderHeaders, buildProviderResult, buildRecallMessage, buildRecapMessage, buildRecords, buildRelayRequest, buildRequestMessage, buildSummaryMessage, chargeUsage, collectAmendments, collectExchanges, collectImageData, collectLedgerEvidence, collectLedgerLines, collectLedgerReplies, collectLive, collectNames, collectProjectionIds, collectRecallMessages, collectRegistry, collectToolGroups, computeThinking, conversationSnapshotContract, conversationSnapshotShape, copyJSON, createAgent, createAgentContext, createAgentQueue, createAgentRegistry, createAgentRunner, createApprover, createAuthority, createChannel, createConversation, createConversationManager, createDatabaseConversationStore, createDatabaseMemoStore, createInstruction, createInstructionManager, createLedger, createMemoManager, createMemoryConversationStore, createMemoryMemoStore, createRelay, createRelayProvider, createScope, createScopeManager, createSkillManager, createSystemOneJudge, createThinkSplitter, cutListing, denyCall, describeQuestionRefusal, describeRelayRefusal, describeSnapshotRefusal, digestLedgerWindow, estimateMessages, estimateRequest, estimateTokens, extractLiterals, extractQueueOptions, extractSystemOneAnswer, extractSystemOneUsage, extractWords, filterAllowList, findLedgerCall, findLedgerCut, fitSlope, freezeJSON, handleAgentQueueJob, hasCalls, hasHiddenRecall, identifyLookup, inferStop, intersectKeys, isAgentError, isAgentJobError, isAuthorityDecision, isAuthorityVerdict, isContextError, isConversation, isConversationError, isConversationSnapshot, isFraction, isJudgeAbortError, isJudgeAnswer, isJudgeEntry, isJudgeError, isJudgeQuestion, isJudgment, isLedgerError, isLedgerQuestion, isLedgerSnapshot, isLedgerWindowSnapshot, isMemo, isMemoOrigin, isMemoSnapshot, isMessage, isMessageInput, isNonNegativeSafeInteger, isPositiveSafeInteger, isProviderAbortError, isProviderError, isProviderResult, isSection, isSystemOneAnswer, isSystemOneResponse, joinThinking, judgeAnswerContract, judgeAnswerShape, judgeEntryShape, judgeQuestionContract, judgeQuestionShape, judgmentContract, judgmentShape, ledgerSnapshotContract, ledgerSnapshotShape, ledgerWindowContract, ledgerWindowShape, linkOwners, matchEntities, matchesCutLine, matchesJSON, matchesJudgment, matchesLookup, measureAnswer, measureMessage, measureRequest, memoContract, memoShape, memoSnapshotContract, memoSnapshotShape, messageContract, messageInputContract, messageInputShape, messageShape, normalizeArgument, normalizeWord, placeMember, providerRequestContract, providerRequestShape, providerResultContract, providerResultShape, questionToSystemOne, raceSignal, rankLedgerCut, readBodyText, readChunks, readFailure, readJudgeOutcome, readProviderDelta, relayFrameContract, relayFrameShape, releaseReader, removeEntries, renderArguments, renderCauseChain, renderFencedFile, renderIndexEntry, renderJSON, renderLedgerAnnotation, renderLedgerPinned, renderLedgerRecord, renderLookupCall, renderSection, renderStub, renderToolValue, renderTopicNames, requestToRelay, requireAnswer, requireBudget, requireConversation, requireConversationSnapshot, requireCumulative, requireEntry, requireFallback, requireJudgeResult, requireJudgment, requireKeep, requireLimit, requireMessageInput, requireProviderResult, requireRole, requireRules, requireSectionsCap, requireSummary, resolveDecision, resolvePredict, sanitizeToken, sanitizeUsage, scanAmendments, scanJudgmentTies, scanProbability, sectionContract, sectionShape, selectRecords, settleAgentJob, settleToolResult, splitResultLines, splitSentences, splitTopic, splitWords, stripThinking, sumUsage, tokenUsageShape, tokenUsageToJSON, toolCallShape };
11729
12307
 
11730
12308
  //# sourceMappingURL=index.js.map