@orkestrel/agent 0.0.37 → 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.
@@ -2756,6 +2756,7 @@ var AgentJudge = class {
2756
2756
  *
2757
2757
  * @remarks
2758
2758
  * The caller supplies the server origin and model. Every question travels in one request.
2759
+ * Wire keys are zero-based positions in question entry order; answers return under the caller's ids.
2759
2760
  * Server measures are ignored; the response model is preserved.
2760
2761
  * `headers` supplies authentication through the shared judge engine.
2761
2762
  * The example readings are those of the response Ollama 0.40.0 returned for that request on 2026-10-07.
@@ -2812,7 +2813,7 @@ var SystemOneJudge = class extends AgentJudge {
2812
2813
  /** Identifies the System One protocol. */
2813
2814
  name = "systemone";
2814
2815
  /**
2815
- * Projects the state and questions onto the System One request with the configured model.
2816
+ * Projects the state and questions onto the System One request with the configured model and positional keys.
2816
2817
  *
2817
2818
  * @param request - The state and questions keyed by caller id
2818
2819
  * @returns The System One request body
@@ -2821,11 +2822,11 @@ var SystemOneJudge = class extends AgentJudge {
2821
2822
  return {
2822
2823
  state: request.state,
2823
2824
  model: this.model,
2824
- questions: Object.fromEntries(Object.entries(request.questions).map(([id, question]) => [id, questionToSystemOne(question)]))
2825
+ questions: Object.fromEntries(Object.values(request.questions).map((question, index) => [String(index), questionToSystemOne(question)]))
2825
2826
  };
2826
2827
  }
2827
2828
  /**
2828
- * Decodes requested System One answers and reports the server model and available usage.
2829
+ * Decodes positional System One answers under caller ids and reports the server model and available usage.
2829
2830
  *
2830
2831
  * @param value - The parsed response body
2831
2832
  * @param request - The questions defining the expected answers
@@ -2835,9 +2836,10 @@ var SystemOneJudge = class extends AgentJudge {
2835
2836
  */
2836
2837
  read(value, request) {
2837
2838
  if (!isSystemOneResponse(value)) throw new JudgeError("PROTOCOL", `judge error: invalid System One response envelope; received ${(0, _orkestrel_contract.preview)(value)}`);
2838
- const entries = Object.entries(request.questions).map(([id, question]) => {
2839
- if (!Object.hasOwn(value.answers, id)) throw new JudgeError("PROTOCOL", `judge error: question ${id} has no System One answer`);
2840
- const wire = value.answers[id];
2839
+ const entries = Object.entries(request.questions).map(([id, question], index) => {
2840
+ const position = String(index);
2841
+ if (!Object.hasOwn(value.answers, position)) throw new JudgeError("PROTOCOL", `judge error: question ${id} has no System One answer`);
2842
+ const wire = value.answers[position];
2841
2843
  if (!isSystemOneAnswer(wire)) throw new JudgeError("PROTOCOL", `judge error: question ${id} has an invalid System One answer; received ${(0, _orkestrel_contract.preview)(wire)}`);
2842
2844
  const answer = extractSystemOneAnswer(wire, question);
2843
2845
  if (answer === void 0) throw new JudgeError("PROTOCOL", `judge error: question ${id} has a mismatched or incomplete System One answer`);
@@ -9833,14 +9835,19 @@ function splitTopic(topic) {
9833
9835
  * Cuts entries to a room and names how many it left out.
9834
9836
  *
9835
9837
  * @remarks
9836
- * The cut keeps at least one entry. When it leaves entries out, its last line reads
9838
+ * By default the cut keeps at least one entry. With `first: false`, it keeps whole entries only
9839
+ * when the joined result, tool framing, and omission line fit, and considers later entries after
9840
+ * an oversized group. When it leaves some entries out, its last line reads
9837
9841
  * `N older items not shown; name a narrower topic to narrow the recall`, which
9838
9842
  * {@link matchesCutLine} recognizes. Pass `more` when the entries aren't in age order, so the line
9839
- * doesn't claim an omitted entry is older than a kept one.
9843
+ * doesn't claim an omitted entry is older than a kept one. A fitted cut also uses `more` when it
9844
+ * skips an entry before a kept one. With no kept entry it reads
9845
+ * `N older items didn't fit in this request; answer from the messages you can see`.
9840
9846
  *
9841
9847
  * @param entries - The entries in the order they are kept
9842
9848
  * @param room - The estimate units the joined entries can take
9843
9849
  * @param kind - What the cut line calls an omitted entry. Default: `older`
9850
+ * @param first - If `true`, keeps the first entry regardless of room; if `false`, fits every entry and the omission line. Default: `true`
9844
9851
  * @returns The kept entries followed by the cut line when any were left out, joined by newlines
9845
9852
  *
9846
9853
  * @example
@@ -9848,7 +9855,30 @@ function splitTopic(topic) {
9848
9855
  * cutListing(['first entry', 'second entry'], 1) // 'first entry\n1 older item not shown; name a narrower topic to narrow the recall'
9849
9856
  * ```
9850
9857
  */
9851
- function cutListing(entries, room, kind = "older") {
9858
+ function cutListing(entries, room, kind = "older", first = true) {
9859
+ if (!first) {
9860
+ if (estimateMessages([{
9861
+ id: "recall",
9862
+ role: "tool",
9863
+ content: entries.join("\n")
9864
+ }]) <= room) return entries.join("\n");
9865
+ const kept = entries.reduce((accepted, entry, at) => {
9866
+ const left = entries.length - accepted.length - 1;
9867
+ const omitted = accepted.length < at ? "more" : kind;
9868
+ return estimateMessages([{
9869
+ id: "recall",
9870
+ role: "tool",
9871
+ content: [
9872
+ ...accepted.map((index) => entries[index]),
9873
+ entry,
9874
+ ...left > 0 ? [`${left} ${omitted} item${left === 1 ? "" : "s"} not shown; name a narrower topic to narrow the recall`] : []
9875
+ ].join("\n")
9876
+ }]) <= room ? [...accepted, at] : accepted;
9877
+ }, []);
9878
+ const left = entries.length - kept.length;
9879
+ const omitted = kept.some((at, index) => at !== index) ? "more" : kind;
9880
+ 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");
9881
+ }
9852
9882
  const stop = [...entries.keys()].findIndex((at) => at > 0 && estimateMessages([{
9853
9883
  id: "recall",
9854
9884
  role: "tool",
@@ -9870,7 +9900,7 @@ function cutListing(entries, room, kind = "older") {
9870
9900
  * ```
9871
9901
  */
9872
9902
  function matchesCutLine(line) {
9873
- return /^\d+ (?:older|more) items? not shown; /.test(line);
9903
+ return /^\d+ (?:older|more) items? (?:not shown; |didn't fit in this request; answer from the messages you can see$)/.test(line);
9874
9904
  }
9875
9905
  /**
9876
9906
  * Renders the tail stub of a lookup result from an earlier request.
@@ -10795,7 +10825,7 @@ var LedgerRequest = class {
10795
10825
  #passes = [];
10796
10826
  #attempts = [];
10797
10827
  #admitted = /* @__PURE__ */ new Set();
10798
- #listed = /* @__PURE__ */ new Map();
10828
+ #listed = [];
10799
10829
  #recalls = 0;
10800
10830
  #closed = false;
10801
10831
  /**
@@ -10932,11 +10962,24 @@ var LedgerRequest = class {
10932
10962
  close() {
10933
10963
  this.#closed = true;
10934
10964
  }
10935
- list(result, lines) {
10936
- this.#listed = new Map([...this.#listed, [result, lines]]);
10965
+ list(result, lines, groups) {
10966
+ const accepted = groups.reduce((kept, group) => {
10967
+ const offset = kept.length === 0 ? 0 : kept.join("\n").length + 1;
10968
+ return result.slice(offset) === group || result.slice(offset).startsWith(`${group}\n`) ? [...kept, group] : kept;
10969
+ }, []);
10970
+ this.#listed = [...this.#listed, {
10971
+ result,
10972
+ lines,
10973
+ groups: accepted,
10974
+ turn: this.calls.length
10975
+ }];
10976
+ }
10977
+ /** Returns recall results with their accepted whole groups and provider-turn positions. */
10978
+ get listings() {
10979
+ return this.#listed;
10937
10980
  }
10938
10981
  lines(result) {
10939
- return this.#listed.get(result);
10982
+ return this.#listed.findLast((listing) => listing.result === result)?.lines;
10940
10983
  }
10941
10984
  };
10942
10985
  // src/core/ledgers/LedgerScan.ts
@@ -11459,10 +11502,10 @@ var Ledger = class {
11459
11502
  });
11460
11503
  const passes = [first];
11461
11504
  if (!caller.aborted && (first.partial || first.content.trim() === "")) {
11462
- const digest = this.#buildDigest(request, entered);
11463
- if (digest !== void 0) this.#annotations.add(this.#conversation.add({
11505
+ const recovery = this.#buildDigest(request, entered);
11506
+ if (recovery.digest !== void 0) this.#annotations.add(this.#conversation.add({
11464
11507
  role: "user",
11465
- content: digest
11508
+ content: recovery.digest
11466
11509
  }).id);
11467
11510
  this.#annotations.add(this.#conversation.add({
11468
11511
  role: "user",
@@ -11478,7 +11521,7 @@ var Ledger = class {
11478
11521
  if (selection.fault !== void 0) return selection;
11479
11522
  return {
11480
11523
  ...selection,
11481
- messages: selection.messages.filter((one) => one.role !== "tool" && !hasCalls(one))
11524
+ messages: selection.messages.filter((one) => one.role !== "tool" && !hasCalls(one) && !recovery.omitted.has(one.id))
11482
11525
  };
11483
11526
  }
11484
11527
  });
@@ -12111,11 +12154,21 @@ var Ledger = class {
12111
12154
  request?.admit("recall", { topic });
12112
12155
  const gauge = this.#gauge;
12113
12156
  const calls = request?.calls ?? [];
12114
- if (gauge !== void 0 && calls.length > 0 && gauge.remainder(calls) - this.#predict < 2 * gauge.reserve(calls, this.#findLongest())) request?.close();
12115
12157
  request?.recall();
12116
12158
  const guidance = `an owner name, an id, or one of ${renderTopicNames(this.#options.topics)}`;
12117
12159
  if (topic === "") throw new Error(`recall needs a topic: ${guidance}`);
12118
- const room = (gauge?.room(calls, this.#findLongest()) ?? 0) - estimateMessages([buildRecallMessage(topic)]);
12160
+ const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
12161
+ const call = calls.at(-1);
12162
+ const carried = request === void 0 ? 0 : estimateMessages(stripThinking([...this.#agent.context.build(request.selection), ...this.#conversation.messages().slice(request.boundary)], this.#replay));
12163
+ const completion = (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : 0;
12164
+ const extra = Math.max(0, (carried - (call?.estimate ?? carried)) * rate - completion);
12165
+ const drift = (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? 0 : Math.max(0, (call?.estimate ?? 0) * (rate - (gauge?.scale ?? rate)));
12166
+ const siblings = estimateMessages((request?.listings ?? []).filter((listing) => listing.turn === calls.length).map((listing) => ({
12167
+ id: "result",
12168
+ role: "tool",
12169
+ content: listing.result
12170
+ })));
12171
+ const room = gauge === void 0 ? -estimateMessages([buildRecallMessage(topic)]) : (gauge.remainder(calls) - this.#predict - extra - drift) / rate - siblings;
12119
12172
  const scan = this.#scan(this.#conversation.messages());
12120
12173
  const added = this.#scans.size === 0;
12121
12174
  if (added) this.#hold(scan);
@@ -12172,8 +12225,8 @@ var Ledger = class {
12172
12225
  return [`${label} ${message.content.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, "\n ")}`];
12173
12226
  });
12174
12227
  const listed = [...gathered.listed, ...earlier];
12175
- const result = listed.length === 0 ? `nothing earlier on "${topic}"; answer from the messages you can see` : cutListing(listed, room, earlier.length > 0 ? "more" : "older");
12176
- request?.list(result, gathered.recalled);
12228
+ 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);
12229
+ request?.list(result, gathered.recalled, listed);
12177
12230
  return result;
12178
12231
  } finally {
12179
12232
  if (added) this.#scans.delete(scan);
@@ -12181,15 +12234,58 @@ var Ledger = class {
12181
12234
  }
12182
12235
  #buildDigest(request, entered) {
12183
12236
  this.#flush();
12184
- const lines = [...new Set(this.#collectAfter(entered).flatMap((message) => {
12237
+ const groups = [...new Set(this.#collectAfter(entered).flatMap((message) => {
12185
12238
  const result = this.#results.get(message.id);
12186
12239
  if (message.role !== "tool" || result?.success !== true) return [];
12187
12240
  const recalled = result.name === "recall";
12188
12241
  if (recalled && /^nothing earlier on /.test(message.content)) return [];
12189
- const stored = recalled ? request.lines(message.content) : void 0;
12190
- return splitResultLines(message.content).filter((line) => !recalled || !matchesCutLine(line)).map((line) => stored?.get(line) ?? line);
12242
+ const listing = recalled ? request.listings.findLast((one) => one.result === message.content) : void 0;
12243
+ return (listing?.groups ?? [message.content]).map((group) => splitResultLines(group).map((line) => listing?.lines.get(line) ?? line).join("\n")).filter((group) => group !== "");
12191
12244
  }))];
12192
- return lines.length === 0 ? void 0 : `${LEDGER_NOTES.results}\n${lines.join("\n")}`;
12245
+ const unique = groups.filter((group, at) => !splitResultLines(group).every((line) => groups.slice(0, at).some((earlier) => splitResultLines(earlier).includes(line))));
12246
+ const digest = unique.length === 0 ? void 0 : `${LEDGER_NOTES.results}\n${unique.join("\n")}`;
12247
+ const omitted = /* @__PURE__ */ new Set();
12248
+ const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
12249
+ const allowance = (this.#options.capacity - this.#predict) / rate;
12250
+ const after = this.#collectAfter(entered);
12251
+ const messages = [
12252
+ ...request.plan?.messages ?? request.selection?.messages ?? [],
12253
+ ...after,
12254
+ {
12255
+ id: "cue",
12256
+ role: "user",
12257
+ content: LEDGER_NOTES.cue
12258
+ }
12259
+ ].filter((message) => message.role !== "tool" && !hasCalls(message));
12260
+ const note = digest === void 0 ? [] : [{
12261
+ id: "results",
12262
+ role: "user",
12263
+ content: digest
12264
+ }];
12265
+ if (this.#gauge === void 0 || estimateMessages(stripThinking(this.#agent.context.build({
12266
+ messages: [...messages, ...note],
12267
+ judgments: []
12268
+ }), this.#replay)) <= allowance) return {
12269
+ digest,
12270
+ omitted
12271
+ };
12272
+ const partials = new Set(after.filter((message) => message.role === "assistant" && !hasCalls(message)).map((message) => message.id));
12273
+ const fitted = cutListing(unique, allowance - estimateMessages(stripThinking(this.#agent.context.build({
12274
+ messages: messages.filter((message) => !partials.has(message.id)),
12275
+ judgments: []
12276
+ }), this.#replay)) - estimateTokens(`${LEDGER_NOTES.results}\n`), "more", false);
12277
+ return {
12278
+ digest: fitted === "" ? void 0 : `${LEDGER_NOTES.results}\n${fitted}`,
12279
+ omitted: partials
12280
+ };
12281
+ }
12282
+ #recallRate(request) {
12283
+ const gauge = this.#gauge;
12284
+ if (gauge === void 0) return 1;
12285
+ const calls = request?.calls ?? [];
12286
+ const last = calls.findLast((call) => (0, _orkestrel_contract.isFiniteNumber)(call.prompt));
12287
+ const measured = last !== void 0 && (0, _orkestrel_contract.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;
12288
+ return Math.max(gauge.rate(calls), measured);
12193
12289
  }
12194
12290
  };
12195
12291
  // src/core/ledgers/factories.ts