@orkestrel/agent 0.0.41 → 0.0.43

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.
@@ -1,14 +1,14 @@
1
- import { ContractError, INTRINSICS, arrayOf, arrayShape, attempt, booleanShape, boundsOf, canonicalStringify, cloneJSONValue, createContract, describeFaults, errorToMessage, failure, holds, isArray, isBoolean, isError, isFiniteNumber, isFunction, isJSONValue, isNumber, isObject, isPromiseLike, isRecord, isString, jsonShape, literalShape, nullableOf, nullableShape, numberShape, objectOf, objectShape, optionalOf, optionalShape, parseJSON, parseJSONAs, parseJSONValue, preview, rawShape, recordOf, recordShape, schemaToShape, stringShape, success, unionOf, unionShape } from "@orkestrel/contract";
1
+ import { ContractError, INTRINSICS, admitValue, arrayOf, arrayShape, attempt, booleanShape, boundsOf, canonicalStringify, cloneJSONValue, createContract, describeFaults, errorToMessage, failure, holds, integerShape, isArray, isBoolean, isError, isFiniteNumber, isFunction, isJSONValue, isNumber, isObject, isPromiseLike, isRecord, isString, jsonShape, literalOf, literalShape, matchesExactKeys, nullableOf, nullableShape, numberShape, objectOf, objectShape, optionalOf, optionalShape, parseJSON, parseJSONAs, parseJSONValue, preview, rawShape, recordOf, recordShape, refuseOptionKeys, schemaToParameters, schemaToShape, stringShape, success, unionOf, unionShape } from "@orkestrel/contract";
2
2
  import { createTokenBudget, createTokenMeter, isBudget, isBudgetAmount, isBudgetError, isTokenUsage } from "@orkestrel/budget";
3
- import { ToolManager, createTool, createToolManager, isToolCall, isToolError } from "@orkestrel/tool";
3
+ import { ToolError, ToolManager, createTool, createToolManager, isToolCall, isToolError } from "@orkestrel/tool";
4
4
  import { createNDJSONParser } from "@orkestrel/ndjson";
5
- import { Timeout, createTimeout } from "@orkestrel/timeout";
5
+ import { createAbort, linkSignal } from "@orkestrel/abort";
6
+ import { MAX_TIMEOUT_MS, Timeout, createTimeout } from "@orkestrel/timeout";
6
7
  import { createDatabase, createMemoryDriver } from "@orkestrel/database";
7
8
  import { Emitter } from "@orkestrel/emitter";
8
9
  import { WorkspaceManager, createFile, isBinary, isTextContent } from "@orkestrel/workspace";
9
10
  import { createQueue } from "@orkestrel/queue";
10
11
  import { createRunner } from "@orkestrel/workflow";
11
- import { createAbort } from "@orkestrel/abort";
12
12
  // src/core/constants.ts
13
13
  /**
14
14
  * Lists the roles any message can play, the built `system` head included, in the order the wire
@@ -725,6 +725,23 @@ function collectToolGroups(messages) {
725
725
  return [...groups, ...orphans];
726
726
  }
727
727
  /**
728
+ * Finds the call that owns a tool message within a collected tool group.
729
+ * @param group - The assistant leader followed by its tool results, as {@link collectToolGroups} returns it
730
+ * @param message - The tool result to pair; distinct leader ids pair by result id when any result has one
731
+ * @returns The call matching the result's call id, or its position when the leader repeats an id or every result lacks an id; undefined when unpaired
732
+ * @example
733
+ * ```ts
734
+ * findToolCall(group, result)?.arguments
735
+ * ```
736
+ */
737
+ function findToolCall(group, message) {
738
+ const [leader, ...results] = group;
739
+ const at = results.findIndex((result) => result.id === message.id);
740
+ if (message.role !== "tool" || at < 0) return void 0;
741
+ const calls = leader?.calls ?? [];
742
+ return !(new Set(calls.map((call) => call.id)).size !== calls.length) && results.some((result) => result.call !== void 0) ? calls.find((call) => call.id === message.call) : calls[at];
743
+ }
744
+ /**
728
745
  * Renders a value as the JSON text `JSON.stringify` produces, or `undefined` when JSON cannot
729
746
  * carry the value.
730
747
  *
@@ -1022,6 +1039,60 @@ var REFUSED_RELAY_STATUS = 422;
1022
1039
  var REFUSED_RELAY_CONTENT_TYPE = "text/plain; charset=utf-8";
1023
1040
  /** Names the System One decision endpoint shared by compatible servers. */
1024
1041
  var SYSTEM_ONE_PATH = "/v1/systemone";
1042
+ /** Lists the members `RelayOptions` declares, frozen — the keys `createRelay` admits. */
1043
+ var RELAY_OPTION_KEYS = Object.freeze([
1044
+ "provider",
1045
+ "authorize",
1046
+ "limit",
1047
+ "system",
1048
+ "tools"
1049
+ ]);
1050
+ /** Lists the members `RelayProviderOptions` declares, frozen — the keys the `RelayProvider` constructor admits. */
1051
+ var RELAY_PROVIDER_KEYS = Object.freeze([
1052
+ "url",
1053
+ "parser",
1054
+ "timeout",
1055
+ "replay",
1056
+ "fetch",
1057
+ "headers"
1058
+ ]);
1059
+ /** Lists the members `RelayStreamOptions` declares, frozen — the keys the `RelayStream` constructor admits. */
1060
+ var RELAY_STREAM_KEYS = Object.freeze([
1061
+ "provider",
1062
+ "request",
1063
+ "signal"
1064
+ ]);
1065
+ /** Lists the members `AgentProviderInput` declares, frozen — the keys the `AgentProvider` constructor admits. */
1066
+ var PROVIDER_INPUT_KEYS = Object.freeze([
1067
+ "url",
1068
+ "path",
1069
+ "split",
1070
+ "strict",
1071
+ "timeout",
1072
+ "replay",
1073
+ "fetch",
1074
+ "headers"
1075
+ ]);
1076
+ /** Lists the members `AgentJudgeInput` declares, frozen — the keys the `AgentJudge` constructor admits. */
1077
+ var JUDGE_INPUT_KEYS = Object.freeze([
1078
+ "url",
1079
+ "path",
1080
+ "model",
1081
+ "batch",
1082
+ "timeout",
1083
+ "fetch",
1084
+ "headers"
1085
+ ]);
1086
+ /** Lists the members `SystemOneJudgeOptions` declares, frozen — the keys the `SystemOneJudge` constructor admits. */
1087
+ var SYSTEM_ONE_OPTION_KEYS = Object.freeze([
1088
+ "url",
1089
+ "model",
1090
+ "timeout",
1091
+ "fetch",
1092
+ "headers"
1093
+ ]);
1094
+ /** Lists the members `ProviderStreamOptions` declares, frozen — the keys the `generate` and `stream` methods of an `AgentProvider` admits. */
1095
+ var STREAM_OPTION_KEYS = Object.freeze(["think", "schema"]);
1025
1096
  // src/core/providers/errors.ts
1026
1097
  /**
1027
1098
  * Reports a provider stream cancelled mid-flight by its bound signal — thrown by the
@@ -2038,6 +2109,7 @@ var RelayStream = class {
2038
2109
  #response;
2039
2110
  #settled = false;
2040
2111
  constructor(options) {
2112
+ refuseOptionKeys(options, RELAY_STREAM_KEYS, "RelayStream", "RelayStreamOptions");
2041
2113
  this.#replay = options.provider.replay ?? "none";
2042
2114
  this.#signal = options.signal;
2043
2115
  this.#listener = this.#abort.bind(this);
@@ -2304,6 +2376,7 @@ var AgentProvider = class {
2304
2376
  #split;
2305
2377
  #strict;
2306
2378
  constructor(input) {
2379
+ refuseOptionKeys(input, PROVIDER_INPUT_KEYS, "AgentProvider", "AgentProviderInput");
2307
2380
  this.#id = crypto.randomUUID();
2308
2381
  this.#replay = input.replay ?? "none";
2309
2382
  this.#url = input.url;
@@ -2351,10 +2424,11 @@ var AgentProvider = class {
2351
2424
  * is malformed or carries an upstream failure, or a strict stream ends without a settled result
2352
2425
  */
2353
2426
  async *stream(messages, signal, tools, options) {
2427
+ refuseOptionKeys(options, STREAM_OPTION_KEYS, "AgentProvider", "ProviderStreamOptions");
2354
2428
  if (signal.aborted) throw new ProviderAbortError({ content: "" });
2355
2429
  const timeout = new Timeout({ ms: this.#timeout });
2356
2430
  timeout.start();
2357
- const combined = AbortSignal.any([timeout.signal, signal]);
2431
+ const combined = linkSignal(timeout.signal, signal);
2358
2432
  try {
2359
2433
  const parser = this.#frame(combined);
2360
2434
  try {
@@ -2506,6 +2580,7 @@ var AgentProvider = class {
2506
2580
  var RelayProvider = class extends AgentProvider {
2507
2581
  #parser;
2508
2582
  constructor(options) {
2583
+ refuseOptionKeys(options, RELAY_PROVIDER_KEYS, "RelayProvider", "RelayProviderOptions");
2509
2584
  const { url, timeout, fetch, headers, replay } = options;
2510
2585
  super({
2511
2586
  url,
@@ -2638,6 +2713,7 @@ var AgentJudge = class {
2638
2713
  #headers;
2639
2714
  #batch;
2640
2715
  constructor(input) {
2716
+ refuseOptionKeys(input, JUDGE_INPUT_KEYS, "AgentJudge", "AgentJudgeInput");
2641
2717
  this.#id = crypto.randomUUID();
2642
2718
  this.#url = input.url;
2643
2719
  this.#path = input.path ?? "";
@@ -2702,7 +2778,7 @@ var AgentJudge = class {
2702
2778
  async #call(request, body, signal, completed) {
2703
2779
  const timeout = new Timeout({ ms: this.#timeout });
2704
2780
  timeout.start();
2705
- const combined = AbortSignal.any([timeout.signal, signal]);
2781
+ const combined = linkSignal(timeout.signal, signal);
2706
2782
  try {
2707
2783
  const headers = await buildProviderHeaders(this.#headers, combined);
2708
2784
  combined.throwIfAborted();
@@ -2803,6 +2879,7 @@ var AgentJudge = class {
2803
2879
  */
2804
2880
  var SystemOneJudge = class extends AgentJudge {
2805
2881
  constructor(options) {
2882
+ refuseOptionKeys(options, SYSTEM_ONE_OPTION_KEYS, "SystemOneJudge", "SystemOneJudgeOptions");
2806
2883
  super({
2807
2884
  ...options,
2808
2885
  path: SYSTEM_ONE_PATH,
@@ -2902,6 +2979,7 @@ var SystemOneJudge = class extends AgentJudge {
2902
2979
  * ```
2903
2980
  */
2904
2981
  function createRelay(options) {
2982
+ refuseOptionKeys(options, RELAY_OPTION_KEYS, "createRelay", "RelayOptions");
2905
2983
  const { provider, authorize, limit } = options;
2906
2984
  const system = { ...options.system };
2907
2985
  const { definitions, accept } = options.tools ?? {};
@@ -3056,6 +3134,43 @@ var DEFAULT_CONVERSATION_KEEP = 0;
3056
3134
  * because it interpolates the per-call provenance `label`).
3057
3135
  */
3058
3136
  var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
3137
+ /** Lists the members `ConversationOptions` declares, frozen — the keys the `Conversation` constructor admits. */
3138
+ var CONVERSATION_OPTION_KEYS = Object.freeze([
3139
+ "id",
3140
+ "on",
3141
+ "error",
3142
+ "summarize",
3143
+ "keep",
3144
+ "sections",
3145
+ "cumulative",
3146
+ "snapshot"
3147
+ ]);
3148
+ /** Lists the members `ConversationManagerOptions` declares, frozen — the keys the `ConversationManager` constructor admits. */
3149
+ var CONVERSATION_MANAGER_KEYS = Object.freeze([
3150
+ "summarize",
3151
+ "keep",
3152
+ "sections",
3153
+ "store"
3154
+ ]);
3155
+ /** Lists the members `ConversationInput` declares, frozen — the keys the `add` method of a `ConversationManager` admits. */
3156
+ var CONVERSATION_INPUT_KEYS = Object.freeze([
3157
+ "id",
3158
+ "summarize",
3159
+ "keep",
3160
+ "sections",
3161
+ "cumulative",
3162
+ "on",
3163
+ "error",
3164
+ "snapshot"
3165
+ ]);
3166
+ /** Lists the members `CompactOptions` declares, frozen — the keys the `compact` method of a `Conversation` admits. */
3167
+ var COMPACT_OPTION_KEYS = Object.freeze([
3168
+ "keep",
3169
+ "sections",
3170
+ "signal"
3171
+ ]);
3172
+ /** Lists the members `ConversationReferenceOptions` declares, frozen — the keys the `reference` method of a `Conversation` admits. */
3173
+ var REFERENCE_OPTION_KEYS = Object.freeze(["label", "messages"]);
3059
3174
  // src/core/conversations/errors.ts
3060
3175
  /**
3061
3176
  * Reports a conversation with no {@link ConversationSummaryHandler} to fold its messages with, a
@@ -3081,7 +3196,8 @@ var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
3081
3196
  * `'SUMMARY'`. A stored message plays a `ConversationRole`: `add` refuses any other role with
3082
3197
  * `'ROLE'` before it stores any message of the batch, a hydrating snapshot that holds one throws
3083
3198
  * `'ROLE'`, and an agent's selection that holds one becomes a `'ROLE'` fault. `add` refuses an input
3084
- * whose other members fail `isMessageInput` with `'MESSAGE'`, its `context` holding the `faults`,
3199
+ * that is not a plain record, before its role is read, or whose other members fail `isMessageInput`
3200
+ * with `'MESSAGE'`, its `context` holding the `faults`,
3085
3201
  * before it stores any message of the batch. A hydrating snapshot that fails
3086
3202
  * `isConversationSnapshot` or repeats an id, and a store row or written snapshot that does, throw
3087
3203
  * `'SNAPSHOT'`; a judgment the guard admits but JSON cannot carry still throws `'JUDGMENT'`. A
@@ -3089,7 +3205,7 @@ var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
3089
3205
  * {@link isConversationError} and branch on `error.code`.
3090
3206
  */
3091
3207
  var ConversationError = class extends Error {
3092
- /** Names the machine-readable condition — `'SUMMARIZER'`: a `compact()` with no summarizer; `'SECTIONS'`: a `sections` cap that is not a positive safe integer, or a cap combined with `cumulative`; `'JUDGMENT'`: a judgment or judge request that is not JSON; `'KEEP'`: a `keep` that is not a nonnegative safe integer; `'SUMMARY'`: a summarizer digest whose trimmed text is empty; `'ROLE'`: a stored or selected message whose role is not a `ConversationRole`; `'MESSAGE'`: a message input whose other members fail `isMessageInput`; `'SNAPSHOT'`: a snapshot that fails `isConversationSnapshot` or repeats an id; `'ID'`: a manager's `add` of an id it already holds. */
3208
+ /** Names the machine-readable condition — `'SUMMARIZER'`: a `compact()` with no summarizer; `'SECTIONS'`: a `sections` cap that is not a positive safe integer, or a cap combined with `cumulative`; `'JUDGMENT'`: a judgment or judge request that is not JSON; `'KEEP'`: a `keep` that is not a nonnegative safe integer; `'SUMMARY'`: a summarizer digest whose trimmed text is empty; `'ROLE'`: a stored or selected message whose role is not a `ConversationRole`; `'MESSAGE'`: a message input that is not a plain record or whose other members fail `isMessageInput`; `'SNAPSHOT'`: a snapshot that fails `isConversationSnapshot` or repeats an id; `'ID'`: a manager's `add` of an id it already holds. */
3093
3209
  code;
3094
3210
  /** Holds the values that locate the failure, such as the `question` id of a malformed question, or `undefined` when none were supplied. */
3095
3211
  context;
@@ -3224,13 +3340,17 @@ function isMessage(value) {
3224
3340
  return checked.success && checked.value;
3225
3341
  }
3226
3342
  /**
3227
- * Checks whether a value is a message input a conversation can store: a string `content`, and `calls`,
3228
- * `call`, `images`, and `thinking` absent or of their declared types.
3343
+ * Checks whether a value is a message input a conversation can store: a `ConversationRole`, a string
3344
+ * `content`, and `calls`, `call`, `images`, and `thinking` absent or of their declared types.
3229
3345
  *
3230
3346
  * @remarks
3231
- * A hand guard, because `add` checks every message: it reads the members once and composes no record
3232
- * guard. The role is `requireRole`'s to refuse, so its text can name the role received. Unknown members
3233
- * are left behind by the store. An unreadable member returns false.
3347
+ * An exact record, because a stored message is one: a value that is not a plain record, such as a
3348
+ * class instance or an object that inherits its `role`, returns false through `recordOf`'s own plain
3349
+ * record check, as does a member `MessageInput` does not declare or a declared optional member holding
3350
+ * `undefined`. Each tool call crosses `isToolCall` of
3351
+ * `@orkestrel/tool`, whose interface admits more members, so the store keeps `id`, `name`, and
3352
+ * `arguments` and leaves the rest. `add` reads the role through `requireRole` first, so its text can
3353
+ * name the role received. An unreadable member returns false.
3234
3354
  *
3235
3355
  * @param value - The unknown input candidate
3236
3356
  * @returns True if the input's members are valid; false otherwise
@@ -3240,14 +3360,19 @@ function isMessage(value) {
3240
3360
  * isMessageInput({ role: 'user' }) // false
3241
3361
  * ```
3242
3362
  */
3243
- function isMessageInput(value) {
3244
- const checked = attempt(() => {
3245
- if (!isRecord(value)) return false;
3246
- const { content, calls, call, images, thinking } = value;
3247
- return isString(content) && (calls === void 0 || arrayOf(isToolCall)(calls)) && (call === void 0 || isString(call)) && (images === void 0 || arrayOf(isString)(images)) && (thinking === void 0 || isString(thinking));
3248
- });
3249
- return checked.success && checked.value;
3250
- }
3363
+ var isMessageInput = recordOf({
3364
+ role: literalOf(CONVERSATION_ROLES),
3365
+ content: isString,
3366
+ calls: arrayOf(isToolCall),
3367
+ call: isString,
3368
+ images: arrayOf(isString),
3369
+ thinking: isString
3370
+ }, [
3371
+ "calls",
3372
+ "call",
3373
+ "images",
3374
+ "thinking"
3375
+ ]);
3251
3376
  /**
3252
3377
  * Checks whether a stored judgment carries a valid question and exactly one answer or refusal.
3253
3378
  *
@@ -3559,14 +3684,17 @@ function requireRole(role) {
3559
3684
  * Returns a message input after checking its role and its members, or throws a coded error.
3560
3685
  *
3561
3686
  * @remarks
3562
- * The role goes first, through {@link requireRole}, so its refusal names what it received. The members
3563
- * cross {@link isMessageInput}, and `audit` runs only after it refused, so a stored message costs one
3564
- * guard read. Tool-call arguments must also be JSON values before a message is stored.
3687
+ * A value that is not a plain record, such as a class instance or an object that inherits its
3688
+ * `role`, refuses first, through `isRecord` of `@orkestrel/contract`, with a text saying so. The role
3689
+ * goes next, through {@link requireRole}, so its refusal names what it received. The members
3690
+ * cross {@link isMessageInput}, an exact record guard, so a member `MessageInput` does not declare or a
3691
+ * declared member holding `undefined` refuses, and `audit` runs only after a refusal. Tool-call
3692
+ * arguments must also be JSON values before a message is stored.
3565
3693
  *
3566
3694
  * @param input - The message input to check
3567
3695
  * @param contract - The message input contract, whose `audit` is read only on a refusal
3568
3696
  * @returns The input, narrowed
3569
- * @throws ConversationError Thrown when the role is not a `ConversationRole` (code `'ROLE'`) or a member fails `isMessageInput` (code `'MESSAGE'`), with the faults under `context.faults`
3697
+ * @throws ConversationError Thrown when the input is not a plain record (code `'MESSAGE'`), when the role is not a `ConversationRole` (code `'ROLE'`), or when a member fails `isMessageInput`, an undeclared member included (code `'MESSAGE'`), with the faults under `context.faults`
3570
3698
  *
3571
3699
  * @example
3572
3700
  * ```ts
@@ -3575,10 +3703,11 @@ function requireRole(role) {
3575
3703
  * ```
3576
3704
  */
3577
3705
  function requireMessageInput(input, contract) {
3578
- requireRole(isRecord(input) ? input.role : void 0);
3706
+ if (!isRecord(input)) throw new ConversationError("MESSAGE", "a message input must be a plain record, an object literal or parsed JSON holding its role and content as its own members, not a class instance or an object that inherits them", { context: { faults: contract.audit(input) } });
3707
+ requireRole(input.role);
3579
3708
  if (isMessageInput(input) && (input.calls === void 0 || input.calls.every((call) => isJSONValue(call.arguments)))) return input;
3580
3709
  const faults = contract.audit(input);
3581
- throw new ConversationError("MESSAGE", `a message input must carry string content, and calls, call, images, and thinking of their declared types\n${describeFaults(faults, contract.schema)}`, { context: { faults } });
3710
+ throw new ConversationError("MESSAGE", `a message input must carry string content, calls, call, images, and thinking of their declared types, and no other member\n${describeFaults(faults, contract.schema)}`, { context: { faults } });
3582
3711
  }
3583
3712
  /**
3584
3713
  * Returns a conversation snapshot after checking its shape, its stored roles, and its ids, or
@@ -3956,6 +4085,7 @@ var Conversation = class {
3956
4085
  #judgments;
3957
4086
  #pending = /* @__PURE__ */ new Set();
3958
4087
  constructor(options) {
4088
+ refuseOptionKeys(options, CONVERSATION_OPTION_KEYS, "Conversation", "ConversationOptions");
3959
4089
  const supplied = options?.snapshot;
3960
4090
  const snapshot = supplied === void 0 ? void 0 : requireConversationSnapshot(supplied, conversationSnapshotContract);
3961
4091
  this.#judgments = new JudgmentManager(snapshot?.judgments);
@@ -4011,6 +4141,7 @@ var Conversation = class {
4011
4141
  return [...this.#sections.map((section) => buildRecapMessage(section)), ...this.#messages.values()];
4012
4142
  }
4013
4143
  async compact(options) {
4144
+ refuseOptionKeys(options, COMPACT_OPTION_KEYS, "Conversation", "CompactOptions");
4014
4145
  const summarize = this.#summarize;
4015
4146
  if (summarize === void 0) throw new ConversationError("SUMMARIZER", "cannot compact a conversation without a summarizer");
4016
4147
  const cap = requireSectionsCap(options?.sections ?? this.#cap);
@@ -4035,6 +4166,7 @@ var Conversation = class {
4035
4166
  return [...this.#sections.flatMap((section) => section.messages), ...this.#messages.values()].filter((message) => message.content.toLowerCase().includes(needle));
4036
4167
  }
4037
4168
  reference(options) {
4169
+ refuseOptionKeys(options, REFERENCE_OPTION_KEYS, "Conversation", "ConversationReferenceOptions");
4038
4170
  const label = options?.label ?? this.#id;
4039
4171
  const messages = options?.messages ?? [];
4040
4172
  return [`[Reference — conversation "${label}" — NOT part of this conversation]`, ...messages.length > 0 ? ["Relevant messages:", ...Array.from(messages, (message) => `- ${message.role}: ${message.content}`)] : []].join("\n");
@@ -4161,6 +4293,7 @@ var ConversationManager = class {
4161
4293
  #sections;
4162
4294
  #store;
4163
4295
  constructor(options) {
4296
+ refuseOptionKeys(options, CONVERSATION_MANAGER_KEYS, "ConversationManager", "ConversationManagerOptions");
4164
4297
  this.#summarize = options?.summarize;
4165
4298
  this.#keep = requireKeep(options?.keep ?? 0);
4166
4299
  this.#sections = options?.sections;
@@ -4179,6 +4312,7 @@ var ConversationManager = class {
4179
4312
  return [...this.#conversations.values()];
4180
4313
  }
4181
4314
  add(input) {
4315
+ refuseOptionKeys(input, CONVERSATION_INPUT_KEYS, "ConversationManager", "ConversationInput");
4182
4316
  const held = input?.id ?? input?.snapshot?.id;
4183
4317
  if (held !== void 0 && this.#conversations.has(held)) throw new ConversationError("ID", `conversation ${preview(held)} is already live; pick another id, or remove it first`, { context: { conversation: held } });
4184
4318
  const sections = input?.sections ?? this.#sections;
@@ -4642,6 +4776,16 @@ function createDatabaseConversationStore(driver = createMemoryDriver()) {
4642
4776
  */
4643
4777
  var WORKSPACE_SECTION_HEADER = "## Workspace";
4644
4778
  /**
4779
+ * Names the section header the loaded skills' bodies render under at the end of the system
4780
+ * message — `'## Loaded skills'`.
4781
+ */
4782
+ var LOADED_SECTION_HEADER = "## Loaded skills";
4783
+ /**
4784
+ * Names the section header the admitted workspace ids render under in the user context message —
4785
+ * `'## Workspaces'`, rendered only while a run can select a workspace.
4786
+ */
4787
+ var WORKSPACE_INDEX_HEADER = "## Workspaces";
4788
+ /**
4645
4789
  * Names the built-in section header a {@link import('./types.js').MemoManagerInterface} renders
4646
4790
  * its index under — `'## Memos'`, the `open` value when no `format.open` overrides it.
4647
4791
  *
@@ -4659,6 +4803,80 @@ var MEMO_SECTION_HEADER = "## Memos";
4659
4803
  * {@link import('./helpers.js').renderIndexEntry}.
4660
4804
  */
4661
4805
  var SKILL_SECTION_HEADER = "## Skills";
4806
+ /**
4807
+ * Lists the members `ScopeInput` declares, frozen — the keys a `Scope` is built from.
4808
+ *
4809
+ * @remarks
4810
+ * The `Scope` constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an
4811
+ * own enumerable member outside it, a misspelled list included, refuses with a `ContractError`
4812
+ * coded `'structure'` instead of building a scope with no constraint.
4813
+ */
4814
+ var SCOPE_KEYS = Object.freeze([
4815
+ "name",
4816
+ "select",
4817
+ "instructions",
4818
+ "skills",
4819
+ "memos",
4820
+ "tools",
4821
+ "files",
4822
+ "workspaces"
4823
+ ]);
4824
+ /**
4825
+ * Lists the members `ScopeFilter` declares, frozen — the keys the `narrow` method of a scope admits.
4826
+ *
4827
+ * @remarks
4828
+ * The `narrow` method passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
4829
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
4830
+ */
4831
+ var SCOPE_FILTER_KEYS = Object.freeze([
4832
+ "instructions",
4833
+ "skills",
4834
+ "memos",
4835
+ "tools",
4836
+ "files",
4837
+ "workspaces"
4838
+ ]);
4839
+ /** Lists the members `AgentContextOptions` declares, frozen — the keys the `AgentContext` constructor admits. */
4840
+ var CONTEXT_OPTION_KEYS = Object.freeze([
4841
+ "system",
4842
+ "tools",
4843
+ "instructions",
4844
+ "skills",
4845
+ "memos",
4846
+ "workspaces",
4847
+ "scope",
4848
+ "conversations",
4849
+ "select"
4850
+ ]);
4851
+ /** Lists the members `ContextBuildOptions` declares, frozen — the keys the `build` method of an `AgentContext` admits. */
4852
+ var BUILD_OPTION_KEYS = Object.freeze([
4853
+ "selection",
4854
+ "scope",
4855
+ "disclosure",
4856
+ "switchable"
4857
+ ]);
4858
+ /** Lists the members `InstructionManagerOptions` declares, frozen — the keys the `InstructionManager` constructor admits. */
4859
+ var INSTRUCTION_MANAGER_KEYS = Object.freeze([
4860
+ "on",
4861
+ "error",
4862
+ "format"
4863
+ ]);
4864
+ /** Lists the members `MemoManagerOptions` declares, frozen — the keys the `MemoManager` constructor admits. */
4865
+ var MEMO_MANAGER_KEYS = Object.freeze([
4866
+ "id",
4867
+ "on",
4868
+ "error",
4869
+ "format",
4870
+ "snapshot"
4871
+ ]);
4872
+ /** Lists the members `ScopeManagerOptions` declares, frozen — the keys the `ScopeManager` constructor admits. */
4873
+ var SCOPE_MANAGER_KEYS = Object.freeze(["on", "error"]);
4874
+ /** Lists the members `SkillManagerOptions` declares, frozen — the keys the `SkillManager` constructor admits. */
4875
+ var SKILL_MANAGER_KEYS = Object.freeze([
4876
+ "on",
4877
+ "error",
4878
+ "format"
4879
+ ]);
4662
4880
  // src/core/contexts/errors.ts
4663
4881
  /**
4664
4882
  * Reports a context snapshot that `isMemoSnapshot` refuses, thrown by the construction of a
@@ -4720,132 +4938,84 @@ function isContextError(value) {
4720
4938
  return context === void 0 || isObject(context) || isFunction(context);
4721
4939
  });
4722
4940
  }
4723
- // src/core/contexts/instructions/Instruction.ts
4941
+ // src/core/contexts/shapers.ts
4942
+ /** Describes a stored memo revision exactly. */
4943
+ var memoShape = objectShape({
4944
+ id: stringShape(),
4945
+ name: stringShape(),
4946
+ description: stringShape(),
4947
+ content: stringShape(),
4948
+ time: numberShape(),
4949
+ origin: optionalShape(objectShape({
4950
+ conversation: stringShape(),
4951
+ message: optionalShape(stringShape())
4952
+ }))
4953
+ });
4724
4954
  /**
4725
- * Represents an immutable named directive — an {@link InstructionInterface} assembled once from
4726
- * its input (`name` / `content` and an optional `priority`), the `id` minted at
4727
- * construction. Default: `0` for `priority`.
4955
+ * Describes a stored memo set exactly: an unknown key refuses.
4728
4956
  *
4729
4957
  * @remarks
4730
- * A thin immutable value object (mirroring {@link import('@orkestrel/tool').Tool}): the
4731
- * constructor mints a fresh `id` (`crypto.randomUUID()`), copies the input's `name` /
4732
- * `content`, resolves `priority` to the input's value or `0`, and carries the input's
4733
- * per-item `override` value when supplied; otherwise that property is `undefined`.
4734
- * Never mutated after construction. An
4735
- * {@link import('./InstructionManager.js').InstructionManager} keys it by `name` and
4736
- * renders it (highest `priority` first) under its section header.
4958
+ * `isMemoSnapshot` is the door a hydrate and a store read, and this shape fills a refusal's faults. A
4959
+ * lockstep test holds that `is` implies the guard, and on the snapshot fixtures the converse.
4960
+ */
4961
+ var memoSnapshotShape = objectShape({
4962
+ id: stringShape(),
4963
+ memos: arrayShape(memoShape)
4964
+ });
4965
+ /**
4966
+ * Describes a memo input exactly: the members of a stored revision less the `id` and `time` the
4967
+ * manager mints, each taken from {@link memoShape}.
4737
4968
  *
4738
- * @example
4739
- * ```ts
4740
- * const instruction = new Instruction({ name: 'tone', content: 'Be concise.', priority: 5 })
4741
- * instruction.priority // 5
4742
- * ```
4969
+ * @remarks
4970
+ * An unknown member, a missing required member, and a declared member holding `undefined` refuse, so
4971
+ * a revision `add` stores is one its own snapshot hydrates.
4743
4972
  */
4744
- var Instruction = class {
4745
- id;
4746
- name;
4747
- content;
4748
- priority;
4749
- override;
4750
- constructor(input) {
4751
- this.id = crypto.randomUUID();
4752
- this.name = input.name;
4753
- this.content = input.content;
4754
- this.priority = input.priority ?? 0;
4755
- if (input.override !== void 0) this.override = input.override;
4756
- }
4757
- };
4758
- // src/core/contexts/instructions/InstructionManager.ts
4973
+ var memoInputShape = objectShape({
4974
+ name: memoShape.properties.name,
4975
+ description: memoShape.properties.description,
4976
+ content: memoShape.properties.content,
4977
+ origin: memoShape.properties.origin
4978
+ });
4759
4979
  /**
4760
- * Registers the immutable {@link Instruction} instances a richer context assembles a directives block
4761
- * from — keyed by `name` so adding an instruction whose name is already stored overwrites it,
4762
- * last write wins, and listed by descending `priority`, carrying the `open` / `render` / `close` build contract and an observable `emitter`.
4980
+ * Describes an instruction input exactly: a string `name` and `content`, and an optional finite
4981
+ * `priority` and string `override`.
4763
4982
  *
4764
4983
  * @remarks
4765
- * - **Registry.** Instructions live in an insertion-ordered `Map` keyed by `name`;
4766
- * `add` takes one {@link InstructionInput} or a batch, mints each instruction's
4767
- * `id`, and adding an instruction whose name is already stored overwrites it (last write wins).
4768
- * `count` is the map size, `instruction(name)` looks one up, and `instructions()` lists them sorted by
4769
- * descending `priority` (a stable sort, so equal priorities keep insertion order).
4770
- * - **Build contract (the whole format cascade).** `open` is the section header a context
4771
- * renders the instructions under, `render(instruction)` renders one instruction, and
4772
- * `close` is the line after them. Each resolves the cascade most-specific-first: `render`
4773
- * returns the instruction's {@link InstructionInput.override}, else the
4774
- * `InstructionManagerOptions.format` `render`, else its `content`; `open` returns the
4775
- * options `open`, else the built-in header; `close` returns the options `close`, else
4776
- * `undefined`. A context reads the three and frames the section from them (see
4777
- * {@link import('../AgentContext.js').AgentContext}).
4778
- * - **Removal.** `remove` drops one by name, or a batch — `true` only when every supplied
4779
- * name was removed; `clear` empties the registry.
4780
- * - **Observable.** The owned {@link emitter} ({@link InstructionManagerEventMap})
4781
- * carries `add` (the created instruction) / `remove` (the name) / `clear` for
4782
- * fire-and-forget observers. Every event is emitted directly, strictly after the map
4783
- * mutation completes; the emitter isolates a listener throw and routes it to its `error`
4784
- * handler (the `error` option), so a buggy observer can never corrupt a mutation.
4984
+ * An unknown member, a missing required member, and a declared member holding `undefined` refuse.
4985
+ */
4986
+ var instructionInputShape = objectShape({
4987
+ name: stringShape(),
4988
+ content: stringShape(),
4989
+ priority: optionalShape(numberShape()),
4990
+ override: optionalShape(stringShape())
4991
+ });
4992
+ /**
4993
+ * Describes a skill input exactly: a string `name`, `description`, and `content`, optional `tools`
4994
+ * names, and optional `files`.
4785
4995
  *
4786
- * @example
4787
- * ```ts
4788
- * const manager = new InstructionManager()
4789
- * manager.add([
4790
- * { name: 'tone', content: 'Be concise.', priority: 1 },
4791
- * { name: 'safety', content: 'Refuse unsafe requests.', priority: 10 },
4792
- * ])
4793
- * manager.instructions().map((one) => one.name) // ['safety', 'tone'] — highest priority first
4794
- * ```
4996
+ * @remarks
4997
+ * An unknown member, a missing required member, and a declared member holding `undefined` refuse.
4998
+ * Each file is the `FileInput` interface of `@orkestrel/workspace`, which publishes no guard for it,
4999
+ * so the shape admits any defined element and `createFile`, that package's own door, reads it.
4795
5000
  */
4796
- var InstructionManager = class {
4797
- #instructions = /* @__PURE__ */ new Map();
4798
- #emitter;
4799
- #format;
4800
- constructor(options) {
4801
- this.#emitter = new Emitter(options);
4802
- this.#format = options?.format;
4803
- }
4804
- get emitter() {
4805
- return this.#emitter;
4806
- }
4807
- get count() {
4808
- return this.#instructions.size;
4809
- }
4810
- get open() {
4811
- return this.#format?.open ?? "## Instructions";
4812
- }
4813
- get close() {
4814
- return this.#format?.close;
4815
- }
4816
- add(input) {
4817
- if (isArray(input)) return input.map((one) => this.#create(one));
4818
- return this.#create(input);
4819
- }
4820
- instruction(name) {
4821
- return this.#instructions.get(name);
4822
- }
4823
- instructions() {
4824
- return [...this.#instructions.values()].sort((a, b) => b.priority - a.priority);
4825
- }
4826
- render(instruction) {
4827
- return instruction.override ?? this.#format?.render?.(instruction) ?? instruction.content;
4828
- }
4829
- remove(names) {
4830
- if (isArray(names)) return removeEntries(names, (name) => this.#delete(name));
4831
- return this.#delete(names);
4832
- }
4833
- clear() {
4834
- this.#instructions.clear();
4835
- this.#emitter.emit("clear");
4836
- }
4837
- #create(input) {
4838
- const instruction = new Instruction(input);
4839
- this.#instructions.set(instruction.name, instruction);
4840
- this.#emitter.emit("add", instruction);
4841
- return instruction;
4842
- }
4843
- #delete(name) {
4844
- const removed = this.#instructions.delete(name);
4845
- if (removed) this.#emitter.emit("remove", name);
4846
- return removed;
4847
- }
4848
- };
5001
+ var skillInputShape = objectShape({
5002
+ name: stringShape(),
5003
+ description: stringShape(),
5004
+ content: stringShape(),
5005
+ tools: optionalShape(arrayShape(stringShape())),
5006
+ files: optionalShape(arrayShape(rawShape({})))
5007
+ });
5008
+ // src/core/contexts/contracts.ts
5009
+ /** Reads the faults of a refused memo revision. */
5010
+ var memoContract = createContract(memoShape);
5011
+ /** Reads the faults of a refused memo snapshot. */
5012
+ var memoSnapshotContract = createContract(memoSnapshotShape);
5013
+ /** Admits a memo input exactly, and reads the faults of a refused one. */
5014
+ var memoInputContract = createContract(memoInputShape);
5015
+ /** Admits an instruction input exactly, and reads the faults of a refused one. */
5016
+ var instructionInputContract = createContract(instructionInputShape);
5017
+ /** Admits a skill input exactly, and reads the faults of a refused one. */
5018
+ var skillInputContract = createContract(skillInputShape);
4849
5019
  // src/core/contexts/helpers.ts
4850
5020
  /**
4851
5021
  * Renders a path-addressed text body as a fenced reference block — a `File: <path>` label line
@@ -5019,6 +5189,70 @@ function intersectKeys(parent, child) {
5019
5189
  return child.filter((key) => allowed.has(key));
5020
5190
  }
5021
5191
  /**
5192
+ * Intersects two scope filters list by list; an absent side constrains nothing.
5193
+ *
5194
+ * @remarks
5195
+ * Pure, and it can only tighten. Each list of the result is {@link intersectKeys} over the two
5196
+ * sides' lists, each read one time, in the order `instructions`, `skills`, `memos`, `tools`,
5197
+ * `files`, and `workspaces`. A list neither side constrains is omitted, and every returned list is
5198
+ * a fresh copy.
5199
+ * `Scope.narrow`, a run's build, and a ledger's answer-pass scope compose their layers through it.
5200
+ *
5201
+ * @param parent - The outer filter, such as the context scope (`undefined` ⇒ no constraint)
5202
+ * @param child - The inner filter, such as a run scope (`undefined` ⇒ no constraint)
5203
+ * @returns The intersected filter
5204
+ *
5205
+ * @example
5206
+ * ```ts
5207
+ * import { intersectFilters } from '@orkestrel/agent'
5208
+ *
5209
+ * intersectFilters({ tools: ['read', 'write'] }, { tools: ['write', 'admin'], files: [] })
5210
+ * // { tools: ['write'], files: [] }
5211
+ * ```
5212
+ */
5213
+ function intersectFilters(parent, child) {
5214
+ const instructions = intersectKeys(parent?.instructions, child?.instructions);
5215
+ const skills = intersectKeys(parent?.skills, child?.skills);
5216
+ const memos = intersectKeys(parent?.memos, child?.memos);
5217
+ const tools = intersectKeys(parent?.tools, child?.tools);
5218
+ const files = intersectKeys(parent?.files, child?.files);
5219
+ const workspaces = intersectKeys(parent?.workspaces, child?.workspaces);
5220
+ return {
5221
+ ...instructions === void 0 ? {} : { instructions },
5222
+ ...skills === void 0 ? {} : { skills },
5223
+ ...memos === void 0 ? {} : { memos },
5224
+ ...tools === void 0 ? {} : { tools },
5225
+ ...files === void 0 ? {} : { files },
5226
+ ...workspaces === void 0 ? {} : { workspaces }
5227
+ };
5228
+ }
5229
+ /**
5230
+ * Resolves the workspace a run renders and acts on: the one `selected` names, else the registry's
5231
+ * active one, kept only while `allowed` admits its id.
5232
+ *
5233
+ * @remarks
5234
+ * A selection the registry does not hold resolves to `undefined` and never falls back to the active
5235
+ * workspace. `allowed` is three-way, as {@link import('../helpers.js').filterAllowList} reads it.
5236
+ * `AgentContext.build`, the caller an agent run hands each tool call, and a ledger's calibration
5237
+ * resolve through it, so what a request renders and what a call receives come from one rule.
5238
+ *
5239
+ * @param workspaces - The workspace registry to read
5240
+ * @param selected - The id a `switch` call or the `workspace` option selected, or `undefined`
5241
+ * @param allowed - The ceiling's `workspaces` list (`undefined` ⇒ every id, `[]` ⇒ none)
5242
+ * @returns The workspace, or `undefined` when none resolves
5243
+ *
5244
+ * @example
5245
+ * ```ts
5246
+ * import { resolveWorkspace } from '@orkestrel/agent'
5247
+ *
5248
+ * resolveWorkspace(agent.context.workspaces, 'returns', ['returns']) // the registered returns workspace
5249
+ * ```
5250
+ */
5251
+ function resolveWorkspace(workspaces, selected, allowed) {
5252
+ const workspace = selected === void 0 ? workspaces.active : workspaces.workspace(selected);
5253
+ return workspace !== void 0 && filterAllowList(allowed, [workspace.id], (id) => id).length > 0 ? workspace : void 0;
5254
+ }
5255
+ /**
5022
5256
  * Renders one index line for a memo or a skill — `- name: description`, the built-in `render` of
5023
5257
  * the memo and skill managers.
5024
5258
  *
@@ -5038,37 +5272,219 @@ function intersectKeys(parent, child) {
5038
5272
  * ```
5039
5273
  */
5040
5274
  function renderIndexEntry(entry) {
5041
- return `- ${entry.name}: ${entry.description}`.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, " ");
5275
+ return renderIndexLine(`${entry.name}: ${entry.description}`);
5042
5276
  }
5043
- // src/core/contexts/shapers.ts
5044
- /** Describes a stored memo revision exactly. */
5045
- var memoShape = objectShape({
5046
- id: stringShape(),
5047
- name: stringShape(),
5048
- description: stringShape(),
5049
- content: stringShape(),
5050
- time: numberShape(),
5051
- origin: optionalShape(objectShape({
5052
- conversation: stringShape(),
5053
- message: optionalShape(stringShape())
5054
- }))
5055
- });
5056
5277
  /**
5057
- * Describes a stored memo set exactly: an unknown key refuses.
5278
+ * Renders one index line from its text as `- text`, with each run of line-break code points as one
5279
+ * space.
5058
5280
  *
5059
5281
  * @remarks
5060
- * `isMemoSnapshot` is the door a hydrate and a store read, and this shape fills a refusal's faults. A
5061
- * lockstep test holds that `is` implies the guard, and on the snapshot fixtures the converse.
5282
+ * Pure and total. The memo and skill index lines and the workspace index lines render through it,
5283
+ * so text in an index never opens another line or heading.
5284
+ *
5285
+ * @param text - The line's text
5286
+ * @returns The index line
5287
+ *
5288
+ * @example
5289
+ * ```ts
5290
+ * import { renderIndexLine } from '@orkestrel/agent'
5291
+ *
5292
+ * renderIndexLine('orders') // '- orders'
5293
+ * ```
5062
5294
  */
5063
- var memoSnapshotShape = objectShape({
5064
- id: stringShape(),
5065
- memos: arrayShape(memoShape)
5066
- });
5067
- // src/core/contexts/contracts.ts
5068
- /** Reads the faults of a refused memo revision. */
5069
- var memoContract = createContract(memoShape);
5070
- /** Reads the faults of a refused memo snapshot. */
5071
- var memoSnapshotContract = createContract(memoSnapshotShape);
5295
+ function renderIndexLine(text) {
5296
+ return `- ${text}`.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, " ");
5297
+ }
5298
+ /**
5299
+ * Renders one loaded skill's body: `### name`, a blank line, then its content.
5300
+ *
5301
+ * @remarks
5302
+ * Pure and total. The context build renders each shown skill through it under the loaded skills
5303
+ * header, last in the system message.
5304
+ *
5305
+ * @param skill - The skill whose name and content the body shows
5306
+ * @returns The body
5307
+ *
5308
+ * @example
5309
+ * ```ts
5310
+ * import { renderSkillBody } from '@orkestrel/agent'
5311
+ *
5312
+ * renderSkillBody({ name: 'refunds', content: 'Check the order first.' })
5313
+ * // '### refunds\n\nCheck the order first.'
5314
+ * ```
5315
+ */
5316
+ function renderSkillBody(skill) {
5317
+ return `### ${skill.name}\n\n${skill.content}`;
5318
+ }
5319
+ /**
5320
+ * Checks a stored record's input against its exact contract, and throws a `ContractError` coded
5321
+ * `'structure'` when it fails.
5322
+ *
5323
+ * @remarks
5324
+ * The input must pass the contract's `is` guard: a missing required member, a member the input type
5325
+ * does not declare, a declared member holding `undefined`, and a member of another type each refuse,
5326
+ * so nothing a refused input carries is stored. A class instance refuses, and the contract reads own
5327
+ * enumerable string keys alone, as every option door does. The contract's `audit` runs only after a refusal, and
5328
+ * the message names the owner, the input type, and every fault with the members to send. The
5329
+ * `context` holds the first fault's `path` and the input type as its `shape`.
5330
+ *
5331
+ * @param input - The input to check
5332
+ * @param contract - The input's compiled contract
5333
+ * @param owner - The entity named at the head of the refusal
5334
+ * @param shape - The input type named in the refusal and its `context`
5335
+ * @throws ContractError Thrown when the input fails the contract (code `'structure'`)
5336
+ *
5337
+ * @example
5338
+ * ```ts
5339
+ * import { memoInputContract, requireRecordInput } from '@orkestrel/agent'
5340
+ *
5341
+ * requireRecordInput({ name: 'tone', description: 'Prefers short replies.', content: 'Answer briefly.' }, memoInputContract, 'MemoManager', 'MemoInput') // returns
5342
+ * requireRecordInput({ name: 'tone', description: undefined, content: 'Answer briefly.' }, memoInputContract, 'MemoManager', 'MemoInput') // throws ContractError ('structure')
5343
+ * ```
5344
+ */
5345
+ function requireRecordInput(input, contract, owner, shape) {
5346
+ if (contract.is(input)) return;
5347
+ const faults = contract.audit(input);
5348
+ throw new ContractError(`${owner}: a ${shape} must hold each required member and no other, every member of its declared type\n${describeFaults(faults, contract.schema)}`, {
5349
+ code: "structure",
5350
+ context: {
5351
+ path: faults[0]?.path ?? [],
5352
+ shape
5353
+ }
5354
+ });
5355
+ }
5356
+ // src/core/contexts/instructions/Instruction.ts
5357
+ /**
5358
+ * Represents an immutable named directive — an {@link InstructionInterface} assembled once from
5359
+ * its input (`name` / `content` and an optional `priority`), the `id` minted at
5360
+ * construction. Default: `0` for `priority`.
5361
+ *
5362
+ * @remarks
5363
+ * A thin immutable value object (mirroring {@link import('@orkestrel/tool').Tool}): the
5364
+ * constructor mints a fresh `id` (`crypto.randomUUID()`), copies the input's `name` /
5365
+ * `content`, resolves `priority` to the input's value or `0`, and carries the input's
5366
+ * per-item `override` value when supplied; otherwise the instance owns no `override` member, so
5367
+ * its own keys and a JSON copy carry no member holding `undefined`. The input meets its exact
5368
+ * contract first, so a missing required member, an undeclared member, and a declared member holding
5369
+ * `undefined` refuse with a `ContractError` coded `'structure'`. Never mutated after construction. An
5370
+ * {@link import('./InstructionManager.js').InstructionManager} keys it by `name` and
5371
+ * renders it (highest `priority` first) under its section header.
5372
+ *
5373
+ * @example
5374
+ * ```ts
5375
+ * const instruction = new Instruction({ name: 'tone', content: 'Be concise.', priority: 5 })
5376
+ * instruction.priority // 5
5377
+ * ```
5378
+ */
5379
+ var Instruction = class {
5380
+ id;
5381
+ name;
5382
+ content;
5383
+ priority;
5384
+ constructor(input) {
5385
+ requireRecordInput(input, instructionInputContract, "Instruction", "InstructionInput");
5386
+ this.id = crypto.randomUUID();
5387
+ this.name = input.name;
5388
+ this.content = input.content;
5389
+ this.priority = input.priority ?? 0;
5390
+ if (input.override !== void 0) this.override = input.override;
5391
+ }
5392
+ };
5393
+ // src/core/contexts/instructions/InstructionManager.ts
5394
+ /**
5395
+ * Registers the immutable {@link Instruction} instances a richer context assembles a directives block
5396
+ * from — keyed by `name` so adding an instruction whose name is already stored overwrites it,
5397
+ * last write wins, and listed by descending `priority`, carrying the `open` / `render` / `close` build contract and an observable `emitter`.
5398
+ *
5399
+ * @remarks
5400
+ * - **Registry.** Instructions live in an insertion-ordered `Map` keyed by `name`;
5401
+ * `add` takes one {@link InstructionInput} or a batch, mints each instruction's
5402
+ * `id`, and adding an instruction whose name is already stored overwrites it (last write wins).
5403
+ * `count` is the map size, `instruction(name)` looks one up, and `instructions()` lists them sorted by
5404
+ * descending `priority` (a stable sort, so equal priorities keep insertion order).
5405
+ * - **Build contract (the whole format cascade).** `open` is the section header a context
5406
+ * renders the instructions under, `render(instruction)` renders one instruction, and
5407
+ * `close` is the line after them. Each resolves the cascade most-specific-first: `render`
5408
+ * returns the instruction's {@link InstructionInput.override}, else the
5409
+ * `InstructionManagerOptions.format` `render`, else its `content`; `open` returns the
5410
+ * options `open`, else the built-in header; `close` returns the options `close`, else
5411
+ * `undefined`. A context reads the three and frames the section from them (see
5412
+ * {@link import('../AgentContext.js').AgentContext}).
5413
+ * - **Removal.** `remove` drops one by name, or a batch — `true` only when every supplied
5414
+ * name was removed; `clear` empties the registry.
5415
+ * - **Observable.** The owned {@link emitter} ({@link InstructionManagerEventMap})
5416
+ * carries `add` (the created instruction) / `remove` (the name) / `clear` for
5417
+ * fire-and-forget observers. Every event is emitted directly, strictly after the map
5418
+ * mutation completes; the emitter isolates a listener throw and routes it to its `error`
5419
+ * handler (the `error` option), so a buggy observer can never corrupt a mutation.
5420
+ *
5421
+ * @example
5422
+ * ```ts
5423
+ * const manager = new InstructionManager()
5424
+ * manager.add([
5425
+ * { name: 'tone', content: 'Be concise.', priority: 1 },
5426
+ * { name: 'safety', content: 'Refuse unsafe requests.', priority: 10 },
5427
+ * ])
5428
+ * manager.instructions().map((one) => one.name) // ['safety', 'tone'] — highest priority first
5429
+ * ```
5430
+ */
5431
+ var InstructionManager = class {
5432
+ #instructions = /* @__PURE__ */ new Map();
5433
+ #emitter;
5434
+ #format;
5435
+ constructor(options) {
5436
+ refuseOptionKeys(options, INSTRUCTION_MANAGER_KEYS, "InstructionManager", "InstructionManagerOptions");
5437
+ this.#emitter = new Emitter(options);
5438
+ this.#format = options?.format;
5439
+ }
5440
+ get emitter() {
5441
+ return this.#emitter;
5442
+ }
5443
+ get count() {
5444
+ return this.#instructions.size;
5445
+ }
5446
+ get open() {
5447
+ return this.#format?.open ?? "## Instructions";
5448
+ }
5449
+ get close() {
5450
+ return this.#format?.close;
5451
+ }
5452
+ add(input) {
5453
+ if (isArray(input)) {
5454
+ input.forEach((one) => requireRecordInput(one, instructionInputContract, "Instruction", "InstructionInput"));
5455
+ return input.map((one) => this.#create(one));
5456
+ }
5457
+ return this.#create(input);
5458
+ }
5459
+ instruction(name) {
5460
+ return this.#instructions.get(name);
5461
+ }
5462
+ instructions() {
5463
+ return [...this.#instructions.values()].sort((a, b) => b.priority - a.priority);
5464
+ }
5465
+ render(instruction) {
5466
+ return instruction.override ?? this.#format?.render?.(instruction) ?? instruction.content;
5467
+ }
5468
+ remove(names) {
5469
+ if (isArray(names)) return removeEntries(names, (name) => this.#delete(name));
5470
+ return this.#delete(names);
5471
+ }
5472
+ clear() {
5473
+ this.#instructions.clear();
5474
+ this.#emitter.emit("clear");
5475
+ }
5476
+ #create(input) {
5477
+ const instruction = new Instruction(input);
5478
+ this.#instructions.set(instruction.name, instruction);
5479
+ this.#emitter.emit("add", instruction);
5480
+ return instruction;
5481
+ }
5482
+ #delete(name) {
5483
+ const removed = this.#instructions.delete(name);
5484
+ if (removed) this.#emitter.emit("remove", name);
5485
+ return removed;
5486
+ }
5487
+ };
5072
5488
  // src/core/contexts/validators.ts
5073
5489
  /**
5074
5490
  * Checks whether a value is a memo's origin: a record with a string `conversation` and an optional
@@ -5149,6 +5565,35 @@ function isMemoSnapshot(value) {
5149
5565
  return isString(id) && arrayOf(isMemo)(memos);
5150
5566
  });
5151
5567
  }
5568
+ /**
5569
+ * Checks whether a value implements `ScopeInterface`; total, each member read one time, any
5570
+ * implementation admitted.
5571
+ *
5572
+ * @remarks
5573
+ * A scope is a string `id` and `name`, each of `instructions`, `skills`, `memos`, `tools`, `files`,
5574
+ * and `workspaces` absent or a dense array of strings, `select` absent or a function, and a `narrow`
5575
+ * function. The guard reads each of those members one time and never calls a method. It admits
5576
+ * unknown members, a class instance, and a transparent proxy, and returns false for a throwing
5577
+ * getter, a revoked proxy, and any other malformed input.
5578
+ *
5579
+ * @param value - The value to test
5580
+ * @returns True if `value` carries the members of a {@link ScopeInterface}; false otherwise
5581
+ *
5582
+ * @example
5583
+ * ```ts
5584
+ * import { createScope, isScope } from '@orkestrel/agent'
5585
+ *
5586
+ * isScope(createScope({ name: 'reader', tools: ['search'] })) // true
5587
+ * isScope({ id: 's1', name: 'reader' }) // false
5588
+ * ```
5589
+ */
5590
+ function isScope(value) {
5591
+ return holds(() => {
5592
+ if (!isObject(value)) return false;
5593
+ const list = optionalOf(arrayOf(isString));
5594
+ return isString(Reflect.get(value, "id")) && isString(Reflect.get(value, "name")) && list(Reflect.get(value, "instructions")) && list(Reflect.get(value, "skills")) && list(Reflect.get(value, "memos")) && list(Reflect.get(value, "tools")) && list(Reflect.get(value, "files")) && list(Reflect.get(value, "workspaces")) && optionalOf(isFunction)(Reflect.get(value, "select")) && isFunction(Reflect.get(value, "narrow"));
5595
+ });
5596
+ }
5152
5597
  // src/core/contexts/memos/MemoManager.ts
5153
5598
  /**
5154
5599
  * Registers the revisions of a memo set — durable facts keyed by `name`, where a same-name `add`
@@ -5196,6 +5641,7 @@ var MemoManager = class {
5196
5641
  * @throws {ContextError} Thrown when the `snapshot` option cannot be copied through JSON or its copy is not a memo snapshot (code `'SNAPSHOT'`); nothing is seated and nothing is emitted
5197
5642
  */
5198
5643
  constructor(options) {
5644
+ refuseOptionKeys(options, MEMO_MANAGER_KEYS, "MemoManager", "MemoManagerOptions");
5199
5645
  const supplied = options?.snapshot;
5200
5646
  const owned = supplied === void 0 ? void 0 : copyJSON(supplied);
5201
5647
  const snapshot = isMemoSnapshot(owned) ? owned : void 0;
@@ -5224,6 +5670,7 @@ var MemoManager = class {
5224
5670
  return this.#format?.close;
5225
5671
  }
5226
5672
  add(input) {
5673
+ (isArray(input) ? input : [input]).forEach((one) => requireRecordInput(one, memoInputContract, "MemoManager", "MemoInput"));
5227
5674
  if (isArray(input)) return input.map((one) => this.#append(one));
5228
5675
  return this.#append(input);
5229
5676
  }
@@ -5282,9 +5729,9 @@ var MemoManager = class {
5282
5729
  // src/core/contexts/scopes/Scope.ts
5283
5730
  /**
5284
5731
  * Represents a named, immutable filter over a richer context's items — an optional allow-list per
5285
- * category (`instructions` / `skills` / `memos` / `tools` / `files`), each keyed by that category's
5286
- * identity (an instruction's, a skill's, a memo's, or a tool's `name`, a workspace file's `path`)
5287
- * and read as an allow-list:
5732
+ * category (`instructions` / `skills` / `memos` / `tools` / `files` / `workspaces`), each keyed by
5733
+ * that category's identity (an instruction's, a skill's, a memo's, or a tool's `name`, a workspace
5734
+ * file's `path`, a workspace's `id`) and read as an allow-list:
5288
5735
  * `undefined` lets everything pass, `[]` lets nothing pass, and a non-empty list passes the listed
5289
5736
  * keys alone. `narrow` composes a tighter child by set intersection.
5290
5737
  *
@@ -5302,6 +5749,10 @@ var MemoManager = class {
5302
5749
  * `undefined` with a list yields the list, and `undefined` with `undefined` stays
5303
5750
  * `undefined`. Narrowing can only tighten, never widen — a key excluded by a parent
5304
5751
  * can never be re-admitted by a child. The child keeps this scope's `name` and `select` handler.
5752
+ * - **Exact.** The constructor refuses an own enumerable member of its input outside
5753
+ * `SCOPE_KEYS`, and `narrow` one of its filter outside `SCOPE_FILTER_KEYS`, with a
5754
+ * `ContractError` coded `'structure'`, so a misspelled list never builds an open scope. A
5755
+ * declared member holding `undefined` reads as omitted.
5305
5756
  *
5306
5757
  * @example
5307
5758
  * ```ts
@@ -5321,8 +5772,10 @@ var Scope = class Scope {
5321
5772
  memos;
5322
5773
  tools;
5323
5774
  files;
5775
+ workspaces;
5324
5776
  select;
5325
5777
  constructor(input) {
5778
+ refuseOptionKeys(input, SCOPE_KEYS, "Scope", "ScopeInput");
5326
5779
  this.name = input.name;
5327
5780
  if (input.select !== void 0) this.select = input.select;
5328
5781
  if (input.instructions !== void 0) this.instructions = [...input.instructions];
@@ -5330,21 +5783,14 @@ var Scope = class Scope {
5330
5783
  if (input.memos !== void 0) this.memos = [...input.memos];
5331
5784
  if (input.tools !== void 0) this.tools = [...input.tools];
5332
5785
  if (input.files !== void 0) this.files = [...input.files];
5786
+ if (input.workspaces !== void 0) this.workspaces = [...input.workspaces];
5333
5787
  }
5334
5788
  narrow(config) {
5335
- const instructions = intersectKeys(this.instructions, config.instructions);
5336
- const skills = intersectKeys(this.skills, config.skills);
5337
- const memos = intersectKeys(this.memos, config.memos);
5338
- const tools = intersectKeys(this.tools, config.tools);
5339
- const files = intersectKeys(this.files, config.files);
5789
+ refuseOptionKeys(config, SCOPE_FILTER_KEYS, "Scope", "ScopeFilter");
5340
5790
  return new Scope({
5341
5791
  name: this.name,
5342
5792
  ...this.select === void 0 ? {} : { select: this.select },
5343
- ...instructions === void 0 ? {} : { instructions },
5344
- ...skills === void 0 ? {} : { skills },
5345
- ...memos === void 0 ? {} : { memos },
5346
- ...tools === void 0 ? {} : { tools },
5347
- ...files === void 0 ? {} : { files }
5793
+ ...intersectFilters(this, config)
5348
5794
  });
5349
5795
  }
5350
5796
  };
@@ -5381,6 +5827,7 @@ var ScopeManager = class {
5381
5827
  #scopes = /* @__PURE__ */ new Map();
5382
5828
  #emitter;
5383
5829
  constructor(options) {
5830
+ refuseOptionKeys(options, SCOPE_MANAGER_KEYS, "ScopeManager", "ScopeManagerOptions");
5384
5831
  this.#emitter = new Emitter(options);
5385
5832
  }
5386
5833
  get emitter() {
@@ -5454,6 +5901,7 @@ var SkillManager = class {
5454
5901
  #emitter;
5455
5902
  #format;
5456
5903
  constructor(options) {
5904
+ refuseOptionKeys(options, SKILL_MANAGER_KEYS, "SkillManager", "SkillManagerOptions");
5457
5905
  this.#emitter = new Emitter(options);
5458
5906
  this.#format = options?.format;
5459
5907
  }
@@ -5470,8 +5918,12 @@ var SkillManager = class {
5470
5918
  return this.#format?.close;
5471
5919
  }
5472
5920
  add(input) {
5473
- if (isArray(input)) return input.map((one) => this.#create(one));
5474
- return this.#create(input);
5921
+ if (isArray(input)) {
5922
+ const skills = input.map((one) => this.#mint(one));
5923
+ skills.forEach((skill) => this.#store(skill));
5924
+ return skills;
5925
+ }
5926
+ return this.#store(this.#mint(input));
5475
5927
  }
5476
5928
  skill(name) {
5477
5929
  return this.#skills.get(name);
@@ -5490,14 +5942,28 @@ var SkillManager = class {
5490
5942
  this.#skills.clear();
5491
5943
  this.#emitter.emit("clear");
5492
5944
  }
5493
- #create(input) {
5494
- const skill = {
5945
+ #mint(input) {
5946
+ const given = isRecord(input) ? input.tools : void 0;
5947
+ const tools = isArray(given) ? Array.from(given) : given;
5948
+ if (tools !== void 0 && !(isArray(tools) && tools.every(isString))) throw new ContractError("SkillManager: tools must be an array of tool names", {
5949
+ code: "placement",
5950
+ context: {
5951
+ path: ["input", "tools"],
5952
+ limit: "readonly string[]",
5953
+ received: preview(given)
5954
+ }
5955
+ });
5956
+ requireRecordInput(input, skillInputContract, "SkillManager", "SkillInput");
5957
+ return {
5495
5958
  id: crypto.randomUUID(),
5496
5959
  name: input.name,
5497
5960
  description: input.description,
5498
5961
  content: input.content,
5499
- files: (input.files ?? []).map((file) => createFile(file))
5962
+ files: (input.files ?? []).map((file) => createFile(file)),
5963
+ tools: tools === void 0 ? [] : tools
5500
5964
  };
5965
+ }
5966
+ #store(skill) {
5501
5967
  this.#skills.set(skill.name, skill);
5502
5968
  this.#emitter.emit("add", skill);
5503
5969
  return skill;
@@ -5694,8 +6160,9 @@ var DatabaseMemoStore = class {
5694
6160
  * {@link ConversationManagerInterface} message source whose active conversation is `messages`, the
5695
6161
  * {@link ToolManagerInterface} registry, and an active {@link ScopeInterface} changed through
5696
6162
  * {@link AgentContextInterface.apply}. `build()` folds the system prompt, the scoped instructions,
5697
- * and the skills index into one system message, the memos index and the active workspace's text into
5698
- * one `user` context message after it, then the conversation, and never reads `tools`.
6163
+ * the skills index, and the shown skills' bodies into one system message, the memos index and the
6164
+ * rendered workspace's text into one `user` context message after it, then the conversation, and
6165
+ * never reads `tools`.
5699
6166
  *
5700
6167
  * @remarks
5701
6168
  * - **Composition.** `system` is the optional system prompt; `instructions` / `skills` / `memos` /
@@ -5714,40 +6181,43 @@ var DatabaseMemoStore = class {
5714
6181
  * reference, no duplication), and `build()` with no selection folds that conversation's `view()`
5715
6182
  * (its per-section summaries + live tail) as the authoritative message inclusion — the scope does
5716
6183
  * not filter the conversation (it owns inclusion through compaction; scope filters only
5717
- * instructions / skills / memos / tools / workspace files). An agent run binds a conversation at
6184
+ * instructions / skills / memos / tools / workspace files / workspaces). An agent run binds a conversation at
5718
6185
  * its call, the active one unless the run names another, and builds through a selection over that
5719
6186
  * conversation, so `conversations.switch(id)` sets the next run's default at any moment and never
5720
6187
  * moves a live run; `select(conversation, request, signal)` checks the conversation it is given.
5721
6188
  * - **`build()` — the scoped assembly + the format cascade.** It folds, in order,
5722
6189
  * the system prompt, the scope-filtered instructions (a block: the section's `open` text,
5723
6190
  * each item's rendering, then any `close` text), then the scope-filtered skills index (a block
5724
- * whose entries join with a single newline) into one leading `system` message (prepended only
5725
- * when at least one part exists), then the scope-filtered memos index (the same block) and the
5726
- * active workspace's text files into one `user` context message (present only when the memos or
5727
- * workspace section renders), then
6191
+ * whose entries join with a single newline), then the shown skills' bodies, into one leading
6192
+ * `system` message (prepended only when at least one part exists), then the scope-filtered memos
6193
+ * index (the same block), the listed workspace ids, and the rendered workspace's text files into
6194
+ * one `user` context message (present only when one of those sections renders), then
5728
6195
  * appends the active conversation's `view()` (the conversation owns message inclusion through
5729
6196
  * compaction — the scope does not filter the conversation). The instruction manager resolves
5730
6197
  * each `open` / item / `close` most-specific-first: `open` = manager-options-override >
5731
6198
  * built-in; per item = item-override > manager-options-override > built-in; `close` =
5732
6199
  * manager-options-override (no built-in ⇒ no closing line when unset) (see
5733
6200
  * {@link AgentContextInterface.build}). With no override set, each section is its built-in
5734
- * header + items, no closing line. The active workspace's scoped-in image files' `base64` payload is attached to
6201
+ * header + items, no closing line. The rendered workspace's scoped-in image files' `base64` payload is attached to
5735
6202
  * the last user message (a vision provider reads images off a user turn); when no user message
5736
6203
  * exists the attachment is skipped. Built fresh each call (recomputed, never cached), so it
5737
- * always reflects the current managers / messages / scope / active workspace; it never mutates a
6204
+ * always reflects the current managers / messages / scope / workspaces; it never mutates a
5738
6205
  * manager or the stored messages. `build(selection)` adds at most one `system` message of its own
5739
6206
  * and reads the selection's messages as conversation parts without a role check; the select path
5740
6207
  * refuses a selected `system` role as a `'ROLE'` fault over `view()`; a host's direct
5741
6208
  * `build(selection)` is host-trusted, and a conversation a host implements itself whose `view()`
5742
6209
  * holds a `system` message is read as host-trusted too.
5743
- * - **The active workspace, rendered by carrier — the sole document/image context.**
5744
- * `workspaces.active` (when set) has its {@link import('@orkestrel/workspace').FileInterface} values scope-filtered by `scope.files`,
6210
+ * - **The rendered workspace, by carrier — the sole document/image context.**
6211
+ * The rendered workspace has its {@link import('@orkestrel/workspace').FileInterface} values scope-filtered by `scope.files`,
5745
6212
  * then split: text files fold into a dedicated `## Workspace` section (fenced reference blocks)
5746
6213
  * in the context message — a `user` message with a minted id, never stored — and image files'
5747
6214
  * `base64` payload attaches to the last user message of the conversation, never to the context
5748
6215
  * message. The `system` message holds host-authored text alone: the system prompt, the
5749
- * instructions, and the skills index. Active-only — never the other registered workspaces; with no
5750
- * active workspace nothing renders for workspaces. `build()` owns this render (a `Workspace` /
6216
+ * instructions, the skills index, and last the bodies of the skills `options.disclosure` shows.
6217
+ * One workspace renders: the one `options.disclosure` selects, else the active one, and only
6218
+ * while the scope's `workspaces` list admits it; with neither, nothing renders for workspaces.
6219
+ * `options.switchable` adds the admitted workspace ids to the context message, after the memos
6220
+ * index. `build()` never moves the active workspace, and it owns this render (a `Workspace` /
5751
6221
  * `WorkspaceManager` stays file-focused).
5752
6222
  * - **Tools are structural, not in the prompt.** The registry is advertised to the provider
5753
6223
  * through `tools.definitions()` (scope-filtered by the loop), never serialized into the
@@ -5774,6 +6244,7 @@ var AgentContext = class {
5774
6244
  #select;
5775
6245
  #scope;
5776
6246
  constructor(options) {
6247
+ refuseOptionKeys(options, CONTEXT_OPTION_KEYS, "AgentContext", "AgentContextOptions");
5777
6248
  this.#system = options?.system;
5778
6249
  this.#instructions = options?.instructions ?? new InstructionManager();
5779
6250
  this.#skills = options?.skills ?? new SkillManager();
@@ -5815,31 +6286,44 @@ var AgentContext = class {
5815
6286
  apply(scope) {
5816
6287
  this.#scope = scope;
5817
6288
  }
5818
- select(conversation, request, signal) {
5819
- const handler = this.#scope?.select ?? this.#select;
6289
+ select(conversation, request, signal, scope) {
6290
+ const handler = scope?.select ?? this.#scope?.select ?? this.#select;
5820
6291
  if (handler === void 0) return void 0;
5821
6292
  return this.#check(handler, conversation, request, signal);
5822
6293
  }
5823
- build(selection) {
5824
- const scope = this.#scope;
6294
+ build(options) {
6295
+ refuseOptionKeys(options, BUILD_OPTION_KEYS, "AgentContext", "ContextBuildOptions");
6296
+ const scope = intersectFilters(this.#scope, options?.scope);
5825
6297
  const parts = [];
5826
6298
  if (this.#system !== void 0) parts.push(this.#system);
5827
- const instructions = filterAllowList(scope?.instructions, this.#instructions.instructions(), (one) => one.name);
6299
+ const instructions = filterAllowList(scope.instructions, this.#instructions.instructions(), (one) => one.name);
5828
6300
  const instructed = renderSection(this.#instructions.open, instructions, (one) => this.#instructions.render(one), this.#instructions.close);
5829
6301
  if (instructed !== void 0) parts.push(instructed);
5830
- const skills = filterAllowList(scope?.skills, this.#skills.skills(), (one) => one.name);
6302
+ const skills = filterAllowList(scope.skills, this.#skills.skills(), (one) => one.name);
5831
6303
  const indexed = renderSection(this.#skills.open, skills.length === 0 ? [] : [skills.map((one) => this.#skills.render(one)).join("\n")], (block) => block, this.#skills.close);
5832
6304
  if (indexed !== void 0) parts.push(indexed);
5833
- const files = filterAllowList(scope?.files, this.#workspaces.active?.files() ?? [], (one) => one.path);
6305
+ const admitted = new Map(skills.map((one) => [one.name, one]));
6306
+ const bodies = renderSection(LOADED_SECTION_HEADER, [...new Set(options?.disclosure?.skills ?? [])].flatMap((name) => {
6307
+ const skill = admitted.get(name);
6308
+ return skill === void 0 || skill.content.trim() === "" ? [] : [skill];
6309
+ }), renderSkillBody, void 0);
6310
+ if (bodies !== void 0) parts.push(bodies);
6311
+ const workspace = resolveWorkspace(this.#workspaces, options?.disclosure?.workspace, scope.workspaces);
6312
+ const files = filterAllowList(scope.files, workspace?.files() ?? [], (one) => one.path);
5834
6313
  const documented = renderSection(WORKSPACE_SECTION_HEADER, files.flatMap((file) => isTextContent(file.content) ? [{
5835
6314
  path: file.path,
5836
6315
  language: file.content.language,
5837
6316
  text: file.content.text
5838
6317
  }] : []), (file) => renderFencedFile(file.path, file.language, file.text), void 0);
5839
- const memos = filterAllowList(scope?.memos, this.#memos.memos(), (one) => one.name);
6318
+ const memos = filterAllowList(scope.memos, this.#memos.memos(), (one) => one.name);
5840
6319
  const remembered = renderSection(this.#memos.open, memos.length === 0 ? [] : [memos.map((one) => this.#memos.render(one)).join("\n")], (block) => block, this.#memos.close);
5841
- const tail = attachUserImages(selection?.messages ?? this.#ensureConversation().view(), collectImageData(files));
5842
- const slots = [remembered, documented].filter((slot) => slot !== void 0);
6320
+ const tail = attachUserImages(options?.selection?.messages ?? this.#ensureConversation().view(), collectImageData(files));
6321
+ const switchable = options?.switchable === true ? filterAllowList(scope.workspaces, this.#workspaces.workspaces(), (one) => one.id) : [];
6322
+ const slots = [
6323
+ remembered,
6324
+ renderSection(WORKSPACE_INDEX_HEADER, switchable.length === 0 ? [] : [switchable.map((one) => renderIndexLine(one.id)).join("\n")], (block) => block, void 0),
6325
+ documented
6326
+ ].filter((slot) => slot !== void 0);
5843
6327
  const context = slots.length === 0 ? [] : [{
5844
6328
  id: crypto.randomUUID(),
5845
6329
  role: "user",
@@ -6160,6 +6644,17 @@ function createAgentContext(options) {
6160
6644
  */
6161
6645
  var DEFAULT_AGENT_LIMIT = 10;
6162
6646
  /**
6647
+ * Opens a job tree finding that refuses a whole job, which sets it apart from a finding that names an exceeded member.
6648
+ *
6649
+ * @remarks
6650
+ * The `scanJobTree` helper writes it before the path of a job the contract refuses or a member read
6651
+ * makes unreadable, and the `AgentFanout` class reads it to open its refusal with the rule that
6652
+ * finding breaks. An
6653
+ * exceeded member's finding opens with `job`, so text later in a finding cannot pass for this
6654
+ * prefix.
6655
+ */
6656
+ var JOB_REFUSAL_PREFIX = "refused ";
6657
+ /**
6163
6658
  * Holds the model-facing texts the loop writes after a tool name in a denial result — the
6164
6659
  * `denied` note for a deny with no reason, an evaluate throw, an invalid decision, or a tool
6165
6660
  * changed after its decision; the `refused` note for an ask the approver refused; and the
@@ -6171,8 +6666,74 @@ var AUTHORITY_NOTES = Object.freeze({
6171
6666
  unanswered: "it needs approval and none was given. Do not call it again; tell the user it needs approval."
6172
6667
  });
6173
6668
  /**
6669
+ * Holds the texts the built-ins return on success — `loaded`, `unloaded`, `remembered`, and
6670
+ * `switched` — frozen.
6671
+ *
6672
+ * @remarks
6673
+ * `loaded`, `unloaded`, and `switched` are the texts the conversation's stored tool messages are
6674
+ * matched against to derive what a run shows, so each is one fixed constant. A stored message
6675
+ * counts only under a call to a built-in the run enables.
6676
+ */
6677
+ var BUILTIN_NOTES = Object.freeze({
6678
+ loaded: "The skill is loaded. Its instructions and tools are yours from your next step.",
6679
+ unloaded: "The skill is unloaded. Its instructions and tools leave from your next step.",
6680
+ remembered: "The memo is saved. Continue with what you were doing.",
6681
+ switched: "The workspace is selected. Its files are yours from your next step."
6682
+ });
6683
+ /**
6684
+ * Caps each built-in argument in code points, as the built-in argument shapes count them — a `name`
6685
+ * (a skill name, a memo name, or a workspace id), a memo `description`, and a memo `content` — and
6686
+ * the `candidates` one refusal lists, frozen.
6687
+ */
6688
+ var BUILTIN_LIMITS = Object.freeze({
6689
+ name: 128,
6690
+ description: 200,
6691
+ content: 4e3,
6692
+ candidates: 20
6693
+ });
6694
+ /**
6695
+ * Declares each built-in tool's model-facing description and annotations, keyed by its
6696
+ * {@link AgentBuiltin} name, frozen.
6697
+ *
6698
+ * @remarks
6699
+ * `memo` is `pure` and `untrusted`, because it returns memo text a model can have written;
6700
+ * `remember` is `consequential`, so an `allow` verdict becomes an `ask`. A run's own tools and the
6701
+ * definitions `collectBuiltinDefinitions` returns both read their text and annotations here.
6702
+ */
6703
+ var BUILTIN_TOOLS = Object.freeze({
6704
+ load: Object.freeze({ description: "Loads one skill from the skills index, so its instructions join yours and its tools become callable from your next step. Example: {\"skill\":\"refunds\"}. Replies with a short acknowledgement, not the instructions; call unload when you no longer need the skill." }),
6705
+ unload: Object.freeze({ description: "Unloads one skill you loaded, so its instructions and tools leave from your next step. Example: {\"skill\":\"refunds\"}. Replies with a short acknowledgement." }),
6706
+ memo: Object.freeze({
6707
+ description: "Reads one memo from the memos index and replies with its full text. Example: {\"name\":\"tone\"}. Reading changes nothing; call remember to write a memo.",
6708
+ annotations: Object.freeze({
6709
+ pure: true,
6710
+ untrusted: true
6711
+ })
6712
+ }),
6713
+ remember: Object.freeze({
6714
+ description: "Saves one memo under a name you are allowed to write: a one-line description for the memos index, and the content. Example: {\"name\":\"tone\",\"description\":\"Reply tone\",\"content\":\"Keep replies brief, because the user asked for that.\"}. Replies with a short acknowledgement; call memo to read a memo back.",
6715
+ annotations: Object.freeze({ consequential: true })
6716
+ }),
6717
+ switch: Object.freeze({ description: "Selects one workspace from the workspaces index, so its files join your context from your next step. Example: {\"workspace\":\"orders\"}. Replies with a short acknowledgement." })
6718
+ });
6719
+ /**
6720
+ * Matches one control, format, line-separator, or paragraph-separator code point — the class
6721
+ * `renderArguments` encodes and a memo's one-line name and description may not hold, frozen.
6722
+ *
6723
+ * @remarks
6724
+ * Not global, so a `test` call keeps no state between calls; a global replace builds its own
6725
+ * global copy from `source`.
6726
+ */
6727
+ var ARGUMENT_CONTROL_PATTERN = Object.freeze(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u);
6728
+ /**
6729
+ * Matches a text that is blank: empty after white space and default-ignorable code points, such
6730
+ * as U+3164, are removed, frozen.
6731
+ */
6732
+ var ARGUMENT_BLANK_PATTERN = Object.freeze(/^[\p{White_Space}\p{Default_Ignorable_Code_Point}]*$/u);
6733
+ /**
6174
6734
  * Holds the model-facing text the loop writes after a tool name in the denial result of a call
6175
- * outside the active scope's tool allow-list or whose name no definition advertises this turn.
6735
+ * outside the turn's tool ceiling, the context scope's tool allow-list intersected with the run
6736
+ * scope's, or whose name no definition advertises this turn.
6176
6737
  */
6177
6738
  var SCOPE_NOTE = "it is not one of the listed tools. Use a listed tool or answer with what you have.";
6178
6739
  /**
@@ -6191,6 +6752,92 @@ var MESSAGE_TOKEN_OVERHEAD = 4;
6191
6752
  * exact count.
6192
6753
  */
6193
6754
  var IMAGE_TOKEN_ESTIMATE = 512;
6755
+ /**
6756
+ * Lists the members `AgentOptions` declares, frozen — the keys the `Agent` constructor admits.
6757
+ *
6758
+ * @remarks
6759
+ * The constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
6760
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
6761
+ */
6762
+ var AGENT_OPTION_KEYS = Object.freeze([
6763
+ "on",
6764
+ "error",
6765
+ "system",
6766
+ "tools",
6767
+ "instructions",
6768
+ "skills",
6769
+ "memos",
6770
+ "workspaces",
6771
+ "scope",
6772
+ "limit",
6773
+ "timeout",
6774
+ "budget",
6775
+ "allowance",
6776
+ "scheduler",
6777
+ "signal",
6778
+ "authority",
6779
+ "conversations",
6780
+ "window",
6781
+ "strict",
6782
+ "select",
6783
+ "builtins",
6784
+ "workspace"
6785
+ ]);
6786
+ /**
6787
+ * Lists the members `AgentRunOptions` declares, frozen — the keys the `stream` and `generate`
6788
+ * methods admit.
6789
+ *
6790
+ * @remarks
6791
+ * The `stream` method passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
6792
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
6793
+ */
6794
+ var AGENT_RUN_KEYS = Object.freeze([
6795
+ "think",
6796
+ "schema",
6797
+ "limit",
6798
+ "timeout",
6799
+ "budget",
6800
+ "allowance",
6801
+ "signal",
6802
+ "conversation",
6803
+ "prompt",
6804
+ "scope",
6805
+ "workspace"
6806
+ ]);
6807
+ /** Lists the members `AuthorityOptions` declares, frozen — the keys the `Authority` constructor admits. */
6808
+ var AUTHORITY_OPTION_KEYS = Object.freeze([
6809
+ "rules",
6810
+ "fallback",
6811
+ "ask"
6812
+ ]);
6813
+ /** Lists the members `AgentRegistryOptions` declares, frozen — the keys the `AgentRegistry` constructor admits. */
6814
+ var REGISTRY_OPTION_KEYS = Object.freeze([
6815
+ "providers",
6816
+ "tools",
6817
+ "authorities",
6818
+ "schedulers",
6819
+ "store",
6820
+ "skills",
6821
+ "memos",
6822
+ "workspaces"
6823
+ ]);
6824
+ /** Lists the members `AgentQueueOptions` declares, frozen — the keys `createAgentQueue` admits. */
6825
+ var QUEUE_OPTION_KEYS = Object.freeze([
6826
+ "registry",
6827
+ "partial",
6828
+ "concurrency",
6829
+ "retries",
6830
+ "timeout",
6831
+ "store"
6832
+ ]);
6833
+ /** Lists the members `AgentRunnerOptions` declares, frozen — the keys `createAgentRunner` admits. */
6834
+ var RUNNER_OPTION_KEYS = Object.freeze([
6835
+ "registry",
6836
+ "partial",
6837
+ "concurrency",
6838
+ "retries",
6839
+ "timeout"
6840
+ ]);
6194
6841
  // src/core/agents/errors.ts
6195
6842
  /**
6196
6843
  * Reports an {@link AgentInterface} run that ended {@link AgentResult.partial} under a
@@ -6274,6 +6921,9 @@ function isAgentJobError(value) {
6274
6921
  * `'AUTHORITY'` reports an `Authority` built with no valid `fallback`, a rule with no valid `verdict`
6275
6922
  * (its `context` names the `rule` index), and an agent built with `tools` and no `authority`. Each
6276
6923
  * throws at construction, with the options a host passes to correct it in the message.
6924
+ * `'BUILTIN'` reports a `builtins` entry that is not a built-in or repeats one, `unload` enabled
6925
+ * without `load`, and a tool manager holding a tool under an enabled built-in's name, each thrown
6926
+ * at construction.
6277
6927
  * `'CONCURRENCY'` reports a run started while another run on the same agent is live, when the
6278
6928
  * agent carries a `window` (a shared context budget) or when the run's effective `budget` or
6279
6929
  * `allowance` is an instance a live run holds — the second run's `start()` would replace that
@@ -6282,18 +6932,18 @@ function isAgentJobError(value) {
6282
6932
  * `agent` id, and the `conversation` id for a shared conversation. For concurrent runs, use
6283
6933
  * separate agents, or one agent with no `window`, distinct budget and allowance instances per
6284
6934
  * run, and a distinct conversation per run.
6285
- * `'LIMIT'` reports a construction or per-run `limit` that is not a positive safe integer,
6286
- * including one a rehydrated job carries into `build`. `'REGISTRY'` reports a rehydration name
6287
- * absent from its registry pool, on `provider` / `tool` / `authority` / `scheduler` / `build`,
6288
- * whose `context` names the `category` and the `name`, and a job that names `tools` and no
6289
- * `authority` at `build`, whose `context` names the `tools`. `'WINDOW'` reports a `strict` run whose
6935
+ * `'LIMIT'` reports a construction or per-run `limit` that is not a positive safe integer.
6936
+ * `'REGISTRY'` reports a rehydration name absent from its registry pool, on `provider` / `tool` /
6937
+ * `authority` / `scheduler` / `build`, whose `context` names the `category` and the `name`, and a
6938
+ * job `build` refuses as a record, whose `context` holds the contract's `faults` or the
6939
+ * `refusals` lines `collectJobRefusals` returns. `'WINDOW'` reports a `strict` run whose
6290
6940
  * complete provider request is still over the `window` budget, equal to its `max` included, after
6291
6941
  * the one permitted fold or with no fold possible; it settles the run `error` before the provider
6292
6942
  * is called, and its `context` holds the `agent` id, the measured `consumed`, and the window `max`.
6293
6943
  * Narrow a caught value with {@link isAgentError} and branch on `error.code`.
6294
6944
  */
6295
6945
  var AgentError = class extends Error {
6296
- /** Names the machine-readable condition — `'APPROVER'`: an `ask` on an authority that has no approver, or one its approver refuses; `'AUTHORITY'`: an authority missing its fallback or a rule's verdict, or tools set with no authority; `'CONCURRENCY'`: a run sharing accounting or a conversation with a live run; `'LIMIT'`: a `limit` that is not a positive safe integer; `'REGISTRY'`: a rehydration name absent from its registry pool; `'WINDOW'`: a strict run whose complete request is still over its window budget. */
6946
+ /** Names the machine-readable condition — `'APPROVER'`: an `ask` on an authority that has no approver, or one its approver refuses; `'AUTHORITY'`: an authority missing its fallback or a rule's verdict, or tools set with no authority; `'BUILTIN'`: a built-in list the agent cannot serve; `'CONCURRENCY'`: a run sharing accounting or a conversation with a live run; `'LIMIT'`: a `limit` that is not a positive safe integer; `'REGISTRY'`: a rehydration name absent from its registry pool; `'WINDOW'`: a strict run whose complete request is still over its window budget. */
6297
6947
  code;
6298
6948
  /** Holds the values that locate the failure, such as the `agent` id of a refused run, or `undefined` when none were supplied. */
6299
6949
  context;
@@ -6332,7 +6982,7 @@ function isAgentError(value) {
6332
6982
  return holds(() => {
6333
6983
  if (!(value instanceof Error && INTRINSICS.prototype(value) !== Error.prototype && INTRINSICS.describe(value, Symbol.for("@orkestrel/agent.AgentError"))?.value === value)) return false;
6334
6984
  const code = INTRINSICS.reflect.read(value, "code");
6335
- if (!(code === "APPROVER" || code === "AUTHORITY" || code === "CONCURRENCY" || code === "LIMIT" || code === "REGISTRY" || code === "SCHEMA" || code === "WINDOW")) return false;
6985
+ if (!(code === "APPROVER" || code === "AUTHORITY" || code === "BUILTIN" || code === "CONCURRENCY" || code === "LIMIT" || code === "REGISTRY" || code === "SCHEMA" || code === "WINDOW")) return false;
6336
6986
  const context = INTRINSICS.reflect.read(value, "context");
6337
6987
  return context === void 0 || isObject(context) || isFunction(context);
6338
6988
  });
@@ -6343,20 +6993,45 @@ function isAgentError(value) {
6343
6993
  * refuses instead of running an agent without it.
6344
6994
  *
6345
6995
  * @remarks
6346
- * `children` holds JSON records, because each child crosses this shape at its own `build`. `limit`
6347
- * reads as a number here and as a positive safe integer at the agent's construction, which owns that
6348
- * refusal.
6996
+ * `children` holds JSON records, because each child crosses this shape at its own `build`. Each
6997
+ * bound states what the build reads: `limit` an integer from 1 through `Number.MAX_SAFE_INTEGER`,
6998
+ * `timeout` an integer from 0 through `MAX_TIMEOUT_MS`, and `budget` a number of at least 0. A seed
6999
+ * message plays a `ConversationRole`, so a `system` message refuses here. A `scope` holds the
7000
+ * `skills`, `memos`, and `workspaces` lists, each required, and no other member; `builtins` admits
7001
+ * only the names of `AgentBuiltin`, and `collectJobRefusals` reads what a list of them cannot state.
6349
7002
  */
6350
7003
  var agentJobShape = objectShape({
6351
7004
  provider: stringShape(),
6352
- messages: optionalShape(arrayShape(messageInputShape)),
7005
+ messages: optionalShape(arrayShape(objectShape({
7006
+ ...messageInputShape.properties,
7007
+ role: literalShape(CONVERSATION_ROLES)
7008
+ }))),
6353
7009
  system: optionalShape(stringShape()),
6354
7010
  tools: optionalShape(arrayShape(stringShape())),
6355
7011
  authority: optionalShape(stringShape()),
6356
7012
  scheduler: optionalShape(stringShape()),
6357
- limit: optionalShape(numberShape()),
6358
- timeout: optionalShape(numberShape()),
6359
- budget: optionalShape(numberShape()),
7013
+ limit: optionalShape(integerShape({
7014
+ min: 1,
7015
+ max: Number.MAX_SAFE_INTEGER
7016
+ })),
7017
+ timeout: optionalShape(integerShape({
7018
+ min: 0,
7019
+ max: MAX_TIMEOUT_MS
7020
+ })),
7021
+ budget: optionalShape(numberShape({ min: 0 })),
7022
+ scope: optionalShape(objectShape({
7023
+ skills: arrayShape(stringShape()),
7024
+ memos: arrayShape(stringShape()),
7025
+ workspaces: arrayShape(stringShape())
7026
+ })),
7027
+ workspace: optionalShape(stringShape()),
7028
+ builtins: optionalShape(arrayShape(literalShape([
7029
+ "load",
7030
+ "unload",
7031
+ "memo",
7032
+ "remember",
7033
+ "switch"
7034
+ ]))),
6360
7035
  children: optionalShape(arrayShape(recordShape(jsonShape())))
6361
7036
  });
6362
7037
  /**
@@ -6426,6 +7101,74 @@ var agentAnswerShape = objectShape({
6426
7101
  id: stringShape(),
6427
7102
  approved: booleanShape()
6428
7103
  });
7104
+ /**
7105
+ * Describes the arguments of the `load` and `unload` built-ins — one `skill` name, non-empty and
7106
+ * at most `BUILTIN_LIMITS.name` code points.
7107
+ *
7108
+ * @remarks
7109
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7110
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7111
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7112
+ */
7113
+ var loadToolShape = objectShape({ skill: stringShape({
7114
+ min: 1,
7115
+ max: BUILTIN_LIMITS.name,
7116
+ description: "A skill name from the skills index"
7117
+ }) });
7118
+ /**
7119
+ * Describes the arguments of the `memo` built-in — one memo `name`, non-empty and at most
7120
+ * `BUILTIN_LIMITS.name` code points.
7121
+ *
7122
+ * @remarks
7123
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7124
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7125
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7126
+ */
7127
+ var memoToolShape = objectShape({ name: stringShape({
7128
+ min: 1,
7129
+ max: BUILTIN_LIMITS.name,
7130
+ description: "A memo name from the memos index"
7131
+ }) });
7132
+ /**
7133
+ * Describes the arguments of the `remember` built-in — a memo `name`, the one-line `description`
7134
+ * the memos index shows, and the `content`, each non-empty and within its `BUILTIN_LIMITS` cap.
7135
+ *
7136
+ * @remarks
7137
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7138
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7139
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7140
+ */
7141
+ var rememberToolShape = objectShape({
7142
+ name: stringShape({
7143
+ min: 1,
7144
+ max: BUILTIN_LIMITS.name,
7145
+ description: "A memo name the host lists for you to write"
7146
+ }),
7147
+ description: stringShape({
7148
+ min: 1,
7149
+ max: BUILTIN_LIMITS.description,
7150
+ description: "One line the memos index shows"
7151
+ }),
7152
+ content: stringShape({
7153
+ min: 1,
7154
+ max: BUILTIN_LIMITS.content,
7155
+ description: "The fact, why it holds, and how to apply it"
7156
+ })
7157
+ });
7158
+ /**
7159
+ * Describes the arguments of the `switch` built-in — one `workspace` id, non-empty and at most
7160
+ * `BUILTIN_LIMITS.name` code points.
7161
+ *
7162
+ * @remarks
7163
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7164
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7165
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7166
+ */
7167
+ var switchToolShape = objectShape({ workspace: stringShape({
7168
+ min: 1,
7169
+ max: BUILTIN_LIMITS.name,
7170
+ description: "A workspace id from the workspaces index"
7171
+ }) });
6429
7172
  // src/core/agents/contracts.ts
6430
7173
  /** Validates and projects agent frames at a JSON wire boundary. */
6431
7174
  var agentFrameContract = createContract(agentFrameShape);
@@ -6435,131 +7178,64 @@ var agentPromptContract = createContract(agentPromptShape);
6435
7178
  var agentJobContract = createContract(agentJobShape);
6436
7179
  /** Validates and projects a page's answers to `ask` frames at a JSON wire boundary. */
6437
7180
  var agentAnswerContract = createContract(agentAnswerShape);
6438
- // src/core/agents/ChannelCell.ts
6439
- /**
6440
- * Holds one value a `Channel` buffers and the cell pushed after it.
6441
- *
6442
- * @remarks
6443
- * A `Channel` keeps its unread values as a chain of these cells, from its `#head` cell to its
6444
- * `#tail` cell, so no public signature names this class. The `#next` field is a latch: the
6445
- * `link` method sets it once, and a later call keeps the first cell.
6446
- */
6447
- var ChannelCell = class {
6448
- #value;
6449
- #next;
6450
- /**
6451
- * Holds a value with no next cell.
6452
- *
6453
- * @param value - The value the cell holds
6454
- */
6455
- constructor(value) {
6456
- this.#value = value;
6457
- }
6458
- /**
6459
- * Returns the value the cell holds.
6460
- *
6461
- * @returns The value
6462
- */
6463
- get value() {
6464
- return this.#value;
6465
- }
6466
- /**
6467
- * Returns the cell pushed after this one.
6468
- *
6469
- * @returns The next cell, or undefined before one is linked
6470
- */
6471
- get next() {
6472
- return this.#next;
6473
- }
6474
- /**
6475
- * Links the cell pushed after this one, once.
6476
- *
6477
- * @param cell - The cell pushed after this one
6478
- */
6479
- link(cell) {
6480
- this.#next ??= cell;
6481
- }
6482
- };
6483
- // src/core/agents/Channel.ts
7181
+ /** Checks the arguments of the `load` and `unload` built-ins; its schema is the parameters they advertise. */
7182
+ var loadToolContract = createContract(loadToolShape);
7183
+ /** Checks the arguments of the `memo` built-in; its schema is the parameters it advertises. */
7184
+ var memoToolContract = createContract(memoToolShape);
7185
+ /** Checks the arguments of the `remember` built-in; its schema is the parameters it advertises. */
7186
+ var rememberToolContract = createContract(rememberToolShape);
7187
+ /** Checks the arguments of the `switch` built-in; its schema is the parameters it advertises. */
7188
+ var switchToolContract = createContract(switchToolShape);
7189
+ /**
7190
+ * Pairs each built-in's argument shape, which its tool's door compiles, with its compiled contract,
7191
+ * keyed by its {@link AgentBuiltin} name, frozen.
7192
+ *
7193
+ * @remarks
7194
+ * The one map from a built-in to its arguments: `createBuiltinTool` builds the door from `shape`,
7195
+ * and `collectBuiltinDefinitions` and `collectDisclosure` read `contract`, so the advertised
7196
+ * parameters, the door, and the derivation admit by one rule. The record declares no shape: each
7197
+ * `shape` is a reference to the one `shapers.ts` declares, held beside the contract compiled from
7198
+ * it, because the tool door takes a shape and the derivation takes a compiled contract. The record
7199
+ * is data, as the rest of this file is.
7200
+ */
7201
+ var builtinToolContracts = Object.freeze({
7202
+ load: Object.freeze({
7203
+ shape: loadToolShape,
7204
+ contract: loadToolContract
7205
+ }),
7206
+ unload: Object.freeze({
7207
+ shape: loadToolShape,
7208
+ contract: loadToolContract
7209
+ }),
7210
+ memo: Object.freeze({
7211
+ shape: memoToolShape,
7212
+ contract: memoToolContract
7213
+ }),
7214
+ remember: Object.freeze({
7215
+ shape: rememberToolShape,
7216
+ contract: rememberToolContract
7217
+ }),
7218
+ switch: Object.freeze({
7219
+ shape: switchToolShape,
7220
+ contract: switchToolContract
7221
+ })
7222
+ });
7223
+ // src/core/agents/validators.ts
6484
7224
  /**
6485
- * Buffers chunks in a minimal unbounded async channel — the eager pump writes them in (`push`)
6486
- * and ends it (`close` / `fail`) regardless of consumption, while a consumer reads them back live
6487
- * through the `drain` async-iterator. Decoupling write from read is what lets a producer make
6488
- * progress without a consumer pulling, and it is why an agent's `result` settles whether or not
6489
- * its `events` are drained.
7225
+ * Checks whether a value names a built-in: `load`, `unload`, `memo`, `remember`, or `switch`.
6490
7226
  *
6491
- * @remarks
6492
- * A waiting `drain` call parks its void resolver in the `#waiting` field. The `push` method links a
6493
- * cell after the `#tail` cell, and the `close` and `fail` methods flag the end; each then fires and
6494
- * clears every parked resolver, so each parked reader wakes, reads the `#head` cell, and either
6495
- * takes the next chunk, returns (on `close`), throws (on `fail`), or parks again when another
6496
- * reader took the chunk first. Taking a chunk reads its value and moves `#head` to the next cell
6497
- * synchronously, so each value reaches exactly one reader, a push and a take each cost constant
6498
- * time, and every taken cell but the newest is released at once; the `#tail` field holds the newest
6499
- * cell until the next push links after it, so a channel retains at most one taken value. A reader
6500
- * suspended at its `yield` expression holds only the value it took, never a cell, so the bound on
6501
- * retained values covers both the channel's fields and a suspended reader.
6502
- * A `return()` issued while a read is parked waits for the next `push`, `close`, or `fail`.
6503
- * The parked read takes a pushed value before that `return()` settles.
7227
+ * @param value - The unknown name candidate
7228
+ * @returns True if the value names a built-in; false otherwise
6504
7229
  *
6505
7230
  * @example
6506
7231
  * ```ts
6507
- * import { Channel } from '@orkestrel/agent'
6508
- *
6509
- * const channel = new Channel<number>()
6510
- * channel.push(1)
6511
- * channel.close()
6512
- * for await (const value of channel.drain()) {
6513
- * value // 1
6514
- * }
7232
+ * isAgentBuiltin('load') // true
7233
+ * isAgentBuiltin('open') // false
6515
7234
  * ```
6516
7235
  */
6517
- var Channel = class {
6518
- #head;
6519
- #tail;
6520
- #waiting = [];
6521
- #closed = false;
6522
- #failure;
6523
- push(value) {
6524
- const cell = new ChannelCell(value);
6525
- this.#tail?.link(cell);
6526
- this.#tail = cell;
6527
- this.#head ??= cell;
6528
- this.#signal();
6529
- }
6530
- close() {
6531
- this.#closed = true;
6532
- this.#signal();
6533
- }
6534
- fail(error) {
6535
- if (this.#failure === void 0) this.#failure = { error };
6536
- this.#closed = true;
6537
- this.#signal();
6538
- }
6539
- async *drain() {
6540
- for (;;) {
6541
- while (this.#head !== void 0) {
6542
- const value = this.#head.value;
6543
- this.#head = this.#head.next;
6544
- yield value;
6545
- }
6546
- if (this.#failure !== void 0) throw this.#failure.error;
6547
- if (this.#closed) return;
6548
- await this.#park();
6549
- }
6550
- }
6551
- #signal() {
6552
- const waiting = this.#waiting;
6553
- this.#waiting = [];
6554
- for (const wake of waiting) wake();
6555
- }
6556
- #park() {
6557
- return new Promise((resolve) => {
6558
- this.#waiting = [...this.#waiting, resolve];
6559
- });
6560
- }
6561
- };
6562
- // src/core/agents/validators.ts
7236
+ function isAgentBuiltin(value) {
7237
+ return value === "load" || value === "unload" || value === "memo" || value === "remember" || value === "switch";
7238
+ }
6563
7239
  /**
6564
7240
  * Checks whether a value is an authority verdict: `allow`, `ask`, or `deny`.
6565
7241
  *
@@ -6600,6 +7276,35 @@ function isAuthorityDecision(value) {
6600
7276
  reason: isString
6601
7277
  }, ["reason"])(value);
6602
7278
  }
7279
+ /**
7280
+ * Checks whether a value is an agent caller: a plain record whose own keys are exactly `workspace`
7281
+ * and `files`, whose `workspace` is `undefined` or an object, and whose `files` is `undefined` or an
7282
+ * array of strings.
7283
+ *
7284
+ * @remarks
7285
+ * Total: each member is read once, and an omitted, inherited, or undeclared member, a throwing
7286
+ * accessor, and a revoked `Proxy` return false rather than throw. It checks the shape a caller
7287
+ * carries, never that `workspace` is a live workspace: a value that crossed a wire passes when it
7288
+ * holds that shape, so accept its `workspace` only as the very instance your own registry holds
7289
+ * under that id.
7290
+ *
7291
+ * @param value - The unknown caller candidate, such as `ToolContext.caller`
7292
+ * @returns True if the value satisfies the caller contract; false otherwise
7293
+ *
7294
+ * @example
7295
+ * ```ts
7296
+ * isAgentCaller({ workspace: undefined, files: undefined }) // true
7297
+ * isAgentCaller({ workspace: 'returns' }) // false
7298
+ * ```
7299
+ */
7300
+ function isAgentCaller(value) {
7301
+ return holds(() => {
7302
+ if (!isRecord(value) || !matchesExactKeys(value, ["workspace", "files"])) return false;
7303
+ const workspace = value.workspace;
7304
+ const files = value.files;
7305
+ return (workspace === void 0 || isObject(workspace)) && (files === void 0 || arrayOf(isString)(files));
7306
+ });
7307
+ }
6603
7308
  // src/core/agents/helpers.ts
6604
7309
  /**
6605
7310
  * Projects an unknown value onto a fresh, exact `JSONValue` representation of an
@@ -7080,6 +7785,118 @@ function handleAgentQueueJob(registry, partial, input, context) {
7080
7785
  return settleAgentJob(registry.build(input, context.signal), partial);
7081
7786
  }
7082
7787
  /**
7788
+ * Collects the refusals a job the contract admitted would meet at its build: tools or built-ins with
7789
+ * no authority, a `builtins` list `requireBuiltins` refuses, and a built-in its own `tools` name.
7790
+ *
7791
+ * @remarks
7792
+ * Each line names the member first, then the refusal and its remedy. The `builtins` line of a list
7793
+ * `requireBuiltins` refuses carries that error's message; a job's tool manager holds exactly its
7794
+ * `tools`, so a built-in its `tools` list also names is the name a manager would already hold.
7795
+ * `AgentRegistry.build` refuses a job with any line, and `scanJobTree` refuses each such job of a
7796
+ * tree before anything spawns.
7797
+ *
7798
+ * @param job - The `tools`, `builtins`, and `authority` of a job `agentJobContract` admitted
7799
+ * @returns One line a refusal, in member order; empty when the job builds
7800
+ *
7801
+ * @example
7802
+ * ```ts
7803
+ * collectJobRefusals({ provider: 'main', tools: ['search'] })
7804
+ * // ["authority: a job that names tools or builtins and no authority cannot run them; add authority: 'NAME' to the job and register that NAME under authorities"]
7805
+ * ```
7806
+ */
7807
+ function collectJobRefusals(job) {
7808
+ const tools = job.tools ?? [];
7809
+ const builtins = job.builtins ?? [];
7810
+ const checked = attempt(() => requireBuiltins(job.builtins, { tool: () => void 0 }));
7811
+ const shared = builtins.filter((name) => tools.includes(name));
7812
+ return [
7813
+ (tools.length > 0 || builtins.length > 0) && job.authority === void 0 ? "authority: a job that names tools or builtins and no authority cannot run them; add authority: 'NAME' to the job and register that NAME under authorities" : void 0,
7814
+ checked.success ? void 0 : `builtins: ${errorToMessage(checked.error)}`,
7815
+ shared.length === 0 ? void 0 : `builtins: names ${shared.map((name) => preview(name)).join(", ")}, which its tools also name; rename that tool or leave the name out of builtins`
7816
+ ].filter((line) => line !== void 0);
7817
+ }
7818
+ /**
7819
+ * Scans every job under a root for a member that exceeds its own parent's; an empty result clears the tree.
7820
+ *
7821
+ * @remarks
7822
+ * The walk audits each job, the root included, through `agentJobContract` before it reads a member,
7823
+ * because a job's `children` cross that contract as JSON records, and then reads each job the
7824
+ * contract admits through `collectJobRefusals`. A job the contract refuses yields one finding with its
7825
+ * faults, or with an unreadable note when a member read throws; a job its build would refuse yields
7826
+ * one finding a refusal, its path, a dot, and the line; and the walk reads nothing under either. A
7827
+ * child is within its parent when each of
7828
+ * its `tools` is one its parent names (a parent with no `tools` names none), each of its `builtins`
7829
+ * is one its parent names (a parent with no `builtins` names none), each name in each list of its
7830
+ * `scope` is in its parent's same list (a job with no `scope` holds the three lists empty), its
7831
+ * `authority` and `scheduler` are absent or its parent's, its `provider` is its parent's, its `limit`
7832
+ * is at most its parent's (an absent `limit` reads `DEFAULT_AGENT_LIMIT`), and, where its parent
7833
+ * holds a `timeout` or a `budget`, it holds one no larger. `system`, `messages`, and `workspace` are
7834
+ * free: a `workspace` renders nothing its own `scope` omits. A refused job's finding
7835
+ * opens with `JOB_REFUSAL_PREFIX` and then the job's path from `job`; an exceeded member's opens
7836
+ * with that path, a dot, and the member. The walk is total: an unreadable job yields a finding,
7837
+ * never a throw. The `AgentFanout` class runs it on every attempt of a unit, before the unit spawns
7838
+ * or runs.
7839
+ *
7840
+ * @param root - The root job of the tree
7841
+ * @returns One finding per refused job and per exceeded member, in walk order
7842
+ *
7843
+ * @example
7844
+ * ```ts
7845
+ * scanJobTree({ provider: 'main', children: [{ provider: 'other' }] })
7846
+ * // ['job.children[0].provider: "other" differs from its parent\'s, which is "main"']
7847
+ * ```
7848
+ */
7849
+ function scanJobTree(root) {
7850
+ const findings = [];
7851
+ const pending = [[
7852
+ root,
7853
+ "job",
7854
+ void 0
7855
+ ]];
7856
+ for (const [job, path, parent] of pending) {
7857
+ if (!agentJobContract.is(job)) {
7858
+ const faults = attempt(() => agentJobContract.audit(job));
7859
+ findings.push(`${JOB_REFUSAL_PREFIX}${path}: ${faults.success ? describeFaults(faults.value, agentJobContract.schema) : "a member read threw, so the job cannot be read"}`);
7860
+ continue;
7861
+ }
7862
+ const refusals = collectJobRefusals(job);
7863
+ if (refusals.length > 0) {
7864
+ findings.push(...refusals.map((line) => `${JOB_REFUSAL_PREFIX}${path}.${line}`));
7865
+ continue;
7866
+ }
7867
+ if (parent !== void 0) {
7868
+ const held = parent.tools ?? [];
7869
+ const outside = (job.tools ?? []).filter((name) => !held.includes(name));
7870
+ const enabled = parent.builtins ?? [];
7871
+ const extra = (job.builtins ?? []).filter((name) => !enabled.includes(name));
7872
+ const unlisted = [
7873
+ "skills",
7874
+ "memos",
7875
+ "workspaces"
7876
+ ].map((list) => [list, (job.scope?.[list] ?? []).filter((name) => !(parent.scope?.[list] ?? []).includes(name))]);
7877
+ const limit = job.limit ?? 10;
7878
+ const bound = parent.limit ?? 10;
7879
+ findings.push(...[
7880
+ outside.length === 0 ? void 0 : `${path}.tools: names ${outside.map((name) => preview(name)).join(", ")}, which its parent does not hold`,
7881
+ extra.length === 0 ? void 0 : `${path}.builtins: names ${extra.map((name) => preview(name)).join(", ")}, which its parent does not hold`,
7882
+ ...unlisted.map(([list, names]) => names.length === 0 ? void 0 : `${path}.scope.${list}: names ${names.map((name) => preview(name)).join(", ")}, which its parent does not hold`),
7883
+ job.authority === void 0 || job.authority === parent.authority ? void 0 : `${path}.authority: ${preview(job.authority)} is neither absent nor its parent's, which is ${parent.authority === void 0 ? "none" : preview(parent.authority)}`,
7884
+ job.provider === parent.provider ? void 0 : `${path}.provider: ${preview(job.provider)} differs from its parent's, which is ${preview(parent.provider)}`,
7885
+ job.scheduler === void 0 || job.scheduler === parent.scheduler ? void 0 : `${path}.scheduler: ${preview(job.scheduler)} is neither absent nor its parent's, which is ${parent.scheduler === void 0 ? "none" : preview(parent.scheduler)}`,
7886
+ limit <= bound ? void 0 : `${path}.limit: ${job.limit === void 0 ? `none reads ${limit}, which exceeds` : `${limit} exceeds`} its parent's ${bound}`,
7887
+ parent.timeout === void 0 || job.timeout !== void 0 && job.timeout <= parent.timeout ? void 0 : `${path}.timeout: ${job.timeout ?? "none"} exceeds its parent's ${parent.timeout}`,
7888
+ parent.budget === void 0 || job.budget !== void 0 && job.budget <= parent.budget ? void 0 : `${path}.budget: ${job.budget ?? "none"} exceeds its parent's ${parent.budget}`
7889
+ ].filter((finding) => finding !== void 0));
7890
+ }
7891
+ for (const [index, child] of (job.children ?? []).entries()) pending.push([
7892
+ child,
7893
+ `${path}.children[${index}]`,
7894
+ job
7895
+ ]);
7896
+ }
7897
+ return findings;
7898
+ }
7899
+ /**
7083
7900
  * Assembles the settled {@link AgentResult} from a run's {@link AgentRunResult} — `thinking`,
7084
7901
  * `usage`, and `stop` are carried only when the run surfaced them, and the loop-internal
7085
7902
  * `exhausted` flag is left out.
@@ -7162,7 +7979,7 @@ function denyCall(call, reason) {
7162
7979
  function renderArguments(value) {
7163
7980
  const rendered = attempt(() => canonicalStringify(value));
7164
7981
  if (!rendered.success || rendered.value === void 0) return void 0;
7165
- return rendered.value.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, (match) => Array.from({ length: match.length }, (_, index) => `\\u${match.charCodeAt(index).toString(16).padStart(4, "0")}`).join(""));
7982
+ return rendered.value.replace(new RegExp(ARGUMENT_CONTROL_PATTERN.source, "gu"), (match) => Array.from({ length: match.length }, (_, index) => `\\u${match.charCodeAt(index).toString(16).padStart(4, "0")}`).join(""));
7166
7983
  }
7167
7984
  /**
7168
7985
  * Applies the `consequential` floor to one {@link AuthorityDecision} — an `allow` for a tool
@@ -7381,6 +8198,37 @@ function requireConversation(value) {
7381
8198
  });
7382
8199
  }
7383
8200
  /**
8201
+ * Returns a scope a run can bind, or throws a `ContractError` coded `'placement'`; `undefined`
8202
+ * passes through.
8203
+ *
8204
+ * @remarks
8205
+ * The check is {@link isScope}, a total guard that reads each member one time and admits any
8206
+ * implementation of `ScopeInterface`. `undefined` passes, because an absent option leaves the
8207
+ * context scope alone as the run's ceiling. The error's `context` carries the option path, the
8208
+ * limit `'ScopeInterface'`, and a preview of the refused value.
8209
+ *
8210
+ * @param value - The run's `scope` option, or `undefined`
8211
+ * @returns The value unchanged
8212
+ * @throws {ContractError} Thrown when `value` is defined and does not implement `ScopeInterface` (code `'placement'`)
8213
+ *
8214
+ * @example
8215
+ * ```ts
8216
+ * requireScope(createScope({ name: 'reader', tools: ['search'] })) // the scope
8217
+ * requireScope({ id: 's1', name: 'reader' }) // throws ContractError ('placement')
8218
+ * ```
8219
+ */
8220
+ function requireScope(value) {
8221
+ if (value === void 0 || isScope(value)) return value;
8222
+ throw new ContractError("Agent: scope must implement ScopeInterface", {
8223
+ code: "placement",
8224
+ context: {
8225
+ path: ["options", "scope"],
8226
+ limit: "ScopeInterface",
8227
+ received: preview(value)
8228
+ }
8229
+ });
8230
+ }
8231
+ /**
7384
8232
  * Renders a tool's output as the string content of its tool message, or returns `undefined` when JSON
7385
8233
  * cannot carry it.
7386
8234
  *
@@ -7482,6 +8330,735 @@ function extractQueueOptions(options) {
7482
8330
  ...timeout === void 0 ? {} : { timeout }
7483
8331
  };
7484
8332
  }
8333
+ /**
8334
+ * Collects the definitions of the tools an agent serves itself, as a run advertises them.
8335
+ *
8336
+ * @remarks
8337
+ * Each definition carries the name, description, and annotations `BUILTIN_TOOLS` declares and the
8338
+ * parameters of the built-in's compiled contract in `builtinToolContracts`, in the order `builtins` lists them, so
8339
+ * it equals the definition of the tool `createBuiltinTool` creates. A relay server lists these
8340
+ * among its declared definitions to forward a page's built-ins. A name that is no built-in
8341
+ * contributes nothing.
8342
+ *
8343
+ * @param options - The agent options whose `builtins` to project
8344
+ * @returns The definitions, empty when `builtins` is absent
8345
+ *
8346
+ * @example
8347
+ * ```ts
8348
+ * collectBuiltinDefinitions({ builtins: ['load', 'unload'] }).map((definition) => definition.name) // ['load', 'unload']
8349
+ * ```
8350
+ */
8351
+ function collectBuiltinDefinitions(options) {
8352
+ return (options.builtins ?? []).filter(isAgentBuiltin).map((name) => {
8353
+ const declared = BUILTIN_TOOLS[name];
8354
+ const parameters = schemaToParameters(builtinToolContracts[name].contract.schema);
8355
+ return {
8356
+ name,
8357
+ description: declared.description,
8358
+ ...parameters === void 0 ? {} : { parameters },
8359
+ ...declared.annotations === void 0 ? {} : { annotations: declared.annotations }
8360
+ };
8361
+ });
8362
+ }
8363
+ /**
8364
+ * Returns an agent's `builtins` option as an owned, frozen list, or throws a coded error before the
8365
+ * agent is built.
8366
+ *
8367
+ * @remarks
8368
+ * `undefined` returns an empty list. The option must be an array of distinct {@link AgentBuiltin}
8369
+ * names; `unload` needs `load`, because only a loaded skill can be unloaded; and the host's tool
8370
+ * manager may hold no tool under an enabled name, because the run serves that name itself.
8371
+ *
8372
+ * @param value - The `builtins` option
8373
+ * @param tools - The host's tool manager
8374
+ * @returns The enabled names, in the order given
8375
+ * @throws {AgentError} Thrown when the option is not an array, names something that is no built-in, repeats a name, lists `unload` without `load`, or names a tool the manager holds (code `'BUILTIN'`)
8376
+ *
8377
+ * @example
8378
+ * ```ts
8379
+ * requireBuiltins(['load', 'unload'], createToolManager()) // ['load', 'unload']
8380
+ * requireBuiltins(['unload'], createToolManager()) // throws AgentError ('BUILTIN')
8381
+ * ```
8382
+ */
8383
+ function requireBuiltins(value, tools) {
8384
+ if (value === void 0) return Object.freeze([]);
8385
+ if (!isArray(value)) throw new AgentError("BUILTIN", `builtins must be an array; received ${preview(value)}`);
8386
+ const names = [...value].filter(isAgentBuiltin);
8387
+ if (names.length !== value.length) throw new AgentError("BUILTIN", "builtins lists a name that is no built-in; list only 'load', 'unload', 'memo', 'remember', and 'switch'", { context: { received: preview(value) } });
8388
+ if (new Set(names).size !== names.length) throw new AgentError("BUILTIN", "builtins lists a name more than once", { context: { builtins: names } });
8389
+ if (names.includes("unload") && !names.includes("load")) throw new AgentError("BUILTIN", "builtins lists 'unload' without 'load'; add 'load' too", { context: { builtins: names } });
8390
+ const taken = names.filter((name) => tools.tool(name) !== void 0);
8391
+ if (taken.length > 0) throw new AgentError("BUILTIN", `the tool manager holds a tool under the built-in name ${taken.map((name) => `'${name}'`).join(", ")}; rename that tool or leave the name out of builtins`, { context: {
8392
+ builtins: names,
8393
+ taken
8394
+ } });
8395
+ return Object.freeze(names);
8396
+ }
8397
+ /**
8398
+ * Returns a `workspace` option unchanged, or throws a `ContractError` coded `'placement'`;
8399
+ * `undefined` passes through.
8400
+ *
8401
+ * @remarks
8402
+ * The check is `isString`. An id the workspace registry does not hold, or the ceiling does not
8403
+ * admit, passes here and renders nothing. The error's `context` carries the option path, the limit
8404
+ * `'string'`, and a preview of the refused value.
8405
+ *
8406
+ * @param value - The `workspace` option of an agent, a run, or a ledger's agent, or `undefined`
8407
+ * @returns The value unchanged
8408
+ * @throws {ContractError} Thrown when `value` is defined and is not a string (code `'placement'`)
8409
+ *
8410
+ * @example
8411
+ * ```ts
8412
+ * requireWorkspace('orders') // 'orders'
8413
+ * requireWorkspace(7) // throws ContractError ('placement')
8414
+ * ```
8415
+ */
8416
+ function requireWorkspace(value) {
8417
+ if (value === void 0 || isString(value)) return value;
8418
+ throw new ContractError("Agent: workspace must be a string", {
8419
+ code: "placement",
8420
+ context: {
8421
+ path: ["options", "workspace"],
8422
+ limit: "string",
8423
+ received: preview(value)
8424
+ }
8425
+ });
8426
+ }
8427
+ /**
8428
+ * Returns the definitions a turn advertises from the allow-list, the registered skills, and the
8429
+ * shown skills.
8430
+ *
8431
+ * @remarks
8432
+ * The allow-list admits first, as `filterAllowList` does. When no skill is passed, or an allow-list
8433
+ * admits no `load` definition, the admitted list returns as it is. Otherwise a tool any registered
8434
+ * skill names is held back unless a shown skill names it too; `load` and `unload` are never held
8435
+ * back. A caller passes the registered skills only while `load` is enabled, so with no allow-list a
8436
+ * ceiling whose empty `skills` list withholds `load` from the candidates still holds the skills'
8437
+ * tools back. Holding a tool back is presentation, never authority: a call to it is denied as
8438
+ * outside the turn's scope.
8439
+ *
8440
+ * @param definitions - The candidate definitions, in advertisement order
8441
+ * @param names - The turn's `tools` allow-list (`undefined` ⇒ all, `[]` ⇒ none)
8442
+ * @param skills - The registered skills whose `tools` gate, or none while `load` is not enabled
8443
+ * @param shown - The names of the skills the turn shows
8444
+ * @returns The advertised definitions, in candidate order
8445
+ *
8446
+ * @example
8447
+ * ```ts
8448
+ * const skills = [{ id: '1', name: 'refunds', description: 'Refunds', content: '', tools: ['refund'], files: [] }]
8449
+ * filterAdvertisedTools([{ name: 'load' }, { name: 'refund' }], undefined, skills, []) // [{ name: 'load' }]
8450
+ * filterAdvertisedTools([{ name: 'load' }, { name: 'refund' }], undefined, skills, ['refunds']).length // 2
8451
+ * ```
8452
+ */
8453
+ function filterAdvertisedTools(definitions, names, skills, shown) {
8454
+ const admitted = filterAllowList(names, definitions, (definition) => definition.name);
8455
+ if (skills.length === 0) return admitted;
8456
+ if (names !== void 0 && !admitted.some((definition) => definition.name === "load")) return admitted;
8457
+ const held = new Set(skills.flatMap((skill) => skill.tools));
8458
+ const visible = new Set(shown);
8459
+ const open = new Set(skills.filter((skill) => visible.has(skill.name)).flatMap((skill) => skill.tools));
8460
+ return admitted.filter((definition) => definition.name === "load" || definition.name === "unload" || !held.has(definition.name) || open.has(definition.name));
8461
+ }
8462
+ /**
8463
+ * Returns the definitions a run offers before the allow-list, without `remember` unless the
8464
+ * ceiling's `memos` list names a memo, without `switch` unless its `workspaces` list names a
8465
+ * workspace, and without `load` and `unload` while its `skills` list is empty.
8466
+ *
8467
+ * @remarks
8468
+ * `remember` writes and `switch` selects only a name its list holds, so an absent list and an empty
8469
+ * list each leave the built-in nothing to act on, and it is not advertised. An absent `skills` list
8470
+ * admits every registered skill, so `load` and `unload` are withheld only when the list is empty.
8471
+ * Every other definition keeps its place.
8472
+ *
8473
+ * @param definitions - The candidate definitions, in advertisement order
8474
+ * @param ceiling - The turn's ceiling
8475
+ * @returns The definitions, in candidate order
8476
+ *
8477
+ * @example
8478
+ * ```ts
8479
+ * filterBuiltinCandidates([{ name: 'memo' }, { name: 'remember' }], { memos: [] }) // [{ name: 'memo' }]
8480
+ * filterBuiltinCandidates([{ name: 'memo' }, { name: 'remember' }], { memos: ['tone'] }).length // 2
8481
+ * ```
8482
+ */
8483
+ function filterBuiltinCandidates(definitions, ceiling) {
8484
+ const writable = (ceiling.memos ?? []).length > 0;
8485
+ const selectable = (ceiling.workspaces ?? []).length > 0;
8486
+ const loadable = ceiling.skills === void 0 || ceiling.skills.length > 0;
8487
+ return definitions.filter((definition) => (definition.name !== "remember" || writable) && (definition.name !== "switch" || selectable) && (definition.name !== "load" && definition.name !== "unload" || loadable));
8488
+ }
8489
+ /**
8490
+ * Returns the refusal that names a subject and at most `BUILTIN_LIMITS.candidates` of the names a
8491
+ * caller can use.
8492
+ *
8493
+ * @remarks
8494
+ * The subject reads after "No", so it names what the caller may use, such as `'skill you can load'`.
8495
+ * The text echoes no argument, so an absent name and a name outside the ceiling read byte for byte
8496
+ * the same. Each name is JSON-encoded and capped by `preview`, in the order given. Past the cap one
8497
+ * fixed sentence sends the reader to the index in its prompt.
8498
+ *
8499
+ * @param subject - What the caller may name, such as `'skill you can load'`
8500
+ * @param names - The names the caller can use, in registry order
8501
+ * @returns The refusal text
8502
+ *
8503
+ * @example
8504
+ * ```ts
8505
+ * describeCandidates('skill you can load', ['refunds', 'billing'])
8506
+ * // 'No skill you can load has that name. Use one of: "refunds", "billing".'
8507
+ * ```
8508
+ */
8509
+ function describeCandidates(subject, names) {
8510
+ if (names.length === 0) return `No ${subject} has that name, and none is available now.`;
8511
+ return `No ${subject} has that name. Use one of: ${names.slice(0, BUILTIN_LIMITS.candidates).map((name) => preview(name)).join(", ")}.${names.length > BUILTIN_LIMITS.candidates ? " The index in your prompt lists the others." : ""}`;
8512
+ }
8513
+ /**
8514
+ * Checks whether a text is one nonblank line: it holds a code point other than white space and
8515
+ * default-ignorable code points (`ARGUMENT_BLANK_PATTERN`), and no control, format, line-separator, or
8516
+ * paragraph-separator code point (`ARGUMENT_CONTROL_PATTERN`).
8517
+ *
8518
+ * @remarks
8519
+ * The class is the one `renderArguments` encodes. A memo's name and description render in the memos
8520
+ * index one line each, so `remember` refuses a value outside this check.
8521
+ *
8522
+ * @param text - The text to check
8523
+ * @returns True if the text is one nonblank line; false otherwise
8524
+ *
8525
+ * @example
8526
+ * ```ts
8527
+ * matchesPlainLine('Reply tone') // true
8528
+ * matchesPlainLine('Reply\ntone') // false
8529
+ * matchesPlainLine(' ') // false
8530
+ * ```
8531
+ */
8532
+ function matchesPlainLine(text) {
8533
+ return !ARGUMENT_BLANK_PATTERN.test(text) && !ARGUMENT_CONTROL_PATTERN.test(text);
8534
+ }
8535
+ /**
8536
+ * Checks whether two ceilings carry the same lists of what a head renders: `instructions`,
8537
+ * `skills`, `memos`, `files`, and `workspaces`.
8538
+ *
8539
+ * @remarks
8540
+ * Each list compares by its members in order, and an absent list equals only an absent list,
8541
+ * because `undefined` admits everything and `[]` admits nothing. The `tools` list is not compared:
8542
+ * it reaches the tools block, never the head.
8543
+ *
8544
+ * @param left - One ceiling's lists
8545
+ * @param right - The other ceiling's lists
8546
+ * @returns True if every compared list matches; false otherwise
8547
+ *
8548
+ * @example
8549
+ * ```ts
8550
+ * matchesHeadLists({ skills: ['refunds'] }, { skills: ['refunds'], tools: [] }) // true
8551
+ * matchesHeadLists({ skills: [] }, {}) // false
8552
+ * ```
8553
+ */
8554
+ function matchesHeadLists(left, right) {
8555
+ return [
8556
+ [left.instructions, right.instructions],
8557
+ [left.skills, right.skills],
8558
+ [left.memos, right.memos],
8559
+ [left.files, right.files],
8560
+ [left.workspaces, right.workspaces]
8561
+ ].every(([one, other]) => one === void 0 || other === void 0 ? one === other : one.length === other.length && one.every((key, index) => key === other[index]));
8562
+ }
8563
+ /**
8564
+ * Resolves what a turn shows: the conversation's record, and the given workspace, within the
8565
+ * ceiling and the registries.
8566
+ *
8567
+ * @remarks
8568
+ * The caller reads the ceiling, so a turn reads each scope list one time. The skills are those
8569
+ * {@link collectDisclosure} reads from the conversation, in load order, keeping each that the skill
8570
+ * registry holds and the ceiling's `skills` list admits; an unregistered or excluded name shows
8571
+ * nothing. The workspace is the conversation's last switch, else the given one, kept only when the
8572
+ * workspace registry holds it and the ceiling's `workspaces` list admits it. The record counts only
8573
+ * the built-ins in `builtins`, as {@link collectDisclosure} states.
8574
+ *
8575
+ * @param context - The agent's context, whose scope, skills, and workspaces it reads
8576
+ * @param conversation - The conversation whose record to read
8577
+ * @param workspace - The `workspace` option of the run or the agent, or `undefined`
8578
+ * @param ceiling - The ceiling the turn read: the context scope intersected with the run scope
8579
+ * @param builtins - The built-ins the run enables
8580
+ * @returns The shown skills, in order, and the shown workspace when one is
8581
+ *
8582
+ * @example
8583
+ * ```ts
8584
+ * resolveDisclosure(agent.context, agent.context.messages, 'orders', {}, []) // { skills: [], workspace: 'orders' } when orders is registered
8585
+ * ```
8586
+ */
8587
+ function resolveDisclosure(context, conversation, workspace, ceiling, builtins) {
8588
+ const recorded = collectDisclosure(conversation, builtins);
8589
+ const admitted = new Set(filterAllowList(ceiling.skills, context.skills.skills(), (skill) => skill.name).map((skill) => skill.name));
8590
+ const skills = recorded.skills.filter((name) => admitted.has(name));
8591
+ const selected = recorded.workspace ?? workspace;
8592
+ return selected !== void 0 && context.workspaces.workspace(selected) !== void 0 && filterAllowList(ceiling.workspaces, [selected], (id) => id).length > 0 ? {
8593
+ skills,
8594
+ workspace: selected
8595
+ } : { skills };
8596
+ }
8597
+ /**
8598
+ * Collects the loaded skills and the selected workspace a conversation's stored built-in calls
8599
+ * record, counting only the built-ins a run enables.
8600
+ *
8601
+ * @remarks
8602
+ * Pure. It reads each section's retained originals, oldest first, then the live tail, and pairs
8603
+ * each tool message with the call that owns it through {@link collectToolGroups} and
8604
+ * {@link findToolCall}. It then folds the pairs in message order: a `load` whose message content
8605
+ * equals `BUILTIN_NOTES.loaded` adds its `skill`, an `unload` with `BUILTIN_NOTES.unloaded` removes
8606
+ * it, and a `switch` with `BUILTIN_NOTES.switched` selects its `workspace`. Each call's arguments
8607
+ * are admitted through the built-in's contract in `builtinToolContracts`, lossless repairs
8608
+ * included, as its tool's door admits them, so a `{ skill: 1 }` the door repaired to `'1'` counts
8609
+ * as `'1'`; arguments the contract refuses change nothing. A skill loaded again while shown keeps
8610
+ * its place, and one loaded again after its unload goes last. Any other message and a denied or
8611
+ * refused call change nothing. The result holds `workspace` only when a switch succeeded.
8612
+ *
8613
+ * A record counts only while its built-in is in `builtins`, so a host tool that holds a built-in's
8614
+ * name on a run that does not enable it shows nothing; with none of `load`, `unload`, and `switch`
8615
+ * enabled, nothing is read. A stored record cannot tell its writer, so one a host tool wrote under a
8616
+ * built-in's name while that built-in was off counts after a run enables it, bounded by the registry
8617
+ * and the ceiling as every record is.
8618
+ *
8619
+ * @param conversation - The conversation whose stored messages record the calls
8620
+ * @param builtins - The built-ins the run enables
8621
+ * @returns The skills in load order and the last selected workspace
8622
+ *
8623
+ * @example
8624
+ * ```ts
8625
+ * import { collectDisclosure, createConversation } from '@orkestrel/agent'
8626
+ *
8627
+ * collectDisclosure(createConversation(), ['load', 'unload']) // { skills: [] }
8628
+ * ```
8629
+ */
8630
+ function collectDisclosure(conversation, builtins) {
8631
+ const counted = new Set(builtins.filter((name) => name === "load" || name === "unload" || name === "switch"));
8632
+ if (counted.size === 0) return { skills: [] };
8633
+ const stored = [...conversation.sections.flatMap((section) => section.messages), ...conversation.messages()];
8634
+ const owners = new Map(collectToolGroups(stored).flatMap((group) => group.flatMap((message) => {
8635
+ const call = findToolCall(group, message);
8636
+ return call === void 0 ? [] : [[message, call]];
8637
+ })));
8638
+ return stored.reduce((shown, message) => {
8639
+ const call = owners.get(message);
8640
+ const name = call?.name;
8641
+ if (call === void 0 || !isAgentBuiltin(name) || !counted.has(name)) return shown;
8642
+ const note = name === "load" ? BUILTIN_NOTES.loaded : name === "unload" ? BUILTIN_NOTES.unloaded : BUILTIN_NOTES.switched;
8643
+ if (message.content !== note) return shown;
8644
+ const admitted = admitValue(builtinToolContracts[name].contract, { ...call.arguments });
8645
+ const skill = admitted.success ? admitted.value.value["skill"] : void 0;
8646
+ const workspace = admitted.success ? admitted.value.value["workspace"] : void 0;
8647
+ if (name === "load") return !isString(skill) || shown.skills.includes(skill) ? shown : {
8648
+ ...shown,
8649
+ skills: [...shown.skills, skill]
8650
+ };
8651
+ if (name === "unload") return !isString(skill) ? shown : {
8652
+ ...shown,
8653
+ skills: shown.skills.filter((one) => one !== skill)
8654
+ };
8655
+ return !isString(workspace) ? shown : {
8656
+ skills: shown.skills,
8657
+ workspace
8658
+ };
8659
+ }, { skills: [] });
8660
+ }
8661
+ // src/core/agents/AgentRun.ts
8662
+ /**
8663
+ * Holds one agent run's own tools and the view its turns and builds share: the ceiling a turn
8664
+ * read, what it shows, what it advertises, and what the working head was built with.
8665
+ *
8666
+ * @remarks
8667
+ * `Agent` creates one per run, so no public signature names this class. The run's built-in tools
8668
+ * live in a tool manager the run owns and never changes after construction, so a tool a host
8669
+ * registers under a built-in's name is never read: `tool` and `execute` answer a built-in's name
8670
+ * from the run's manager and every other name from the host's. Each handler reads the ceiling when
8671
+ * it runs, the context scope intersected with the run scope at that moment, never a turn's copy.
8672
+ * The `caller` each call receives reads the ceiling the same way, as the call starts.
8673
+ */
8674
+ var AgentRun = class {
8675
+ #context;
8676
+ #conversation;
8677
+ #scope;
8678
+ #workspace;
8679
+ #enabled;
8680
+ #builtins;
8681
+ #tools;
8682
+ #ceiling = {};
8683
+ #shown = { skills: [] };
8684
+ #ids = [];
8685
+ #files = [];
8686
+ #advertised = [];
8687
+ #snapshot;
8688
+ #selection;
8689
+ #length = 0;
8690
+ #head;
8691
+ /**
8692
+ * Creates the run's own tools for the enabled built-ins.
8693
+ *
8694
+ * @param context - The agent's context
8695
+ * @param conversation - The conversation the run is bound to
8696
+ * @param scope - The run scope, or `undefined`
8697
+ * @param workspace - The workspace the run renders in place of the active one, already checked, or `undefined`
8698
+ * @param builtins - The enabled built-ins, already checked
8699
+ */
8700
+ constructor(context, conversation, scope, workspace, builtins) {
8701
+ this.#context = context;
8702
+ this.#conversation = conversation;
8703
+ this.#scope = scope;
8704
+ this.#workspace = workspace;
8705
+ this.#enabled = builtins;
8706
+ this.#builtins = new Set(builtins);
8707
+ this.#tools = createToolManager();
8708
+ this.#tools.add(builtins.map((name) => createBuiltinTool(name, (args) => this.#handle(name, args))));
8709
+ }
8710
+ /**
8711
+ * Returns the conversation the run is bound to.
8712
+ *
8713
+ * @returns The conversation
8714
+ */
8715
+ get conversation() {
8716
+ return this.#conversation;
8717
+ }
8718
+ /**
8719
+ * Returns the run scope.
8720
+ *
8721
+ * @returns The scope, or undefined when the run has none
8722
+ */
8723
+ get scope() {
8724
+ return this.#scope;
8725
+ }
8726
+ /**
8727
+ * Returns an owned scope holding the run scope's lists as the last `observe` read them, so a build
8728
+ * renders by the turn's one read and never reads the run scope again.
8729
+ *
8730
+ * @returns The owned scope, or undefined when the run has none
8731
+ */
8732
+ get snapshot() {
8733
+ return this.#snapshot;
8734
+ }
8735
+ /**
8736
+ * Returns the ceiling the last `observe` read: the context scope intersected with the run scope.
8737
+ *
8738
+ * @returns The ceiling's lists
8739
+ */
8740
+ get ceiling() {
8741
+ return this.#ceiling;
8742
+ }
8743
+ /**
8744
+ * Returns the definitions the last `observe` advertised.
8745
+ *
8746
+ * @returns The advertised definitions
8747
+ */
8748
+ get advertised() {
8749
+ return this.#advertised;
8750
+ }
8751
+ /**
8752
+ * Returns what a build renders from the last `observe`: the shown skills and workspace, or
8753
+ * undefined when it shows neither.
8754
+ *
8755
+ * @returns The shown value, or undefined
8756
+ */
8757
+ get disclosure() {
8758
+ return this.#shown.skills.length === 0 && this.#shown.workspace === void 0 ? void 0 : this.#shown;
8759
+ }
8760
+ /**
8761
+ * Returns whether the last `observe` advertised `switch`, which lists the workspaces in the head.
8762
+ *
8763
+ * @returns True if `switch` is advertised; false otherwise
8764
+ */
8765
+ get switchable() {
8766
+ return this.#advertised.some((definition) => definition.name === "switch");
8767
+ }
8768
+ /**
8769
+ * Returns what a tool call receives as `ToolContext.caller`: the workspace instance the working
8770
+ * head rendered, kept only while the ceiling at this moment admits its id, and that ceiling's
8771
+ * `files` list.
8772
+ *
8773
+ * @remarks
8774
+ * The instance is the one the head's build resolved, never the registry's `active` pointer at the
8775
+ * call, so a host's `switch` made while the provider answers reaches the next turn's head and not
8776
+ * this call. Each read of this getter reads the `workspaces` and `files` lists of the context
8777
+ * scope and the run scope one time each, and no other list, so a call reads them as it starts, a
8778
+ * narrowing made after the turn's decision reaches that call, and a turn still reads its `tools`
8779
+ * list once.
8780
+ *
8781
+ * @returns A fresh caller
8782
+ */
8783
+ get caller() {
8784
+ const outer = this.#context.scope;
8785
+ const inner = this.#scope;
8786
+ const rendered = this.#head?.rendered;
8787
+ const [workspace] = filterAllowList(intersectKeys(outer?.workspaces, inner?.workspaces), rendered === void 0 ? [] : [rendered], (one) => one.id);
8788
+ return {
8789
+ workspace,
8790
+ files: intersectKeys(outer?.files, inner?.files)
8791
+ };
8792
+ }
8793
+ /**
8794
+ * Returns the selection the working head rendered.
8795
+ *
8796
+ * @returns The selection, or undefined before the first build
8797
+ */
8798
+ get selection() {
8799
+ return this.#selection;
8800
+ }
8801
+ /**
8802
+ * Returns the length of the array the working head was built as.
8803
+ *
8804
+ * @returns The length, 0 before the first build
8805
+ */
8806
+ get length() {
8807
+ return this.#length;
8808
+ }
8809
+ /**
8810
+ * Reads the ceiling, resolves what the run shows within it, and computes what it advertises.
8811
+ *
8812
+ * @remarks
8813
+ * The ceiling's lists are each read one time. The conversation's record is read only while
8814
+ * `load`, `unload`, or `switch` is enabled, and only the records of an enabled built-in count.
8815
+ * Registered skills hold their tools back only while `load` is enabled. The view compared with
8816
+ * the working head's is the ceiling's `instructions`, `skills`, `memos`, `files`, and
8817
+ * `workspaces` lists, the shown skills by id, the shown workspace, the files that workspace
8818
+ * renders under the ceiling's `files` list, compared by identity, and whether `switch` is
8819
+ * advertised. An edit to a rendered file, by a tool or by the host, therefore makes the head
8820
+ * stale, and an edit to a file no build renders does not.
8821
+ *
8822
+ * @returns True if the working head was built with another view and must be rebuilt; false otherwise
8823
+ */
8824
+ observe() {
8825
+ const lists = this.#scope === void 0 ? void 0 : intersectFilters(void 0, this.#scope);
8826
+ const ceiling = intersectFilters(this.#context.scope, lists);
8827
+ this.#snapshot = lists === void 0 ? void 0 : createScope({
8828
+ name: "run",
8829
+ ...lists
8830
+ });
8831
+ const shown = resolveDisclosure(this.#context, this.#conversation, this.#workspace, ceiling, this.#enabled);
8832
+ this.#ceiling = ceiling;
8833
+ this.#shown = shown;
8834
+ this.#ids = shown.skills.flatMap((name) => this.#context.skills.skill(name)?.id ?? []);
8835
+ this.#files = filterAllowList(ceiling.files, resolveWorkspace(this.#context.workspaces, shown.workspace, ceiling.workspaces)?.files() ?? [], (file) => file.path);
8836
+ this.#advertised = filterAdvertisedTools(this.#candidates(ceiling), ceiling.tools, this.#builtins.has("load") ? this.#context.skills.skills() : [], shown.skills);
8837
+ const head = this.#head;
8838
+ return head === void 0 || !matchesHeadLists(head.ceiling, ceiling) || head.ids.length !== this.#ids.length || head.ids.some((id, index) => id !== this.#ids[index]) || head.workspace !== shown.workspace || head.switchable !== this.switchable || head.files.length !== this.#files.length || head.files.some((file, index) => file !== this.#files[index]);
8839
+ }
8840
+ /**
8841
+ * Records what the working head was built with: the selection, the built array's length, the
8842
+ * view the last `observe` resolved, and the workspace instance the build rendered.
8843
+ *
8844
+ * @remarks
8845
+ * Call it right after the build: it resolves the rendered workspace from the inputs the build
8846
+ * read, the shown workspace and the context scope's `workspaces` list intersected with the
8847
+ * snapshot's.
8848
+ *
8849
+ * @param selection - The selection the build rendered
8850
+ * @param length - The length of the built array
8851
+ */
8852
+ record(selection, length) {
8853
+ this.#selection = selection;
8854
+ this.#length = length;
8855
+ this.#head = {
8856
+ ceiling: this.#ceiling,
8857
+ ids: this.#ids,
8858
+ workspace: this.#shown.workspace,
8859
+ switchable: this.switchable,
8860
+ files: this.#files,
8861
+ rendered: resolveWorkspace(this.#context.workspaces, this.#shown.workspace, intersectKeys(this.#context.scope?.workspaces, this.#snapshot?.workspaces))
8862
+ };
8863
+ }
8864
+ /**
8865
+ * Finds the tool a call names: the run's own for a built-in's name, the host's for any other.
8866
+ *
8867
+ * @param name - The tool name
8868
+ * @returns The tool, or undefined when none is registered under the name
8869
+ */
8870
+ tool(name) {
8871
+ return this.#builtins.has(name) ? this.#tools.tool(name) : this.#context.tools.tool(name);
8872
+ }
8873
+ /**
8874
+ * Executes one call through the run's manager for a built-in's name, and the host's for any other.
8875
+ *
8876
+ * @param call - The call to execute
8877
+ * @param context - The execution context
8878
+ * @returns The correlated result
8879
+ */
8880
+ execute(call, context) {
8881
+ return this.#builtins.has(call.name) ? this.#tools.execute(call, context) : this.#context.tools.execute(call, context);
8882
+ }
8883
+ #candidates(ceiling) {
8884
+ return [...this.#context.tools.definitions().filter((one) => !this.#builtins.has(one.name)), ...filterBuiltinCandidates(this.#tools.definitions(), ceiling)];
8885
+ }
8886
+ #live() {
8887
+ return intersectFilters(this.#context.scope, this.#scope);
8888
+ }
8889
+ #handle(name, args) {
8890
+ switch (name) {
8891
+ case "load": return this.#load(args.skill);
8892
+ case "unload": return this.#unload(args.skill);
8893
+ case "memo": return this.#memo(args.name);
8894
+ case "remember": return this.#remember(args.name, args.description, args.content);
8895
+ case "switch": return this.#switch(args.workspace);
8896
+ }
8897
+ }
8898
+ #load(skill) {
8899
+ const admitted = filterAllowList(this.#live().skills, this.#context.skills.skills(), (one) => one.name).map((one) => one.name);
8900
+ if (!isString(skill) || !admitted.includes(skill)) throw new ToolError("ARGUMENTS", describeCandidates("skill you can load", admitted));
8901
+ return BUILTIN_NOTES.loaded;
8902
+ }
8903
+ #unload(skill) {
8904
+ const loaded = collectDisclosure(this.#conversation, this.#enabled).skills;
8905
+ if (!isString(skill) || !loaded.includes(skill)) throw new ToolError("ARGUMENTS", describeCandidates("skill you loaded", loaded));
8906
+ return BUILTIN_NOTES.unloaded;
8907
+ }
8908
+ #memo(name) {
8909
+ const admitted = filterAllowList(this.#live().memos, this.#context.memos.memos(), (one) => one.name);
8910
+ const memo = admitted.find((one) => one.name === name);
8911
+ if (memo === void 0) throw new ToolError("ARGUMENTS", describeCandidates("memo you can read", admitted.map((one) => one.name)));
8912
+ return memo.content;
8913
+ }
8914
+ #remember(name, description, content) {
8915
+ const allowed = this.#live().memos;
8916
+ if (allowed === void 0 || allowed.length === 0) throw new ToolError("ARGUMENTS", "No memo can be written here: the host lists no memo name you may write. Continue without saving it.");
8917
+ if (!isString(name) || !allowed.includes(name)) throw new ToolError("ARGUMENTS", describeCandidates("memo you may write", allowed));
8918
+ if (!isString(description) || !isString(content)) throw new ToolError("ARGUMENTS", "Send description and content as text.");
8919
+ if (!matchesPlainLine(name) || !matchesPlainLine(description)) throw new ToolError("ARGUMENTS", "The name and the description must each be one line of visible text, with no line break or control character. Send them again on one line.");
8920
+ const current = this.#context.memos.memo(name);
8921
+ if (current !== void 0 && current.origin?.conversation !== this.#conversation.id) throw new ToolError("ARGUMENTS", "A memo with that name exists and this conversation did not write it, so it stays as it is. Read it with memo, or write a name you created.");
8922
+ this.#context.memos.add({
8923
+ name,
8924
+ description,
8925
+ content,
8926
+ origin: { conversation: this.#conversation.id }
8927
+ });
8928
+ return BUILTIN_NOTES.remembered;
8929
+ }
8930
+ #switch(workspace) {
8931
+ const allowed = this.#live().workspaces;
8932
+ if (allowed === void 0 || allowed.length === 0) throw new ToolError("ARGUMENTS", "No workspace can be selected here: the host lists no workspace you may select. Continue with the workspace you have.");
8933
+ const admitted = filterAllowList(allowed, this.#context.workspaces.workspaces(), (one) => one.id).map((one) => one.id);
8934
+ if (!isString(workspace) || !admitted.includes(workspace)) throw new ToolError("ARGUMENTS", describeCandidates("workspace you can select", admitted));
8935
+ return BUILTIN_NOTES.switched;
8936
+ }
8937
+ };
8938
+ // src/core/agents/ChannelCell.ts
8939
+ /**
8940
+ * Holds one value a `Channel` buffers and the cell pushed after it.
8941
+ *
8942
+ * @remarks
8943
+ * A `Channel` keeps its unread values as a chain of these cells, from its `#head` cell to its
8944
+ * `#tail` cell, so no public signature names this class. The `#next` field is a latch: the
8945
+ * `link` method sets it once, and a later call keeps the first cell.
8946
+ */
8947
+ var ChannelCell = class {
8948
+ #value;
8949
+ #next;
8950
+ /**
8951
+ * Holds a value with no next cell.
8952
+ *
8953
+ * @param value - The value the cell holds
8954
+ */
8955
+ constructor(value) {
8956
+ this.#value = value;
8957
+ }
8958
+ /**
8959
+ * Returns the value the cell holds.
8960
+ *
8961
+ * @returns The value
8962
+ */
8963
+ get value() {
8964
+ return this.#value;
8965
+ }
8966
+ /**
8967
+ * Returns the cell pushed after this one.
8968
+ *
8969
+ * @returns The next cell, or undefined before one is linked
8970
+ */
8971
+ get next() {
8972
+ return this.#next;
8973
+ }
8974
+ /**
8975
+ * Links the cell pushed after this one, once.
8976
+ *
8977
+ * @param cell - The cell pushed after this one
8978
+ */
8979
+ link(cell) {
8980
+ this.#next ??= cell;
8981
+ }
8982
+ };
8983
+ // src/core/agents/Channel.ts
8984
+ /**
8985
+ * Buffers chunks in a minimal unbounded async channel — the eager pump writes them in (`push`)
8986
+ * and ends it (`close` / `fail`) regardless of consumption, while a consumer reads them back live
8987
+ * through the `drain` async-iterator. Decoupling write from read is what lets a producer make
8988
+ * progress without a consumer pulling, and it is why an agent's `result` settles whether or not
8989
+ * its `events` are drained.
8990
+ *
8991
+ * @remarks
8992
+ * A waiting `drain` call parks its void resolver in the `#waiting` field. The `push` method links a
8993
+ * cell after the `#tail` cell, and the `close` and `fail` methods flag the end; each then fires and
8994
+ * clears every parked resolver, so each parked reader wakes, reads the `#head` cell, and either
8995
+ * takes the next chunk, returns (on `close`), throws (on `fail`), or parks again when another
8996
+ * reader took the chunk first. Taking a chunk reads its value and moves `#head` to the next cell
8997
+ * synchronously, so each value reaches exactly one reader, a push and a take each cost constant
8998
+ * time, and every taken cell but the newest is released at once; the `#tail` field holds the newest
8999
+ * cell until the next push links after it, so a channel retains at most one taken value. A reader
9000
+ * suspended at its `yield` expression holds only the value it took, never a cell, so the bound on
9001
+ * retained values covers both the channel's fields and a suspended reader.
9002
+ * A `return()` issued while a read is parked waits for the next `push`, `close`, or `fail`.
9003
+ * The parked read takes a pushed value before that `return()` settles.
9004
+ *
9005
+ * @example
9006
+ * ```ts
9007
+ * import { Channel } from '@orkestrel/agent'
9008
+ *
9009
+ * const channel = new Channel<number>()
9010
+ * channel.push(1)
9011
+ * channel.close()
9012
+ * for await (const value of channel.drain()) {
9013
+ * value // 1
9014
+ * }
9015
+ * ```
9016
+ */
9017
+ var Channel = class {
9018
+ #head;
9019
+ #tail;
9020
+ #waiting = [];
9021
+ #closed = false;
9022
+ #failure;
9023
+ push(value) {
9024
+ const cell = new ChannelCell(value);
9025
+ this.#tail?.link(cell);
9026
+ this.#tail = cell;
9027
+ this.#head ??= cell;
9028
+ this.#signal();
9029
+ }
9030
+ close() {
9031
+ this.#closed = true;
9032
+ this.#signal();
9033
+ }
9034
+ fail(error) {
9035
+ if (this.#failure === void 0) this.#failure = { error };
9036
+ this.#closed = true;
9037
+ this.#signal();
9038
+ }
9039
+ async *drain() {
9040
+ for (;;) {
9041
+ while (this.#head !== void 0) {
9042
+ const value = this.#head.value;
9043
+ this.#head = this.#head.next;
9044
+ yield value;
9045
+ }
9046
+ if (this.#failure !== void 0) throw this.#failure.error;
9047
+ if (this.#closed) return;
9048
+ await this.#park();
9049
+ }
9050
+ }
9051
+ #signal() {
9052
+ const waiting = this.#waiting;
9053
+ this.#waiting = [];
9054
+ for (const wake of waiting) wake();
9055
+ }
9056
+ #park() {
9057
+ return new Promise((resolve) => {
9058
+ this.#waiting = [...this.#waiting, resolve];
9059
+ });
9060
+ }
9061
+ };
7485
9062
  // src/core/agents/Agent.ts
7486
9063
  /**
7487
9064
  * Composes a {@link ProviderInterface}, an {@link AgentContext}, and a
@@ -7546,9 +9123,12 @@ var Agent = class {
7546
9123
  #window;
7547
9124
  #strict;
7548
9125
  #emitter;
9126
+ #builtins;
9127
+ #workspace;
7549
9128
  #settled = "idle";
7550
9129
  #runs = /* @__PURE__ */ new Map();
7551
9130
  constructor(provider, options) {
9131
+ refuseOptionKeys(options, AGENT_OPTION_KEYS, "Agent", "AgentOptions");
7552
9132
  if (options?.tools !== void 0 && options.authority === void 0) throw new AgentError("AUTHORITY", "tools are set and no authority is; pass authority: createAuthority({ fallback: { zone: 'default', verdict: 'allow' } }) to allow every call, or rules with a deny fallback");
7553
9133
  this.#limit = requireLimit(options?.limit ?? 10);
7554
9134
  this.#id = crypto.randomUUID();
@@ -7565,6 +9145,9 @@ var Agent = class {
7565
9145
  ...options?.conversations === void 0 ? {} : { conversations: options.conversations },
7566
9146
  ...options?.select === void 0 ? {} : { select: options.select }
7567
9147
  });
9148
+ this.#builtins = requireBuiltins(options?.builtins, this.#context.tools);
9149
+ if (this.#builtins.length > 0 && options?.authority === void 0) throw new AgentError("AUTHORITY", "builtins are set and no authority is; pass authority: createAuthority({ fallback: { zone: 'default', verdict: 'allow' } }) to allow every call, or rules with a deny fallback");
9150
+ this.#workspace = requireWorkspace(options?.workspace);
7568
9151
  this.#timeoutMs = options?.timeout;
7569
9152
  this.#budget = options?.budget;
7570
9153
  this.#allowance = options?.allowance;
@@ -7591,6 +9174,7 @@ var Agent = class {
7591
9174
  return this.stream(options).result;
7592
9175
  }
7593
9176
  stream(options) {
9177
+ refuseOptionKeys(options, AGENT_RUN_KEYS, "Agent", "AgentRunOptions");
7594
9178
  const prompt = options?.prompt;
7595
9179
  if (prompt !== void 0 && !agentPromptContract.is(prompt)) {
7596
9180
  const faults = attempt(() => describeFaults(agentPromptContract.audit(prompt), agentPromptContract.schema));
@@ -7603,11 +9187,13 @@ var Agent = class {
7603
9187
  }
7604
9188
  });
7605
9189
  }
9190
+ const workspace = requireWorkspace(options?.workspace) ?? this.#workspace;
7606
9191
  const limit = options?.limit === void 0 ? this.#limit : requireLimit(options.limit);
7607
9192
  const budget = requireBudget(options?.budget ?? this.#budget, "budget");
7608
9193
  const allowance = requireBudget(options?.allowance ?? this.#allowance, "allowance");
7609
9194
  const held = [budget, allowance].filter((one, index, all) => one !== void 0 && all.indexOf(one) === index);
7610
9195
  if (this.#runs.size > 0 && (this.#window !== void 0 || [...this.#runs.values()].some((live) => live.budgets.some((one) => held.includes(one))))) throw new AgentError("CONCURRENCY", "a live run on this agent holds the window or this budget or allowance instance; use separate agents or distinct instances per run", { context: { agent: this.#id } });
9196
+ const scope = requireScope(options?.scope);
7611
9197
  const conversation = requireConversation(options?.conversation) ?? this.#context.messages;
7612
9198
  if ([...this.#runs.values()].some((live) => live.conversation === conversation)) throw new AgentError("CONCURRENCY", "a live run on this agent holds this conversation; await it or bind the run to another conversation", { context: {
7613
9199
  agent: this.#id,
@@ -7631,7 +9217,8 @@ var Agent = class {
7631
9217
  this.#emitter.emit("start", this.id);
7632
9218
  const channel = new Channel();
7633
9219
  const settled = Promise.withResolvers();
7634
- this.#drive(abort, conversation, channel, options?.think, options?.schema, limit, budget, allowance).then((outcome) => this.#finish(abort, timeout, channel, settled, limit, outcome), (error) => this.#fail(abort, timeout, channel, settled, error));
9220
+ const run = new AgentRun(this.#context, conversation, scope, workspace, this.#builtins);
9221
+ this.#drive(abort, run, channel, options?.think, options?.schema, limit, budget, allowance).then((outcome) => this.#finish(abort, timeout, channel, settled, limit, outcome), (error) => this.#fail(abort, timeout, channel, settled, error));
7635
9222
  settled.promise.catch(() => {});
7636
9223
  return {
7637
9224
  events: this.#drainEvents(channel, abort),
@@ -7661,10 +9248,10 @@ var Agent = class {
7661
9248
  settled.reject(error);
7662
9249
  this.#emitter.emit("error", error);
7663
9250
  }
7664
- async #drive(abort, conversation, channel, think, schema, limit, budget, allowance) {
7665
- const run = this.#execute(abort, conversation, think, schema, limit, budget, allowance);
9251
+ async #drive(abort, run, channel, think, schema, limit, budget, allowance) {
9252
+ const steps = this.#execute(abort, run, think, schema, limit, budget, allowance);
7666
9253
  for (;;) {
7667
- const next = await run.next();
9254
+ const next = await steps.next();
7668
9255
  if (next.done === true) return next.value;
7669
9256
  channel.push(next.value);
7670
9257
  }
@@ -7676,15 +9263,16 @@ var Agent = class {
7676
9263
  abort.abort();
7677
9264
  }
7678
9265
  }
7679
- async *#execute(abort, conversation, think, schema, limit, budget, allowance) {
9266
+ async *#execute(abort, run, think, schema, limit, budget, allowance) {
9267
+ const conversation = run.conversation;
7680
9268
  const last = conversation.view().at(-1);
7681
9269
  const request = last?.role === "user" ? last : void 0;
7682
- const selection = this.#select(conversation, request, abort, budget);
9270
+ run.observe();
9271
+ const selection = this.#select(run, request, abort, budget);
7683
9272
  const entry = selection === void 0 ? {
7684
- messages: this.#build(conversation),
9273
+ messages: this.#build(run),
7685
9274
  usage: void 0
7686
9275
  } : await selection;
7687
- const tools = this.#context.tools;
7688
9276
  const options = think === void 0 && schema === void 0 ? void 0 : {
7689
9277
  ...think === void 0 ? {} : { think },
7690
9278
  ...schema === void 0 ? {} : { schema }
@@ -7693,14 +9281,14 @@ var Agent = class {
7693
9281
  const turns = [];
7694
9282
  while (turns.length < limit && (turns.at(-1)?.outcome ?? "tools") === "tools") {
7695
9283
  const index = turns.length;
7696
- const base = turns.at(-1)?.messages ?? entry.messages;
9284
+ const prior = turns.at(-1)?.messages ?? entry.messages;
7697
9285
  const futile = turns.at(-1)?.futile ?? false;
7698
9286
  const stopped = {
7699
9287
  content: "",
7700
9288
  thinking: void 0,
7701
9289
  usage: void 0,
7702
9290
  spent: void 0,
7703
- messages: base,
9291
+ messages: prior,
7704
9292
  outcome: "cancel",
7705
9293
  partial: true,
7706
9294
  futile,
@@ -7719,9 +9307,10 @@ var Agent = class {
7719
9307
  turns.push(stopped);
7720
9308
  continue;
7721
9309
  }
7722
- const names = this.#context.scope?.tools?.slice();
7723
- const advertised = filterAllowList(names, tools.definitions(), (definition) => definition.name);
7724
- const held = new Map(advertised.map((definition) => [definition.name, tools.tool(definition.name)]));
9310
+ const base = run.observe() ? this.#refresh(run, prior) : prior;
9311
+ const names = run.ceiling.tools;
9312
+ const advertised = run.advertised;
9313
+ const held = new Map(advertised.map((definition) => [definition.name, run.tool(definition.name)]));
7725
9314
  const definitions = advertised.length > 0 ? advertised : void 0;
7726
9315
  const composed = this.#compose(base, definitions, options);
7727
9316
  const admitted = this.#window === void 0 ? {
@@ -7729,7 +9318,7 @@ var Agent = class {
7729
9318
  messages: base,
7730
9319
  spent: void 0,
7731
9320
  futile
7732
- } : await this.#admit(this.#window, conversation, composed, base, request, index > 0, futile, abort, budget);
9321
+ } : await this.#admit(this.#window, run, composed, base, request, index > 0, futile, abort, budget);
7733
9322
  if (admitted.dispatch === void 0) {
7734
9323
  turns.push({
7735
9324
  ...stopped,
@@ -7739,12 +9328,25 @@ var Agent = class {
7739
9328
  });
7740
9329
  continue;
7741
9330
  }
9331
+ const dispatch = admitted.dispatch;
9332
+ Object.freeze(dispatch.messages);
9333
+ if (dispatch.tools !== void 0) Object.freeze(dispatch.tools);
9334
+ this.#emitter.emit("request", index, Object.freeze(dispatch), conversation);
9335
+ if (abort.signal.aborted) {
9336
+ turns.push({
9337
+ ...stopped,
9338
+ messages: admitted.messages,
9339
+ spent: admitted.spent,
9340
+ futile: admitted.futile
9341
+ });
9342
+ continue;
9343
+ }
7742
9344
  const streamed = [];
7743
9345
  const meter = allowance === void 0 ? void 0 : createTokenMeter({
7744
9346
  budget: allowance,
7745
9347
  estimator: estimateTokens
7746
9348
  });
7747
- const provided = yield* this.#provide(admitted.dispatch, abort.signal, streamed, meter);
9349
+ const provided = yield* this.#provide(dispatch, abort.signal, streamed, meter);
7748
9350
  const settled = this.#reconcile(provided, abort, streamed, admitted, meter, budget, allowance);
7749
9351
  if (!provided.success) {
7750
9352
  turns.push({
@@ -7767,8 +9369,8 @@ var Agent = class {
7767
9369
  if (result.tools !== void 0 && result.tools.length > 0) {
7768
9370
  const admittedCalls = filterAllowList(names, result.tools, (call) => call.name);
7769
9371
  const authorization = this.#authorize(result.tools, admittedCalls, advertised, held);
7770
- if (authorization.asked.length > 0 && !abort.signal.aborted) yield* this.#approve(tools, authorization, abort.signal);
7771
- if (!abort.signal.aborted) this.#confirm(tools, authorization, advertised);
9372
+ if (authorization.asked.length > 0 && !abort.signal.aborted) yield* this.#approve(run, authorization, abort.signal);
9373
+ if (!abort.signal.aborted) this.#confirm(run, authorization, advertised);
7772
9374
  if (abort.signal.aborted) {
7773
9375
  turns.push({
7774
9376
  ...settled,
@@ -7783,7 +9385,7 @@ var Agent = class {
7783
9385
  calls: result.tools,
7784
9386
  ...recorded
7785
9387
  });
7786
- const results = await this.#dispatch(tools, result.tools, abort.signal, authorization, advertised);
9388
+ const results = await this.#dispatch(run, result.tools, abort.signal, authorization, advertised);
7787
9389
  const replies = [];
7788
9390
  for (const [position, call] of result.tools.entries()) {
7789
9391
  const dispatched = results[position];
@@ -7879,11 +9481,11 @@ var Agent = class {
7879
9481
  #compose(messages, tools, options) {
7880
9482
  return {
7881
9483
  messages: [...stripThinking(messages, this.#replay)],
7882
- ...tools === void 0 ? {} : { tools },
9484
+ ...tools === void 0 ? {} : { tools: [...tools] },
7883
9485
  ...options === void 0 ? {} : { options }
7884
9486
  };
7885
9487
  }
7886
- async #admit(window, conversation, composed, messages, request, latch, futile, abort, budget) {
9488
+ async #admit(window, run, composed, messages, request, latch, futile, abort, budget) {
7887
9489
  window.clear();
7888
9490
  window.consume(composed);
7889
9491
  if (!window.exhausted) return {
@@ -7898,15 +9500,16 @@ var Agent = class {
7898
9500
  spent: void 0,
7899
9501
  futile
7900
9502
  };
9503
+ const conversation = run.conversation;
7901
9504
  if (!conversation.summarizable || futile) return {
7902
9505
  dispatch: this.#enforce(window, composed),
7903
9506
  messages,
7904
9507
  spent: void 0,
7905
9508
  futile
7906
9509
  };
7907
- return conversation.compact({ signal: abort.signal }).then((section) => this.#refold(section, window, conversation, composed, messages, request, latch, abort, budget), (error) => this.#unfold(error, composed, messages, abort));
9510
+ return conversation.compact({ signal: abort.signal }).then((section) => this.#refold(section, window, run, composed, messages, request, latch, abort, budget), (error) => this.#unfold(error, composed, messages, abort));
7908
9511
  }
7909
- async #refold(section, window, conversation, composed, messages, request, latch, abort, budget) {
9512
+ async #refold(section, window, run, composed, messages, request, latch, abort, budget) {
7910
9513
  if (abort.signal.aborted) return {
7911
9514
  dispatch: void 0,
7912
9515
  messages,
@@ -7919,9 +9522,9 @@ var Agent = class {
7919
9522
  spent: void 0,
7920
9523
  futile: latch
7921
9524
  };
7922
- const selection = this.#select(conversation, request, abort, budget);
9525
+ const selection = this.#select(run, request, abort, budget);
7923
9526
  const rebuilt = selection === void 0 ? {
7924
- messages: this.#build(conversation),
9527
+ messages: this.#build(run),
7925
9528
  usage: void 0
7926
9529
  } : await selection;
7927
9530
  if (abort.signal.aborted) return {
@@ -7970,47 +9573,61 @@ var Agent = class {
7970
9573
  } });
7971
9574
  return request;
7972
9575
  }
7973
- #select(conversation, request, abort, budget) {
7974
- const pending = request === void 0 ? void 0 : this.#context.select(conversation, request, abort.signal);
7975
- return pending === void 0 ? void 0 : pending.then((selection) => this.#adopt(conversation, selection, abort, budget), (error) => this.#reject(conversation, error, abort));
9576
+ #select(run, request, abort, budget) {
9577
+ const pending = request === void 0 ? void 0 : this.#context.select(run.conversation, request, abort.signal, run.scope);
9578
+ return pending === void 0 ? void 0 : pending.then((selection) => this.#adopt(run, selection, abort, budget), (error) => this.#reject(run, error, abort));
7976
9579
  }
7977
- #adopt(conversation, selection, abort, budget) {
9580
+ #adopt(run, selection, abort, budget) {
7978
9581
  const usage = selection.usage === void 0 ? void 0 : sanitizeUsage(selection.usage);
7979
9582
  if (usage !== void 0) chargeUsage(budget, usage, 0);
7980
9583
  if (abort.signal.aborted) return {
7981
- messages: this.#build(conversation),
9584
+ messages: this.#build(run),
7982
9585
  usage
7983
9586
  };
7984
9587
  if (selection.fault !== void 0) {
7985
9588
  this.#emitter.emit("fault", selection.fault);
7986
9589
  if (abort.signal.aborted) return {
7987
- messages: this.#build(conversation),
9590
+ messages: this.#build(run),
7988
9591
  usage
7989
9592
  };
7990
9593
  if (this.#strict) throw selection.fault;
7991
9594
  }
7992
- const messages = this.#build(conversation, selection);
9595
+ const messages = this.#build(run, selection);
7993
9596
  this.#emitter.emit("select", selection);
7994
9597
  return {
7995
9598
  messages,
7996
9599
  usage
7997
9600
  };
7998
9601
  }
7999
- #reject(conversation, error, abort) {
9602
+ #reject(run, error, abort) {
8000
9603
  if (!abort.signal.aborted) {
8001
9604
  this.#emitter.emit("fault", error);
8002
9605
  if (this.#strict && !abort.signal.aborted) throw error;
8003
9606
  }
8004
9607
  return {
8005
- messages: this.#build(conversation),
9608
+ messages: this.#build(run),
8006
9609
  usage: void 0
8007
9610
  };
8008
9611
  }
8009
- #build(conversation, selection) {
8010
- return this.#context.build(selection ?? {
8011
- messages: conversation.view(),
9612
+ #build(run, selection) {
9613
+ const rendered = selection ?? {
9614
+ messages: run.conversation.view(),
8012
9615
  judgments: []
9616
+ };
9617
+ const scope = run.snapshot;
9618
+ const disclosure = run.disclosure;
9619
+ const messages = this.#context.build({
9620
+ selection: rendered,
9621
+ ...scope === void 0 ? {} : { scope },
9622
+ ...disclosure === void 0 ? {} : { disclosure },
9623
+ ...run.switchable ? { switchable: true } : {}
8013
9624
  });
9625
+ run.record(rendered, messages.length);
9626
+ return messages;
9627
+ }
9628
+ #refresh(run, messages) {
9629
+ const added = messages.slice(run.length);
9630
+ return [...this.#build(run, run.selection), ...added];
8014
9631
  }
8015
9632
  #authorize(calls, admitted, advertised, held) {
8016
9633
  const allowed = /* @__PURE__ */ new Map();
@@ -8083,7 +9700,7 @@ var Agent = class {
8083
9700
  }
8084
9701
  return success(evaluated.value);
8085
9702
  }
8086
- async *#approve(tools, authorization, signal) {
9703
+ async *#approve(run, authorization, signal) {
8087
9704
  const authority = this.#authority;
8088
9705
  for (const { index, call, decision, tool } of authorization.asked) {
8089
9706
  if (signal.aborted) return;
@@ -8112,7 +9729,7 @@ var Agent = class {
8112
9729
  };
8113
9730
  const outcome = await raceSignal(answer, signal).catch(() => void 0);
8114
9731
  if (outcome === void 0 || signal.aborted) return;
8115
- if (outcome.approved && tools.tool(call.name) === tool) {
9732
+ if (outcome.approved && run.tool(call.name) === tool) {
8116
9733
  authorization.allowed.set(index, {
8117
9734
  call,
8118
9735
  tool
@@ -8124,29 +9741,32 @@ var Agent = class {
8124
9741
  }
8125
9742
  }
8126
9743
  }
8127
- #changed(tools, call, tool, advertised) {
9744
+ #changed(run, call, tool, advertised) {
8128
9745
  const same = attempt(() => {
8129
- const current = tools.tool(call.name);
9746
+ const current = run.tool(call.name);
8130
9747
  const definition = advertised.find((entry) => entry.name === call.name);
8131
9748
  return current === tool && current?.annotations?.consequential === true === (definition?.annotations?.consequential === true);
8132
9749
  });
8133
9750
  return !same.success || !same.value;
8134
9751
  }
8135
- #confirm(tools, authorization, advertised) {
9752
+ #confirm(run, authorization, advertised) {
8136
9753
  for (const [index, { call, tool }] of authorization.allowed) {
8137
- if (!this.#changed(tools, call, tool, advertised)) continue;
9754
+ if (!this.#changed(run, call, tool, advertised)) continue;
8138
9755
  authorization.allowed.delete(index);
8139
9756
  authorization.denials.set(index, denyCall(call, AUTHORITY_NOTES.denied));
8140
9757
  this.#emitter.emit("deny", call, `${call.name} changed after its decision`, void 0);
8141
- this.#confirm(tools, authorization, advertised);
9758
+ this.#confirm(run, authorization, advertised);
8142
9759
  return;
8143
9760
  }
8144
9761
  }
8145
- async #dispatch(tools, calls, signal, authorization, advertised) {
9762
+ async #dispatch(run, calls, signal, authorization, advertised) {
8146
9763
  const { allowed, denials } = authorization;
8147
9764
  const entries = [...allowed].sort(([left], [right]) => left - right);
8148
9765
  const started = entries.map(([, { call, tool }]) => {
8149
- if (!this.#changed(tools, call, tool, advertised)) return tools.execute(call, { signal });
9766
+ if (!this.#changed(run, call, tool, advertised)) return run.execute(call, {
9767
+ signal,
9768
+ caller: run.caller
9769
+ });
8150
9770
  this.#emitter.emit("deny", call, `${call.name} changed after its decision`, void 0);
8151
9771
  return denyCall(call, AUTHORITY_NOTES.denied);
8152
9772
  });
@@ -8215,6 +9835,17 @@ var Agent = class {
8215
9835
  * awaited inline, so a bounded runner frees the parent's slot for them. `createAgentRunner`
8216
9836
  * creates one instance per runner and binds its `execute` method as the runner's handler.
8217
9837
  *
9838
+ * A child job runs within its parent. Before a unit spawns or runs, on every attempt, the
9839
+ * `execute` method walks the whole tree under it through the `scanJobTree` helper and refuses the
9840
+ * unit when any job exceeds its own parent on `tools`, `builtins`, `scope`, `authority`,
9841
+ * `provider`, `scheduler`, `limit`, `timeout`, or `budget`, or when a job is malformed or its build
9842
+ * would refuse it (the lines `collectJobRefusals` returns), so nothing in a refused tree spawns, builds,
9843
+ * or runs. A child's `workspace` is free, because it renders nothing its own `scope`
9844
+ * omits. Each child is held within its parent alone, so the children of one parent can together
9845
+ * spend more than it. A job that needs another provider, another authority, a skill, memo,
9846
+ * workspace, or built-in its parent lacks, or more room than its parent is a root job the host
9847
+ * enqueues.
9848
+ *
8218
9849
  * @example
8219
9850
  * ```ts
8220
9851
  * import type { AgentJobInput, ProviderInterface } from '@orkestrel/agent'
@@ -8244,14 +9875,32 @@ var AgentFanout = class {
8244
9875
  this.#partial = partial;
8245
9876
  }
8246
9877
  /**
8247
- * Executes one unit: spawns its declared children unless an earlier attempt of the unit already
8248
- * did, then runs the rehydrated agent and settles it under the partial policy.
9878
+ * Executes one unit: refuses its tree when a job in it is malformed or exceeds its parent, spawns
9879
+ * its declared children unless an earlier attempt of the unit already did, then runs the
9880
+ * rehydrated agent and settles it under the partial policy.
9881
+ *
9882
+ * @remarks
9883
+ * The check runs on every attempt, before the unit spawns or runs, and throws synchronously. The
9884
+ * error's message opens with the rule each kind of finding breaks, a refused job's rule when a
9885
+ * finding opens with `JOB_REFUSAL_PREFIX` and the within-parent rule when a finding names an
9886
+ * exceeded member, then lists every finding, one a line. Its `context.findings` holds the same
9887
+ * findings in walk order.
8249
9888
  *
8250
9889
  * @param controller - The runner controller for this unit's attempt
8251
9890
  * @returns The settled agent result of the unit
9891
+ * @throws {AgentError} Thrown with code `'REGISTRY'` when a job in the unit's tree is malformed or exceeds its parent, or when the registry cannot rehydrate the unit
8252
9892
  * @throws {AgentJobError} Thrown when the run ended partial and the partial policy is `false`
8253
9893
  */
8254
9894
  execute(controller) {
9895
+ const findings = scanJobTree(controller.input);
9896
+ if (findings.length > 0) {
9897
+ const refused = findings.filter((finding) => finding.startsWith(JOB_REFUSAL_PREFIX));
9898
+ throw new AgentError("REGISTRY", [
9899
+ ...refused.length === 0 ? [] : ["every job in a tree must be readable, hold only the declared members of AgentJobInput, and build under its parent; correct each job a following finding names"],
9900
+ ...refused.length === findings.length ? [] : ["a child job must stay within its parent on tools, builtins, scope, authority, provider, scheduler, limit, timeout, and budget; enqueue a job that needs more as a root job"],
9901
+ ...findings
9902
+ ].join("\n"), { context: { findings } });
9903
+ }
8255
9904
  const children = controller.input.children;
8256
9905
  if (children !== void 0 && !this.#spawned.has(controller.id)) {
8257
9906
  this.#spawned = /* @__PURE__ */ new Set([...this.#spawned, controller.id]);
@@ -8305,6 +9954,7 @@ var Authority = class {
8305
9954
  #handler;
8306
9955
  constructor(options) {
8307
9956
  this.#fallback = requireFallback(options);
9957
+ refuseOptionKeys(options, AUTHORITY_OPTION_KEYS, "Authority", "AuthorityOptions");
8308
9958
  this.#rules = requireRules(options.rules);
8309
9959
  this.#handler = options.ask;
8310
9960
  }
@@ -8427,16 +10077,22 @@ var Approver = class {
8427
10077
  * the message `unknown <category>: <name>` when it is absent — an unknown name in a
8428
10078
  * rehydrated job is a programmer / config error that must fail loudly at build time, never
8429
10079
  * silently resolve to `undefined` and run an agent missing a dependency.
8430
- * - **`build` rehydrates.** Resolve the job's `provider`; assemble a fresh
8431
- * {@link ToolManager} from the `tools` names, which refuses with `code: 'REGISTRY'` when the job
8432
- * names no `authority`; rebuild the token `budget` from its
8433
- * ceiling (`createTokenBudget({ max })`); resolve the optional `authority` /
10080
+ * - **`build` rehydrates.** Refuse with `code: 'REGISTRY'` a job `agentJobContract` refuses,
10081
+ * its faults under `context.faults`, and a job `collectJobRefusals` refuses, its lines under
10082
+ * `context.refusals`: tools or built-ins with no `authority`, a `builtins` list an agent
10083
+ * refuses, and a built-in the job's `tools` also name. Then resolve the job's `provider`;
10084
+ * assemble a fresh {@link ToolManager} from the `tools` names; rebuild the token `budget` from
10085
+ * its ceiling (`createTokenBudget({ max })`); resolve the optional `authority` /
8434
10086
  * `scheduler` names; construct the {@link Agent} with `system` / `limit` / `timeout` /
8435
10087
  * the threaded `signal`; seed its context with the job's `messages`; return it. The
8436
10088
  * `signal` is the queue attempt's / runner unit's cancel, so a bounded abort propagates
8437
10089
  * into the agent (which commits a partial — the job's `partial` policy then
8438
- * decides success vs. retry). A job `limit` that is not a positive safe integer throws an
8439
- * {@link AgentError} carrying `code: 'LIMIT'` from the agent's construction.
10090
+ * decides success vs. retry). A job is a record, so a malformed `limit`, `timeout`, `budget`,
10091
+ * seed role, or `builtins` list refuses as one, with `code: 'REGISTRY'`.
10092
+ * - **A job holds what it names.** A job with a `scope` gets the registry's `skills`, `memos`, and
10093
+ * `workspaces` managers, which every scoped job shares, and a scope named `job` built from its
10094
+ * three lists; a job with no `scope` gets none of them. Its `workspace` and `builtins` pass to
10095
+ * the agent.
8440
10096
  * - **Event-free.** A pure resolver — no Emitter, no events.
8441
10097
  *
8442
10098
  * @example
@@ -8453,12 +10109,19 @@ var AgentRegistry = class {
8453
10109
  #authorities;
8454
10110
  #schedulers;
8455
10111
  #store;
10112
+ #skills;
10113
+ #memos;
10114
+ #workspaces;
8456
10115
  constructor(options) {
10116
+ refuseOptionKeys(options, REGISTRY_OPTION_KEYS, "AgentRegistry", "AgentRegistryOptions");
8457
10117
  this.#providers = new Map(Object.entries(options.providers));
8458
10118
  this.#tools = new Map(Object.entries(options.tools ?? {}));
8459
10119
  this.#authorities = new Map(Object.entries(options.authorities ?? {}));
8460
10120
  this.#schedulers = new Map(Object.entries(options.schedulers ?? {}));
8461
10121
  this.#store = options.store;
10122
+ this.#skills = options.skills;
10123
+ this.#memos = options.memos;
10124
+ this.#workspaces = options.workspaces;
8462
10125
  }
8463
10126
  provider(name) {
8464
10127
  return requireEntry(this.#providers, "provider", name);
@@ -8475,13 +10138,16 @@ var AgentRegistry = class {
8475
10138
  build(input, signal) {
8476
10139
  const faults = agentJobContract.audit(input);
8477
10140
  if (faults.length > 0) throw new AgentError("REGISTRY", `a job must hold only the declared members of AgentJobInput\n${describeFaults(faults, agentJobContract.schema)}`, { context: { faults } });
10141
+ const refusals = collectJobRefusals(input);
10142
+ if (refusals.length > 0) throw new AgentError("REGISTRY", ["a job must build as it is written; correct each member a following line names", ...refusals].join("\n"), { context: { refusals } });
8478
10143
  const agent = new Agent(this.provider(input.provider), this.#buildOptions(input, signal));
8479
10144
  for (const message of input.messages ?? []) agent.context.messages.add(message);
8480
10145
  return agent;
8481
10146
  }
8482
10147
  #buildOptions(input, signal) {
8483
10148
  const names = input.tools ?? [];
8484
- if (names.length > 0 && input.authority === void 0) throw new AgentError("REGISTRY", "a job that names tools and no authority cannot run them; add authority: 'NAME' to the job and register that NAME under authorities", { context: { tools: names } });
10149
+ const builtins = input.builtins ?? [];
10150
+ const scope = input.scope;
8485
10151
  const budget = input.budget === void 0 ? void 0 : createTokenBudget({ max: input.budget });
8486
10152
  return {
8487
10153
  ...names.length === 0 ? {} : { tools: this.#buildToolManager(names) },
@@ -8492,6 +10158,15 @@ var AgentRegistry = class {
8492
10158
  ...input.authority === void 0 ? {} : { authority: this.authority(input.authority) },
8493
10159
  ...input.scheduler === void 0 ? {} : { scheduler: this.scheduler(input.scheduler) },
8494
10160
  ...this.#store === void 0 ? {} : { conversations: new ConversationManager({ store: this.#store }) },
10161
+ ...scope === void 0 || this.#skills === void 0 ? {} : { skills: this.#skills },
10162
+ ...scope === void 0 || this.#memos === void 0 ? {} : { memos: this.#memos },
10163
+ ...scope === void 0 || this.#workspaces === void 0 ? {} : { workspaces: this.#workspaces },
10164
+ ...scope === void 0 ? {} : { scope: createScope({
10165
+ name: "job",
10166
+ ...scope
10167
+ }) },
10168
+ ...input.workspace === void 0 ? {} : { workspace: input.workspace },
10169
+ ...builtins.length === 0 ? {} : { builtins },
8495
10170
  ...signal === void 0 ? {} : { signal }
8496
10171
  };
8497
10172
  }
@@ -8726,6 +10401,7 @@ function createAgentRegistry(options) {
8726
10401
  * ```
8727
10402
  */
8728
10403
  function createAgentQueue(options) {
10404
+ refuseOptionKeys(options, QUEUE_OPTION_KEYS, "createAgentQueue", "AgentQueueOptions");
8729
10405
  const { registry, partial = false, store } = options;
8730
10406
  return createQueue({
8731
10407
  ...extractQueueOptions(options),
@@ -8772,6 +10448,7 @@ function createAgentQueue(options) {
8772
10448
  * ```
8773
10449
  */
8774
10450
  function createAgentRunner(options) {
10451
+ refuseOptionKeys(options, RUNNER_OPTION_KEYS, "createAgentRunner", "AgentRunnerOptions");
8775
10452
  const { registry, partial = false } = options;
8776
10453
  const fanout = new AgentFanout(registry, partial);
8777
10454
  return createRunner({
@@ -8779,6 +10456,38 @@ function createAgentRunner(options) {
8779
10456
  handler: fanout.execute.bind(fanout)
8780
10457
  });
8781
10458
  }
10459
+ /**
10460
+ * Creates the tool a run serves for one built-in: its description and annotations from
10461
+ * `BUILTIN_TOOLS`, its argument shape from `builtinToolContracts`, and the given handler.
10462
+ *
10463
+ * @remarks
10464
+ * The tool's door refuses a missing, empty, over-long, or extra argument before the handler runs,
10465
+ * and repairs a value that converts to a string without loss, such as a number, so the handler
10466
+ * reads only arguments the shape admits. Its definition equals the one `collectBuiltinDefinitions`
10467
+ * returns for the same name.
10468
+ *
10469
+ * @param name - The built-in to create
10470
+ * @param execute - The handler the door hands admitted arguments to
10471
+ * @returns The tool
10472
+ *
10473
+ * @example
10474
+ * ```ts
10475
+ * import { createBuiltinTool } from '@orkestrel/agent'
10476
+ *
10477
+ * const memo = createBuiltinTool('memo', (args) => `read ${String(args.name)}`)
10478
+ * memo.annotations // { pure: true, untrusted: true }
10479
+ * ```
10480
+ */
10481
+ function createBuiltinTool(name, execute) {
10482
+ const declared = BUILTIN_TOOLS[name];
10483
+ return createTool({
10484
+ name,
10485
+ description: declared.description,
10486
+ ...declared.annotations === void 0 ? {} : { annotations: declared.annotations },
10487
+ contract: builtinToolContracts[name].shape,
10488
+ execute
10489
+ });
10490
+ }
8782
10491
  // src/core/ledgers/constants.ts
8783
10492
  /**
8784
10493
  * Lists the categories the ledger files a message under, in the order the category question
@@ -8853,19 +10562,25 @@ var LEDGER_QUESTIONS = Object.freeze({
8853
10562
  /**
8854
10563
  * Supplies the text of each ledger note, worded for a model that answers in its final
8855
10564
  * message.
10565
+ *
10566
+ * @remarks
10567
+ * `overflow` names `unload` because a skill the thread shows is the room a model can give back: a
10568
+ * successful `unload` reopens every call the room denied in that request.
8856
10569
  */
8857
10570
  var LEDGER_NOTES = Object.freeze({
8858
10571
  cue: "Give your complete answer to the request now, from what you already have.",
8859
10572
  results: "Your lookups and recalls in this request returned:",
8860
10573
  repeat: "You already have this result earlier in this request; give your complete answer now as your final message.",
8861
- closure: "recall is closed for the rest of this request; give your final answer from what you have"
10574
+ closure: "recall is closed for the rest of this request; give your final answer from what you have",
10575
+ overflow: "there is no room left for it in this request. Unload a skill you no longer need before you call it again, or answer with what you have."
8862
10576
  });
8863
10577
  /**
8864
- * Supplies the default prompt and tail shares.
10578
+ * Supplies the default prompt, tail, and skills shares.
8865
10579
  */
8866
10580
  var DEFAULT_LEDGER_SHARE = Object.freeze({
8867
10581
  prompt: .7,
8868
- tail: .35
10582
+ tail: .35,
10583
+ skills: .5
8869
10584
  });
8870
10585
  /**
8871
10586
  * Caps the tool-iteration turns of a ledger's agent at 8 turns.
@@ -8902,6 +10617,85 @@ var LEDGER_SCALE_DRIFT = .06;
8902
10617
  var LEDGER_WORD = /[\p{L}\p{N}]+/gu;
8903
10618
  /** Matches the logprob decoding failure `invalid or duplicate top logprob token`, which the ledger holds for the ledger's life. */
8904
10619
  var DETERMINISTIC_JUDGE_ERROR = /invalid or duplicate top logprob token/;
10620
+ /**
10621
+ * Lists the members `LedgerOptions` declares, frozen — the keys the `Ledger` constructor admits.
10622
+ *
10623
+ * @remarks
10624
+ * The constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
10625
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
10626
+ */
10627
+ var LEDGER_OPTION_KEYS = Object.freeze([
10628
+ "judge",
10629
+ "system",
10630
+ "topics",
10631
+ "questions",
10632
+ "thresholds",
10633
+ "capacity",
10634
+ "predict",
10635
+ "think",
10636
+ "gauge",
10637
+ "lookups",
10638
+ "writes",
10639
+ "share",
10640
+ "recall",
10641
+ "correction",
10642
+ "agent",
10643
+ "snapshot"
10644
+ ]);
10645
+ /**
10646
+ * Lists the members the `LedgerAgentOptions` pick declares, frozen — the keys a ledger's `agent`
10647
+ * option admits.
10648
+ *
10649
+ * @remarks
10650
+ * The `Ledger` constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an
10651
+ * own enumerable member outside it, an `AgentOptions` member the pick leaves out included, refuses
10652
+ * with a `ContractError` coded `'structure'`.
10653
+ */
10654
+ var LEDGER_AGENT_KEYS = Object.freeze([
10655
+ "limit",
10656
+ "timeout",
10657
+ "on",
10658
+ "scheduler",
10659
+ "instructions",
10660
+ "skills",
10661
+ "memos",
10662
+ "workspaces",
10663
+ "strict",
10664
+ "authority",
10665
+ "builtins",
10666
+ "workspace"
10667
+ ]);
10668
+ /** Lists the members `ClassifierOptions` declares, frozen — the keys the `Classifier` constructor admits. */
10669
+ var CLASSIFIER_OPTION_KEYS = Object.freeze([
10670
+ "strict",
10671
+ "conversation",
10672
+ "judge",
10673
+ "questions",
10674
+ "topics",
10675
+ "thresholds",
10676
+ "assign",
10677
+ "entities",
10678
+ "correction",
10679
+ "scheduler"
10680
+ ]);
10681
+ /** Lists the members `GaugeOptions` declares, frozen — the keys the `Gauge` constructor admits. */
10682
+ var GAUGE_OPTION_KEYS = Object.freeze([
10683
+ "scale",
10684
+ "overhead",
10685
+ "capacity",
10686
+ "predict",
10687
+ "replay"
10688
+ ]);
10689
+ /**
10690
+ * Sets the 3 UTF-8 bytes one UTF-16 code unit encodes to at most, the divisor that sizes the
10691
+ * workspace text a calibration's third call carries.
10692
+ *
10693
+ * @remarks
10694
+ * A tokenizer that falls back to bytes charges at most one token a byte, so workspace text cut to the
10695
+ * capacity the call without definitions left, divided by this figure, in characters, reports at most
10696
+ * `capacity` apart from a template's few tokens for an added message.
10697
+ */
10698
+ var LEDGER_SAMPLE_BYTES = 3;
8905
10699
  // src/core/ledgers/errors.ts
8906
10700
  /**
8907
10701
  * Reports an invalid ledger configuration, failed calibration, or unowned request selection,
@@ -8910,7 +10704,9 @@ var DETERMINISTIC_JUDGE_ERROR = /invalid or duplicate top logprob token/;
8910
10704
  * @remarks
8911
10705
  * A threshold, share, capacity, limit, topic, lookup, write, question, or gauge outside its bounds is a
8912
10706
  * programmer error and throws this with the matching {@link LedgerErrorCode}. A calibration call that reports
8913
- * no prompt usage throws this with `'GAUGE'`. A snapshot option that `isLedgerSnapshot` refuses
10707
+ * no prompt usage, or a calibration call, with the tool definitions, without them, or with the workspace text, that reports a prompt
10708
+ * over `capacity`, the head included, throws this with `'GAUGE'`; the second holds `capacity` and
10709
+ * `prompt` in its `context`. A snapshot option that `isLedgerSnapshot` refuses
8914
10710
  * throws this with `'SNAPSHOT'`, its `context` holding the `faults`. Narrow a caught value with {@link isLedgerError} and
8915
10711
  * branch on `error.code`. A selection without an active `respond` call, for a conversation other
8916
10712
  * than the ledger's, or for a request other than that call's request or a ledger note, reports
@@ -8952,7 +10748,7 @@ function isLedgerError(value) {
8952
10748
  return holds(() => {
8953
10749
  if (!(value instanceof Error && INTRINSICS.prototype(value) !== Error.prototype && INTRINSICS.describe(value, Symbol.for("@orkestrel/agent.LedgerError"))?.value === value)) return false;
8954
10750
  const code = INTRINSICS.reflect.read(value, "code");
8955
- if (!(code === "REQUEST" || code === "THRESHOLD" || code === "SHARE" || code === "CAPACITY" || code === "LIMIT" || code === "TOPIC" || code === "LOOKUP" || code === "GAUGE" || code === "REFERENCE" || code === "SNAPSHOT" || code === "QUESTION" || code === "WRITE")) return false;
10751
+ if (!(code === "REQUEST" || code === "THRESHOLD" || code === "SHARE" || code === "CAPACITY" || code === "LIMIT" || code === "TOPIC" || code === "LOOKUP" || code === "GAUGE" || code === "REFERENCE" || code === "SNAPSHOT" || code === "QUESTION" || code === "WRITE" || code === "OVERFLOW")) return false;
8956
10752
  const context = INTRINSICS.reflect.read(value, "context");
8957
10753
  return context === void 0 || isObject(context) || isFunction(context);
8958
10754
  });
@@ -8988,9 +10784,10 @@ function digestLedgerWindow(exchanges, messages) {
8988
10784
  * Digests tool definitions as the JSON a provider request carries them, in order.
8989
10785
  *
8990
10786
  * @remarks
8991
- * A ledger stores this digest with its gauge, so a hydrate reuses the gauge only for the same
8992
- * definitions: an added, removed, reordered, or edited definition changes the digest. The digest is
8993
- * a {@link computeLedgerChecksum} checksum and makes no security claim.
10787
+ * A ledger stores this digest with its gauge and compares it at a hydrate and at each `respond`
10788
+ * call, so it reuses the gauge only for the same definitions: an added, removed, reordered, or
10789
+ * edited definition changes the digest. The digest is a {@link computeLedgerChecksum} checksum and
10790
+ * makes no security claim.
8994
10791
  *
8995
10792
  * @param definitions - The tool definitions, as a tool manager's `definitions` method returns them
8996
10793
  * @returns The 16-digit hexadecimal digest, or undefined when JSON cannot render the definitions
@@ -9004,6 +10801,82 @@ function digestLedgerTools(definitions) {
9004
10801
  return text === void 0 ? void 0 : computeLedgerChecksum(text);
9005
10802
  }
9006
10803
  /**
10804
+ * Computes the tokens one estimate unit of a definition costs beyond the calibrated set.
10805
+ *
10806
+ * @remarks
10807
+ * The larger of `scale` and the calibrated set's overhead per estimate unit, or `scale` for an
10808
+ * empty set or one that estimates 0. A set's estimate is `estimateTokens` over its JSON text.
10809
+ *
10810
+ * @param calibrated - The definitions the gauge's overhead priced
10811
+ * @param gauge - The gauge's scale and overhead
10812
+ * @returns The tokens one estimate unit of a definition costs
10813
+ * @example
10814
+ * ```ts
10815
+ * computeToolDensity([], { scale: 1.1, overhead: 400 }) // 1.1
10816
+ * ```
10817
+ */
10818
+ function computeToolDensity(calibrated, gauge) {
10819
+ const estimate = calibrated.length === 0 ? 0 : estimateTokens(JSON.stringify(calibrated));
10820
+ return estimate > 0 ? Math.max(gauge.scale, gauge.overhead / estimate) : gauge.scale;
10821
+ }
10822
+ /**
10823
+ * Computes what a call's advertised set spares of the calibrated overhead and adds beyond it.
10824
+ *
10825
+ * @remarks
10826
+ * Definitions compare by their JSON text, so a definition replaced under its name by another text
10827
+ * is both left out and added. An empty advertised set spares the whole overhead. Otherwise the
10828
+ * calibrated definitions the call leaves out spare the lower of `scale` and the calibrated set's
10829
+ * overhead per estimate unit, times their estimate, so a credit never exceeds their proportional
10830
+ * share of the overhead; the definitions it advertises beyond the calibrated set add
10831
+ * {@link computeToolDensity}, the higher of the two, times theirs. A set's estimate is
10832
+ * `estimateTokens` over its JSON text, 0 for an empty set. The call's price is
10833
+ * `overhead - spared + added`.
10834
+ *
10835
+ * @param calibrated - The definitions the gauge's overhead priced
10836
+ * @param advertised - The definitions the call advertises
10837
+ * @param gauge - The gauge's scale and overhead
10838
+ * @returns The tokens the call spares and adds
10839
+ * @example
10840
+ * ```ts
10841
+ * computeToolPrice([], [], { scale: 1.1, overhead: 400 }) // { spared: 400, added: 0 }
10842
+ * ```
10843
+ */
10844
+ function computeToolPrice(calibrated, advertised, gauge) {
10845
+ if (advertised.length === 0) return {
10846
+ spared: gauge.overhead,
10847
+ added: 0
10848
+ };
10849
+ const sent = new Set(advertised.map((definition) => JSON.stringify(definition)));
10850
+ const priced = new Set(calibrated.map((definition) => JSON.stringify(definition)));
10851
+ const hidden = calibrated.filter((definition) => !sent.has(JSON.stringify(definition)));
10852
+ const extra = advertised.filter((definition) => !priced.has(JSON.stringify(definition)));
10853
+ return {
10854
+ spared: hidden.length === 0 ? 0 : Math.min(gauge.scale, gauge.overhead / estimateTokens(JSON.stringify(calibrated))) * estimateTokens(JSON.stringify(hidden)),
10855
+ added: extra.length === 0 ? 0 : computeToolDensity(calibrated, gauge) * estimateTokens(JSON.stringify(extra))
10856
+ };
10857
+ }
10858
+ /**
10859
+ * Computes the tokens loading a skill adds: its body, and the definitions it names beyond an advertised set.
10860
+ *
10861
+ * @remarks
10862
+ * The body is the text `renderSkillBody` renders, priced at `scale`, and a blank `content` renders
10863
+ * no body. The caller passes the definitions the skill names that the turn does not advertise,
10864
+ * priced as {@link computeToolPrice} prices an addition.
10865
+ *
10866
+ * @param skill - The skill to load
10867
+ * @param added - The definitions the skill names that the advertised set leaves out
10868
+ * @param calibrated - The definitions the gauge's overhead priced
10869
+ * @param gauge - The gauge's scale and overhead
10870
+ * @returns The tokens the load adds
10871
+ * @example
10872
+ * ```ts
10873
+ * computeSkillPrice({ name: 'refunds', content: 'Check the order first.' }, [], [], { scale: 1, overhead: 0 }) // 9
10874
+ * ```
10875
+ */
10876
+ function computeSkillPrice(skill, added, calibrated, gauge) {
10877
+ return (skill.content.trim() === "" ? 0 : gauge.scale * estimateTokens(renderSkillBody(skill))) + computeToolPrice(calibrated, [...calibrated, ...added], gauge).added;
10878
+ }
10879
+ /**
9007
10880
  * Computes a checksum of text: two 32-bit FNV-1a lanes over its code points, as 16 hexadecimal digits.
9008
10881
  *
9009
10882
  * @remarks
@@ -9232,23 +11105,6 @@ function splitWords(part) {
9232
11105
  return part.split(/\s+/).map((word) => word.replace(/^[\p{P}\p{S}]+|[\p{P}\p{S}]+$/gu, "").toLowerCase()).filter((word) => word !== "");
9233
11106
  }
9234
11107
  /**
9235
- * Finds the call that owns a tool message within a collected tool group.
9236
- * @param group - The assistant leader followed by its tool results
9237
- * @param message - The tool result to pair; distinct leader ids pair by result id when any result has one
9238
- * @returns The call matching the result's call id, or its position when the leader repeats an id or every result lacks an id; undefined when unpaired
9239
- * @example
9240
- * ```ts
9241
- * findLedgerCall(group, result)?.arguments
9242
- * ```
9243
- */
9244
- function findLedgerCall(group, message) {
9245
- const [leader, ...results] = group;
9246
- const at = results.findIndex((result) => result.id === message.id);
9247
- if (message.role !== "tool" || at < 0) return void 0;
9248
- const calls = leader?.calls ?? [];
9249
- return !(new Set(calls.map((call) => call.id)).size !== calls.length) && results.some((result) => result.call !== void 0) ? calls.find((call) => call.id === message.call) : calls[at];
9250
- }
9251
- /**
9252
11108
  * Ranks a source the records render for removal before the next prompt.
9253
11109
  *
9254
11110
  * @param group - The planning group: 1 for a topic match, 2 for an off-topic rule or correction, or 3 for a name match
@@ -9942,23 +11798,34 @@ function matchesCutLine(line) {
9942
11798
  /**
9943
11799
  * Renders the tail stub of a lookup result from an earlier request.
9944
11800
  *
11801
+ * @remarks
11802
+ * A `hidden` stub names the `recall` call that finds its result, with the first string argument,
11803
+ * trimmed, or `its id` when no argument is a string or that one is blank, only while `offered` is `true`. The ledger plans the
11804
+ * tail with every stub naming `recall`, then renders the tail's stubs again after it admits a window
11805
+ * rung, with `offered` set to whether that rung offers `recall` and the scope admits it, so the
11806
+ * second render can only shorten a stub and raises no price. `offered` changes no other state.
11807
+ *
9945
11808
  * @param name - The lookup tool name
9946
11809
  * @param args - The arguments the call carried; arguments JSON cannot carry leave the call as its bare tool name, per {@link renderLookupCall}
9947
11810
  * @param state - What became of the result: `failed`, `empty`, `shown` in the records, or `hidden` from them
9948
11811
  * @param write - If `true`, the call is a write's, whose `empty` state reads `changed nothing`; if `false`, a lookup's, whose `empty` state reads `no record`. Default: `false`
11812
+ * @param offered - If `true`, a `hidden` result names the `recall` route; if `false`, because the admitted rung offers no `recall` or the scope excludes it, it reads `result not shown`. Default: `true`
9949
11813
  * @returns The stub text, which carries the call and its state
9950
11814
  *
9951
11815
  * @example
9952
11816
  * ```ts
9953
11817
  * renderStub('lookup_order', { id: 'BW-5512' }, 'hidden')
9954
11818
  * // 'lookup_order {"id":"BW-5512"}: result not shown; call recall with BW-5512'
11819
+ * renderStub('lookup_order', { id: 'BW-5512' }, 'hidden', false, false)
11820
+ * // 'lookup_order {"id":"BW-5512"}: result not shown'
9955
11821
  * ```
9956
11822
  */
9957
- function renderStub(name, args, state, write = false) {
11823
+ function renderStub(name, args, state, write = false, offered = true) {
9958
11824
  const head = renderLookupCall(name, args);
9959
11825
  if (state === "failed") return `${head}: failed`;
9960
11826
  if (state === "empty") return write ? `${head}: changed nothing` : `${head}: no record`;
9961
11827
  if (state === "shown") return `${head}: result shown under Records below`;
11828
+ if (!offered) return `${head}: result not shown`;
9962
11829
  return `${head}: result not shown; call recall with ${(Object.values(args).find(isString) ?? "").trim() || "its id"}`;
9963
11830
  }
9964
11831
  /**
@@ -10328,6 +12195,51 @@ function collectLedgerEvidence(messages, sources, amendments) {
10328
12195
  ]), { ...edge }]))
10329
12196
  };
10330
12197
  }
12198
+ /**
12199
+ * Cuts the workspace text a calibration sample adds over its head to a number of characters, or
12200
+ * returns `undefined` when the sample adds anything else.
12201
+ *
12202
+ * @remarks
12203
+ * The head and the shown sample are what a context's `build` returns for an empty turn without and
12204
+ * with the workspace: the shown sample holds the head's messages with the workspace section appended
12205
+ * to the context message, or one context message added where the head holds none. The cut keeps the
12206
+ * first `limit` characters of that added text and every other message as the sample holds it. A
12207
+ * sample whose messages differ from the head's in any other way, or that adds no text, returns
12208
+ * `undefined`.
12209
+ *
12210
+ * @param head - The head built with no workspace
12211
+ * @param shown - The head built with the workspace a request renders
12212
+ * @param limit - The characters of added text to keep
12213
+ * @returns The cut sample, or `undefined` when the sample adds no trailing text to one message
12214
+ *
12215
+ * @example
12216
+ * ```ts
12217
+ * import { cutWorkspaceSample } from '@orkestrel/agent'
12218
+ *
12219
+ * const head = [{ id: 's', role: 'system', content: 'Serve the desk.' }] as const
12220
+ * const shown = [...head, { id: 'c', role: 'user', content: '## Workspace\n\nFile: a.md' }] as const
12221
+ * cutWorkspaceSample(head, shown, 12)?.[1]?.content // '## Workspace'
12222
+ * ```
12223
+ */
12224
+ function cutWorkspaceSample(head, shown, limit) {
12225
+ const at = shown.findIndex((message, index) => message.role !== head[index]?.role || message.content !== head[index]?.content);
12226
+ const added = shown[at];
12227
+ const extended = shown.length === head.length ? head[at] : void 0;
12228
+ const prefix = extended?.content ?? "";
12229
+ const rest = shown.slice(at + 1);
12230
+ const held = head.slice(extended === void 0 ? at : at + 1);
12231
+ if (added === void 0 || shown.length - head.length > 1 || shown.length < head.length) return void 0;
12232
+ if (!added.content.startsWith(prefix) || added.content.length === prefix.length) return void 0;
12233
+ if (rest.length !== held.length || rest.some((message, index) => message.role !== held[index]?.role || message.content !== held[index]?.content)) return void 0;
12234
+ return [
12235
+ ...shown.slice(0, at),
12236
+ {
12237
+ ...added,
12238
+ content: prefix + added.content.slice(prefix.length, prefix.length + limit)
12239
+ },
12240
+ ...rest
12241
+ ];
12242
+ }
10331
12243
  // src/core/ledgers/Classifier.ts
10332
12244
  /**
10333
12245
  * Files messages through the conversation's judgment manager and reads their categories and corrections.
@@ -10348,6 +12260,7 @@ var Classifier = class {
10348
12260
  * @param options - The conversation, judge, questions, cutoffs, and handlers
10349
12261
  */
10350
12262
  constructor(options) {
12263
+ refuseOptionKeys(options, CLASSIFIER_OPTION_KEYS, "Classifier", "ClassifierOptions");
10351
12264
  this.#options = options;
10352
12265
  }
10353
12266
  /**
@@ -10724,6 +12637,7 @@ var Gauge = class {
10724
12637
  * @throws {LedgerError} Thrown when `predict` is not a nonnegative safe integer less than `capacity` (code `'CAPACITY'`)
10725
12638
  */
10726
12639
  constructor(options) {
12640
+ refuseOptionKeys(options, GAUGE_OPTION_KEYS, "Gauge", "GaugeOptions");
10727
12641
  if (!isFiniteNumber(options.scale) || options.scale <= 0) throw new LedgerError("GAUGE", "gauge scale must be finite and greater than 0");
10728
12642
  if (!isFiniteNumber(options.overhead) || options.overhead < 0) throw new LedgerError("GAUGE", "gauge overhead must be finite and at least 0");
10729
12643
  if (!isPositiveSafeInteger(options.capacity)) throw new LedgerError("CAPACITY", "gauge capacity must be a positive safe integer");
@@ -10754,7 +12668,7 @@ var Gauge = class {
10754
12668
  }
10755
12669
  remainder(calls) {
10756
12670
  const call = calls.at(-1);
10757
- 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);
12671
+ 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)) + (call?.added ?? 0) + this.#scale * (call?.estimate ?? 0);
10758
12672
  return Math.max(0, this.#capacity - used);
10759
12673
  }
10760
12674
  reserve(calls, longest) {
@@ -10774,7 +12688,7 @@ var Gauge = class {
10774
12688
  }
10775
12689
  observe(calls, reply) {
10776
12690
  const first = calls[0];
10777
- if (first !== void 0 && isFiniteNumber(first.prompt) && first.estimate > 0 && ((first.spared ?? 0) === 0 || (first.spared ?? 0) >= this.#overhead)) {
12691
+ if (first !== void 0 && isFiniteNumber(first.prompt) && first.estimate > 0 && (first.added ?? 0) === 0 && ((first.spared ?? 0) === 0 || (first.spared ?? 0) >= this.#overhead)) {
10778
12692
  const priced = first.prompt - Math.max(0, this.#overhead - (first.spared ?? 0));
10779
12693
  if (priced > 0) this.#scale = priced / first.estimate;
10780
12694
  }
@@ -10982,6 +12896,7 @@ var LedgerRequest = class {
10982
12896
  #admitted = /* @__PURE__ */ new Set();
10983
12897
  #denials = /* @__PURE__ */ new Set();
10984
12898
  #listed = [];
12899
+ #commits = [];
10985
12900
  #recalls = 0;
10986
12901
  #closed = false;
10987
12902
  /**
@@ -11168,6 +13083,23 @@ var LedgerRequest = class {
11168
13083
  lines(result) {
11169
13084
  return this.#listed.findLast((listing) => listing.result === result)?.lines;
11170
13085
  }
13086
+ commit(price, skill) {
13087
+ this.#commits = [...this.#commits, {
13088
+ price,
13089
+ turn: this.calls.length,
13090
+ ...skill === void 0 ? {} : { skill }
13091
+ }];
13092
+ }
13093
+ get committed() {
13094
+ return this.#inflight().reduce((sum, entry) => sum + entry.price, 0);
13095
+ }
13096
+ get loads() {
13097
+ return this.#inflight().flatMap((entry) => entry.skill === void 0 ? [] : [entry.skill]);
13098
+ }
13099
+ #inflight() {
13100
+ const turn = this.calls.length;
13101
+ return this.#commits.filter((entry) => entry.turn === turn);
13102
+ }
11171
13103
  };
11172
13104
  // src/core/ledgers/LedgerScan.ts
11173
13105
  /**
@@ -11445,16 +13377,20 @@ var Ledger = class {
11445
13377
  #share;
11446
13378
  #conversation;
11447
13379
  #agent;
13380
+ #record;
13381
+ #builtins;
13382
+ #given;
11448
13383
  #classifier;
11449
13384
  #requests = /* @__PURE__ */ new Set();
11450
13385
  #annotations = /* @__PURE__ */ new Set();
11451
13386
  #results = /* @__PURE__ */ new Map();
11452
13387
  #pending = /* @__PURE__ */ new Map();
11453
- #live = /* @__PURE__ */ new Set();
13388
+ #live = /* @__PURE__ */ new Map();
11454
13389
  #scans = /* @__PURE__ */ new Set();
11455
13390
  #calibrations = /* @__PURE__ */ new Set();
11456
13391
  #gauge;
11457
- #digest;
13392
+ #measured;
13393
+ #refusal;
11458
13394
  #window = [];
11459
13395
  #evidence = collectLedgerEvidence([], /* @__PURE__ */ new Set(), []);
11460
13396
  /**
@@ -11469,6 +13405,8 @@ var Ledger = class {
11469
13405
  * @throws {ConversationError} Thrown when `isLedgerSnapshot` admits `snapshot` but its conversation holds a `system` message (code `'ROLE'`) or repeats an id (code `'SNAPSHOT'`)
11470
13406
  */
11471
13407
  constructor(provider, options) {
13408
+ refuseOptionKeys(options, LEDGER_OPTION_KEYS, "Ledger", "LedgerOptions");
13409
+ refuseOptionKeys(options.agent, LEDGER_AGENT_KEYS, "Ledger agent", "LedgerAgentOptions");
11472
13410
  for (const key of [
11473
13411
  "category",
11474
13412
  "topic",
@@ -11546,6 +13484,9 @@ var Ledger = class {
11546
13484
  }) }),
11547
13485
  execute: (args) => this.#recall(args)
11548
13486
  }));
13487
+ this.#builtins = requireBuiltins(options.agent?.builtins, tools);
13488
+ if (this.#builtins.length > 0 && host === void 0) throw new AgentError("AUTHORITY", "agent.builtins are set and no agent.authority is; pass authority: createAuthority({ fallback: { zone: 'default', verdict: 'allow' } }) to allow every call, or rules with a deny fallback");
13489
+ this.#given = requireWorkspace(options.agent?.workspace);
11549
13490
  const advertised = new Authority({
11550
13491
  rules: [{
11551
13492
  match: (context) => names.includes(context.call.name),
@@ -11564,17 +13505,14 @@ var Ledger = class {
11564
13505
  system: options.system,
11565
13506
  tools,
11566
13507
  authority: {
11567
- evaluate: (context) => this.#current()?.denied(context.call.name, context.call.arguments) === true ? {
11568
- zone: "ledger",
11569
- verdict: "deny",
11570
- reason: LEDGER_NOTES.repeat
11571
- } : host === void 0 || context.call.name === "recall" || !names.includes(context.call.name) ? advertised.evaluate(context) : host.evaluate(context),
13508
+ evaluate: (context) => this.#evaluate(context, host, advertised, names),
11572
13509
  ask: (context, decision, signal) => this.#current()?.denied(context.call.name, context.call.arguments) === true ? Promise.resolve(false) : host?.ask === void 0 ? advertised.ask(context, decision, signal) : host.ask(context, decision, signal)
11573
13510
  },
11574
13511
  limit: options.agent?.limit ?? 8,
11575
13512
  select: (conversation, request, signal) => this.#select(conversation, request, signal)
11576
13513
  });
11577
- const digest = digestLedgerTools(this.#agent.context.tools.definitions());
13514
+ const calibrated = this.#calibrated();
13515
+ const digest = digestLedgerTools(calibrated);
11578
13516
  const gauge = options.gauge ?? (snapshot?.gauge?.digest === digest ? snapshot?.gauge : void 0);
11579
13517
  if (gauge !== void 0) {
11580
13518
  this.#gauge = new Gauge({
@@ -11584,10 +13522,10 @@ var Ledger = class {
11584
13522
  predict: this.#predict,
11585
13523
  replay: this.#replay
11586
13524
  });
11587
- this.#digest = digest;
13525
+ this.#measured = calibrated;
11588
13526
  }
11589
13527
  this.#agent.emitter.on("deny", (call, _reason, decision) => {
11590
- if (decision !== void 0 && call.name !== "recall" && names.includes(call.name)) this.#current()?.deny(call.name, call.arguments);
13528
+ if (decision !== void 0 && decision.reason !== LEDGER_NOTES.overflow && call.name !== "recall" && (names.includes(call.name) || this.#enables(call.name))) this.#current()?.deny(call.name, call.arguments);
11591
13529
  });
11592
13530
  this.#agent.emitter.on("tool", (call, result) => {
11593
13531
  this.#flush();
@@ -11600,21 +13538,34 @@ var Ledger = class {
11600
13538
  const request = this.#current();
11601
13539
  if (request?.calls.at(-1)?.tools === 0) request.abort();
11602
13540
  this.#release(call, result);
13541
+ this.#reopen(call, result);
11603
13542
  });
11604
13543
  this.#agent.emitter.on("select", (selection) => {
11605
13544
  if (isLedgerError(selection.fault) && selection.fault.code === "REQUEST") return;
11606
13545
  this.#current()?.adopt(selection, this.#conversation.messages().length);
11607
13546
  });
11608
13547
  this.#agent.emitter.on("fault", (error) => this.#current()?.fault(error));
11609
- this.#agent.emitter.on("turn", () => this.#observeTurn());
13548
+ this.#agent.emitter.on("request", (index, sent) => {
13549
+ if (this.#gate(index, sent)) this.#observeRequest(sent);
13550
+ });
11610
13551
  this.#agent.emitter.on("usage", (usage) => this.#current()?.spend(usage));
13552
+ const agent = this.#agent;
13553
+ this.#record = Object.freeze({
13554
+ emitter: agent.emitter,
13555
+ id: agent.id,
13556
+ get status() {
13557
+ return agent.status;
13558
+ },
13559
+ context: agent.context,
13560
+ abort: (reason) => agent.abort(reason)
13561
+ });
11611
13562
  }
11612
13563
  /**
11613
- * Returns the owned request engine.
11614
- * @returns The engine serving this conversation
13564
+ * Returns the record of the owned request engine, which starts no run.
13565
+ * @returns The frozen record of the agent serving this conversation
11615
13566
  */
11616
13567
  get agent() {
11617
- return this.#agent;
13568
+ return this.#record;
11618
13569
  }
11619
13570
  /**
11620
13571
  * Returns the owned message history.
@@ -11635,9 +13586,10 @@ var Ledger = class {
11635
13586
  }
11636
13587
  /**
11637
13588
  * Measures message scale and tool overhead from provider prompt usage.
11638
- * @param signal - The signal bounding both calibration calls; an abort rejects with its reason
13589
+ * @param signal - The signal bounding every calibration call; an abort rejects with its reason
11639
13590
  * @returns The stored scale and overhead cost
11640
- * @throws {LedgerError} Thrown when either call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
13591
+ * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
13592
+ * @throws {LedgerError} Thrown when a calibration call, with the tool definitions, without them, or with the workspace text, reports a prompt over `capacity`, the head included (code `'GAUGE'`)
11641
13593
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
11642
13594
  */
11643
13595
  async calibrate(signal) {
@@ -11660,7 +13612,7 @@ var Ledger = class {
11660
13612
  const conversation = this.#conversation.snapshot();
11661
13613
  const ids = conversation.messages.map((message) => message.id);
11662
13614
  const gauge = this.gauge;
11663
- const priced = this.#digest;
13615
+ const priced = this.#digest();
11664
13616
  const window = {
11665
13617
  exchanges: this.#window.map((entry) => ({
11666
13618
  request: entry.request.id,
@@ -11694,13 +13646,15 @@ var Ledger = class {
11694
13646
  /**
11695
13647
  * Appends a request and serves it, recovering an unfinished or rejected first pass with one answer pass.
11696
13648
  * In either pass, a call the model makes in a turn that advertises no tools runs nothing and ends the pass after that generation, whatever the agent's `limit`, and when every call of that turn failed, the ledger stores the reply without its calls: a reply with text ends the pass complete, and one without text ends it partial with empty content.
11697
- * A calibration call that reports no prompt usage, or a prompt usage of 0 or less, rejects with `LedgerError` code `'GAUGE'`; a provider error during calibration rejects with that error.
13649
+ * A calibration call that reports no prompt usage, or a prompt usage of 0 or less, rejects with `LedgerError` code `'GAUGE'`; a provider error during calibration rejects with that error. A calibration call, with the tool definitions, without them, or with the workspace text, that reports a prompt over `capacity`, the head included, rejects with the same code and stores no request.
11698
13650
  * When the answer pass rejects, `respond` rejects with that error, and under the default `strict` a judge failure or another selection failure rejects it too. A rejection belongs to its own call: the ledger stays free, and the next call asks the judge again about whatever is still unanswered.
11699
13651
  * @param content - The request text
11700
13652
  * @param signal - The caller's cancellation signal; an abort during calibration rejects with its reason
11701
13653
  * @returns The final pass, the usage of every pass, and the filing attempts of this request alone
11702
13654
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
11703
13655
  * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
13656
+ * @throws {LedgerError} Thrown when a calibration call, with the tool definitions, without them, or with the workspace text, reports a prompt over `capacity`, the head included (code `'GAUGE'`)
13657
+ * @throws {LedgerError} Thrown when the answer pass prices over `capacity - predict` with its note left out, or a host act after the note grows its request past that limit (code `'OVERFLOW'`)
11704
13658
  * @throws {ContractError} Thrown when `signal` is defined and is not a native `AbortSignal`
11705
13659
  */
11706
13660
  async respond(content, signal) {
@@ -11708,12 +13662,15 @@ var Ledger = class {
11708
13662
  const caller = createAbort({ ...signal === void 0 ? {} : { signal } }).signal;
11709
13663
  const request = new LedgerRequest(content, this.#options.recall?.limit);
11710
13664
  const scope = this.#agent.context.scope;
11711
- this.#live.add(request);
13665
+ this.#live.set(request, scope);
11712
13666
  try {
11713
- if (this.#gauge === void 0) await this.#measureGauge(caller);
13667
+ if (this.#gauge === void 0 || digestLedgerTools(this.#calibrated()) !== this.#digest()) {
13668
+ this.#gauge = void 0;
13669
+ await this.#measureGauge(caller);
13670
+ }
11714
13671
  const entered = request.enter(this.#conversation);
11715
13672
  this.#requests.add(entered.id);
11716
- const first = this.#dropStrayCalls(request, await this.#generatePass(request, caller, this.#options.think).catch((error) => {
13673
+ const first = this.#dropStrayCalls(request, await this.#generatePass(request, caller, this.#options.think, scope).catch((error) => {
11717
13674
  if (request.faulted(error)) throw error;
11718
13675
  return {
11719
13676
  content: "",
@@ -11732,13 +13689,12 @@ var Ledger = class {
11732
13689
  role: "user",
11733
13690
  content: LEDGER_NOTES.cue
11734
13691
  }).id);
11735
- const previous = this.#agent.context.scope;
13692
+ const filter = intersectFilters(scope, { tools: [] });
11736
13693
  const answer = createScope({
11737
- ...previous,
13694
+ ...filter,
11738
13695
  name: "answer",
11739
- tools: [],
11740
13696
  select: async (conversation, message, runSignal) => {
11741
- const selection = await this.#select(conversation, message, runSignal);
13697
+ const selection = await this.#select(conversation, message, runSignal, filter);
11742
13698
  if (selection.fault !== void 0) return selection;
11743
13699
  return {
11744
13700
  ...selection,
@@ -11746,12 +13702,9 @@ var Ledger = class {
11746
13702
  };
11747
13703
  }
11748
13704
  });
11749
- this.#agent.context.apply(answer);
11750
- try {
11751
- passes.push(this.#dropStrayCalls(request, await this.#generatePass(request, caller, false)));
11752
- } finally {
11753
- if (this.#agent.context.scope === answer) this.#agent.context.apply(previous);
11754
- }
13705
+ passes.push(this.#dropStrayCalls(request, await this.#generatePass(request, caller, false, answer)));
13706
+ const refusal = this.#refusal;
13707
+ if (refusal !== void 0 && !caller.aborted) throw refusal;
11755
13708
  }
11756
13709
  const last = passes.at(-1) ?? first;
11757
13710
  request.think(this.#conversation.messages());
@@ -11777,26 +13730,31 @@ var Ledger = class {
11777
13730
  }
11778
13731
  async #measureGauge(signal) {
11779
13732
  try {
11780
- const messages = stripThinking(this.#agent.context.build({
11781
- messages: [...this.#conversation.view(), {
11782
- id: "calibration",
11783
- role: "user",
11784
- content: ""
11785
- }],
11786
- judgments: []
11787
- }), this.#replay).slice(0, -1);
11788
- const definitions = this.#agent.context.tools.definitions();
13733
+ const messages = this.#agent.context.build({
13734
+ selection: {
13735
+ messages: [],
13736
+ judgments: []
13737
+ },
13738
+ scope: createScope({
13739
+ name: "calibration",
13740
+ workspaces: []
13741
+ })
13742
+ });
13743
+ const definitions = this.#calibrated();
11789
13744
  const priced = await this.#provider.generate(messages, signal, definitions, { think: false });
13745
+ this.#refuseOverCapacity("with", priced.usage?.prompt);
11790
13746
  const bare = await this.#provider.generate(messages, signal, void 0, { think: false });
11791
- if (!isFiniteNumber(priced.usage?.prompt) || priced.usage.prompt <= 0 || !isFiniteNumber(bare.usage?.prompt) || bare.usage.prompt <= 0) throw new LedgerError("GAUGE", "calibration requires prompt usage from both calls");
13747
+ this.#refuseOverCapacity("without", bare.usage?.prompt);
13748
+ if (!isFiniteNumber(priced.usage?.prompt) || priced.usage.prompt <= 0 || !isFiniteNumber(bare.usage?.prompt) || bare.usage.prompt <= 0) throw new LedgerError("GAUGE", "calibration requires prompt usage from each call");
13749
+ const scale = await this.#measureScale(messages, bare.usage.prompt, signal);
11792
13750
  this.#gauge = new Gauge({
11793
13751
  capacity: this.#options.capacity,
11794
13752
  predict: this.#predict,
11795
13753
  replay: this.#replay,
11796
- scale: bare.usage.prompt / estimateMessages(messages),
13754
+ scale,
11797
13755
  overhead: Math.max(0, priced.usage.prompt - bare.usage.prompt)
11798
13756
  });
11799
- this.#digest = digestLedgerTools(definitions);
13757
+ this.#measured = definitions;
11800
13758
  return {
11801
13759
  scale: this.#gauge.scale,
11802
13760
  overhead: this.#gauge.overhead
@@ -11806,13 +13764,52 @@ var Ledger = class {
11806
13764
  throw error;
11807
13765
  }
11808
13766
  }
11809
- async #generatePass(request, signal, think) {
13767
+ async #measureScale(head, bare, signal) {
13768
+ const rate = bare / estimateMessages(head);
13769
+ const workspace = this.#show(intersectFilters(this.#agent.context.scope, void 0)).workspace;
13770
+ const files = (resolveWorkspace(this.#agent.context.workspaces, workspace, void 0)?.files() ?? []).flatMap((file) => isTextContent(file.content) ? [file.path] : []);
13771
+ const shown = this.#agent.context.build({
13772
+ selection: {
13773
+ messages: [],
13774
+ judgments: []
13775
+ },
13776
+ scope: createScope({
13777
+ name: "calibration",
13778
+ files
13779
+ }),
13780
+ disclosure: {
13781
+ skills: [],
13782
+ ...workspace === void 0 ? {} : { workspace }
13783
+ }
13784
+ });
13785
+ const whole = estimateMessages(shown) - estimateMessages(head);
13786
+ const cut = cutWorkspaceSample(head, shown, Math.floor((this.#options.capacity - bare) / 3));
13787
+ const part = cut === void 0 ? 0 : estimateMessages(cut) - estimateMessages(head);
13788
+ if (cut === void 0 || whole <= 0 || part <= 0) return rate;
13789
+ const third = await this.#provider.generate(cut, signal, void 0, { think: false });
13790
+ this.#refuseOverCapacity("workspace", third.usage?.prompt);
13791
+ if (!isFiniteNumber(third.usage?.prompt) || third.usage.prompt <= 0) throw new LedgerError("GAUGE", "calibration requires prompt usage from each call");
13792
+ const tokens = bare + (third.usage.prompt - bare) * whole / part;
13793
+ return Math.max(rate, tokens / estimateMessages(shown));
13794
+ }
13795
+ #refuseOverCapacity(call, prompt) {
13796
+ const capacity = this.#options.capacity;
13797
+ const subject = call === "workspace" ? "with the workspace text" : `${call} the tool definitions`;
13798
+ const remedy = call === "workspace" ? "narrow what the scope shows of the workspace, or raise capacity" : "hold tools behind a skill with the `load` built-in, register fewer tools, or raise capacity";
13799
+ if (isFiniteNumber(prompt) && prompt > capacity) throw new LedgerError("GAUGE", `the calibration call ${subject} prices ${prompt} tokens, the head included, over the capacity ${capacity}; ${remedy}`, { context: {
13800
+ capacity,
13801
+ prompt
13802
+ } });
13803
+ }
13804
+ async #generatePass(request, signal, think, scope) {
13805
+ this.#refusal = void 0;
11810
13806
  request.open(this.#conversation.messages().length);
11811
13807
  const stop = request.signal;
11812
13808
  return await this.#agent.generate({
11813
- signal: stop === void 0 ? signal : AbortSignal.any([signal, stop]),
13809
+ signal: stop === void 0 ? signal : linkSignal(signal, stop),
11814
13810
  conversation: this.#conversation,
11815
- ...think === void 0 ? {} : { think }
13811
+ ...think === void 0 ? {} : { think },
13812
+ ...scope === void 0 ? {} : { scope }
11816
13813
  });
11817
13814
  }
11818
13815
  #dropStrayCalls(request, pass) {
@@ -11838,12 +13835,103 @@ var Ledger = class {
11838
13835
  };
11839
13836
  }
11840
13837
  #current() {
11841
- const [request] = this.#live;
13838
+ const [request] = this.#live.keys();
11842
13839
  return request?.message === void 0 ? void 0 : request;
11843
13840
  }
11844
13841
  #busy() {
11845
13842
  return this.#live.size > 0 || this.#calibrations.size > 0;
11846
13843
  }
13844
+ #evaluate(context, host, advertised, names) {
13845
+ const request = this.#current();
13846
+ const { name, arguments: args } = context.call;
13847
+ if (request?.denied(name, args) === true) return {
13848
+ zone: "ledger",
13849
+ verdict: "deny",
13850
+ reason: LEDGER_NOTES.repeat
13851
+ };
13852
+ if (this.#enables(name)) {
13853
+ const admitted = attempt(() => request?.admit(name, args));
13854
+ if (!admitted.success) {
13855
+ if (isError(admitted.error) && admitted.error.message === LEDGER_NOTES.repeat) return {
13856
+ zone: "ledger",
13857
+ verdict: "deny",
13858
+ reason: LEDGER_NOTES.repeat
13859
+ };
13860
+ throw admitted.error;
13861
+ }
13862
+ if (request !== void 0) {
13863
+ const commitment = this.#room(request, name, args);
13864
+ if (commitment === void 0) return {
13865
+ zone: "ledger",
13866
+ verdict: "deny",
13867
+ reason: LEDGER_NOTES.overflow
13868
+ };
13869
+ request.commit(commitment.price, commitment.skill);
13870
+ }
13871
+ return host === void 0 ? advertised.evaluate(context) : host.evaluate(context);
13872
+ }
13873
+ const decision = host === void 0 || name === "recall" || !names.includes(name) ? advertised.evaluate(context) : host.evaluate(context);
13874
+ if (request !== void 0 && decision.verdict !== "deny" && name !== "recall" && names.includes(name)) request.commit(this.#priceResult(request.calls));
13875
+ return decision;
13876
+ }
13877
+ #room(request, name, args) {
13878
+ if (name !== "load" && name !== "remember" && name !== "switch") return { price: 0 };
13879
+ const gauge = this.#gauge;
13880
+ if (gauge === void 0) return void 0;
13881
+ const room = gauge.remainder(request.calls) - this.#predict;
13882
+ const admitted = admitValue(builtinToolContracts[name].contract, { ...args });
13883
+ const values = admitted.success ? admitted.value.value : {};
13884
+ if (name === "remember") {
13885
+ const line = isString(values["name"]) && isString(values["description"]) ? renderIndexEntry({
13886
+ name: values["name"],
13887
+ description: values["description"]
13888
+ }) : "";
13889
+ const price = gauge.scale * estimateTokens(line);
13890
+ return (request.committed + price) * 1.06 > room ? void 0 : { price };
13891
+ }
13892
+ const ceiling = this.#ceiling(request);
13893
+ const shown = this.#show(ceiling);
13894
+ if (name === "switch") {
13895
+ const workspace = values["workspace"];
13896
+ const price = isString(workspace) ? gauge.scale * Math.max(0, this.#estimateHead(request, ceiling, {
13897
+ skills: shown.skills,
13898
+ workspace
13899
+ }) - this.#estimateHead(request, ceiling, shown)) : 0;
13900
+ return (request.committed + price) * 1.06 > room ? void 0 : { price };
13901
+ }
13902
+ const thread = [.../* @__PURE__ */ new Set([...shown.skills, ...request.loads])];
13903
+ const skill = filterAllowList(ceiling.skills, this.#agent.context.skills.skills(), (one) => one.name).find((one) => one.name === values["skill"]);
13904
+ if (skill === void 0 || thread.includes(skill.name)) return { price: 0 };
13905
+ const price = this.#priceSkill(skill, ceiling, this.#advertise(ceiling, thread));
13906
+ const budget = (this.#options.capacity - this.#predict) * this.#share.prompt;
13907
+ return (request.committed + price + this.#priceResult(request.calls)) * 1.06 > room || this.#priceShown({ skills: thread }, ceiling) + price > this.#share.skills * budget ? void 0 : {
13908
+ price,
13909
+ skill: skill.name
13910
+ };
13911
+ }
13912
+ #estimateHead(request, ceiling, shown) {
13913
+ return estimateMessages(stripThinking(this.#agent.context.build({
13914
+ ...request.selection === void 0 ? {} : { selection: request.selection },
13915
+ disclosure: shown,
13916
+ switchable: this.#advertise(ceiling, shown.skills).some((tool) => tool.name === "switch")
13917
+ }), this.#replay));
13918
+ }
13919
+ #reopen(call, result) {
13920
+ const request = this.#current();
13921
+ const entered = request?.message;
13922
+ if (request === void 0 || entered === void 0 || !result.success || !this.#enables(call.name)) return;
13923
+ const messages = this.#conversation.messages();
13924
+ const pairs = collectToolGroups(messages.slice(messages.findIndex((message) => message.id === entered.id) + 1)).flatMap((group) => group.slice(1).flatMap((message) => {
13925
+ const owner = findToolCall(group, message);
13926
+ return owner === void 0 ? [] : [{
13927
+ call: owner,
13928
+ content: message.content
13929
+ }];
13930
+ }));
13931
+ const key = canonicalStringify(call.arguments);
13932
+ const reopened = pairs.filter(({ call: earlier, content }) => call.name === "unload" ? earlier.name === "load" && earlier.arguments.skill === call.arguments.skill || content === settleToolResult(denyCall(earlier, LEDGER_NOTES.overflow)).content : call.name === "load" ? earlier.name === "unload" && earlier.arguments.skill === call.arguments.skill : call.name === "switch" ? earlier.name === "switch" && canonicalStringify(earlier.arguments) !== key : call.name === "remember" && earlier.name === "memo" && earlier.arguments.name === call.arguments.name);
13933
+ for (const { call: earlier } of reopened) request.release(earlier.name, earlier.arguments);
13934
+ }
11847
13935
  #flush() {
11848
13936
  if (this.#pending.size === 0) return;
11849
13937
  const messages = this.#conversation.messages();
@@ -11894,7 +13982,7 @@ var Ledger = class {
11894
13982
  for (const message of messages) {
11895
13983
  if (!snapshot.failures.includes(message.id)) continue;
11896
13984
  const group = groups.find((entries) => entries.some((one) => one.id === message.id));
11897
- const call = group === void 0 ? void 0 : findLedgerCall(group, message);
13985
+ const call = group === void 0 ? void 0 : findToolCall(group, message);
11898
13986
  this.#results.set(message.id, {
11899
13987
  success: false,
11900
13988
  id: call?.id ?? message.call ?? message.id,
@@ -11924,7 +14012,7 @@ var Ledger = class {
11924
14012
  #readLookups(messages) {
11925
14013
  this.#flush();
11926
14014
  return collectToolGroups(messages).flatMap((group) => group.slice(1).flatMap((message) => {
11927
- const call = findLedgerCall(group, message);
14015
+ const call = findToolCall(group, message);
11928
14016
  const lookup = this.#options.lookups?.find((one) => one.tool.name === call?.name);
11929
14017
  const write = this.#options.writes?.find((one) => one.tool.name === call?.name);
11930
14018
  if (call === void 0 || lookup === void 0 && write === void 0 || this.#results.get(message.id)?.success === false) return [];
@@ -11991,7 +14079,7 @@ var Ledger = class {
11991
14079
  const start = messages.findIndex((message) => message.id === request.id);
11992
14080
  return messages.slice(start + 1).filter((message) => message.role !== "assistant" || hasCalls(message) || message.content.trim() !== "");
11993
14081
  }
11994
- async #select(conversation, message, signal) {
14082
+ async #select(conversation, message, signal, filter) {
11995
14083
  const request = this.#current();
11996
14084
  const selected = request?.message;
11997
14085
  if (conversation !== this.#conversation || request === void 0 || selected === void 0 || message.id !== selected.id && !this.#annotations.has(message.id)) return this.#refuse(new LedgerError("REQUEST", "ledger selection requires a request owned by an active respond call"), { judgments: [] }, conversation);
@@ -12003,7 +14091,7 @@ var Ledger = class {
12003
14091
  const scanned = attempt(() => this.#scan(this.#conversation.messages()));
12004
14092
  if (scanned.success) this.#hold(scanned.value);
12005
14093
  try {
12006
- return await this.#classifier.classify(this.#requests, signal).then((classified) => this.#file(classified, request, selected, message), (error) => this.#refuse(error, { judgments: [] }, this.#conversation));
14094
+ return await this.#classifier.classify(this.#requests, signal).then((classified) => this.#file(classified, request, selected, message, filter ?? this.#live.get(request)), (error) => this.#refuse(error, { judgments: [] }, this.#conversation));
12007
14095
  } finally {
12008
14096
  const [scan] = this.#scans;
12009
14097
  if (scan !== void 0) this.#scans.delete(scan);
@@ -12025,11 +14113,11 @@ var Ledger = class {
12025
14113
  this.#hold(scan);
12026
14114
  return scan;
12027
14115
  }
12028
- #file(classified, request, selected, message) {
14116
+ #file(classified, request, selected, message, filter) {
12029
14117
  const { attempts, failure, ...filed } = classified;
12030
14118
  const planned = attempt(() => {
12031
14119
  if (filed.fault !== void 0) throw filed.fault;
12032
- const { draft, ...plan } = this.#plan(selected, this.#rescan());
14120
+ const { draft, ...plan } = this.#plan(selected, this.#rescan(), filter);
12033
14121
  return {
12034
14122
  draft,
12035
14123
  selection: {
@@ -12062,7 +14150,7 @@ var Ledger = class {
12062
14150
  const category = this.#classifier.category(source);
12063
14151
  return !(role === "user" && !this.#classifier.decisive(source)) && (category === "rule" || category === "correction");
12064
14152
  }
12065
- #plan(request, scan) {
14153
+ #plan(request, scan, filter) {
12066
14154
  const { input, projection } = this.#project(scan);
12067
14155
  const registry = scan.registry;
12068
14156
  const links = linkOwners(input.findings, registry.owners);
@@ -12074,10 +14162,20 @@ var Ledger = class {
12074
14162
  }),
12075
14163
  topics: [...near]
12076
14164
  });
12077
- const total = Math.max(0, (this.#options.capacity - this.#predict) * this.#share.prompt - (this.#gauge?.overhead ?? 0)) / (1 + LEDGER_SCALE_DRIFT);
14165
+ const lists = intersectFilters(void 0, filter);
14166
+ const bound = lists.tools;
14167
+ const disclosure = this.#show(lists);
14168
+ const advertised = this.#advertise(lists, disclosure.skills);
14169
+ const calibrated = this.#measuredSet();
14170
+ const price = computeToolPrice(calibrated, advertised, this.#priced());
14171
+ const total = Math.max(0, (this.#options.capacity - this.#predict) * this.#share.prompt - (this.#priced().overhead - price.spared + price.added) - this.#holdback(lists, disclosure, advertised, [])) / (1 + LEDGER_SCALE_DRIFT);
12078
14172
  const built = this.#agent.context.build({
12079
- messages: [request],
12080
- judgments: []
14173
+ selection: {
14174
+ messages: [request],
14175
+ judgments: []
14176
+ },
14177
+ disclosure,
14178
+ switchable: advertised.some((tool) => tool.name === "switch")
12081
14179
  });
12082
14180
  const tail = this.#selectTail(request, total, input, scan);
12083
14181
  const tailIds = new Set(tail.map((message) => message.id));
@@ -12186,23 +14284,24 @@ var Ledger = class {
12186
14284
  ...rung.window.flatMap((entry) => [entry.request, entry.reply]),
12187
14285
  buildRequestMessage(built.at(-1) ?? request, rung.rendered.text)
12188
14286
  ], this.#replay)]);
12189
- const names = this.#agent.context.scope?.tools;
12190
- const definitions = this.#agent.context.tools.definitions();
12191
14287
  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]));
12192
14288
  const longest = this.#findLongest();
12193
14289
  const closed = probes.map((messages, at) => {
12194
- const tools = definitions.filter((tool) => (names === void 0 || names.includes(tool.name)) && (tool.name !== "recall" || offered[at]));
14290
+ const tools = advertised.filter((tool) => tool.name !== "recall" || offered[at]);
12195
14291
  const calls = [{
12196
14292
  estimate: estimateMessages(messages),
12197
14293
  tools: tools.length,
12198
- spared: this.#spared(tools)
14294
+ ...computeToolPrice(calibrated, tools, this.#priced())
12199
14295
  }];
12200
14296
  return offered[at] === true && this.#gauge !== void 0 && this.#gauge.remainder(calls) - this.#predict < 2 * this.#gauge.reserve(calls, longest);
12201
14297
  });
12202
14298
  const prices = probes.map((messages) => this.#price(messages));
12203
14299
  const admitted = rungs.find((_rung, at) => (prices[at] ?? Infinity) <= total && (closed[2] === true || closed[at] === false)) ?? empty;
12204
- if (offered[rungs.indexOf(admitted)] === false && (names === void 0 || names.includes("recall"))) {
12205
- const tools = definitions.filter((tool) => tool.name !== "recall").map((tool) => tool.name);
14300
+ const recall = offered[rungs.indexOf(admitted)] === true;
14301
+ const ceiling = intersectKeys(this.#agent.context.scope?.tools, bound);
14302
+ const admits = ceiling === void 0 || ceiling.includes("recall");
14303
+ if (!recall && admits) {
14304
+ const tools = this.#definitions().filter((tool) => tool.name !== "recall").map((tool) => tool.name);
12206
14305
  const narrowed = this.#agent.context.scope?.narrow({ tools }) ?? createScope({
12207
14306
  name: "ledger",
12208
14307
  tools
@@ -12225,10 +14324,11 @@ var Ledger = class {
12225
14324
  window: admitted.window
12226
14325
  };
12227
14326
  const shown = buildLedgerRenderContext(scan.messages, [...kept, ...admitted.rendered.lines], []).complete;
14327
+ const route = recall && admits;
12228
14328
  return {
12229
14329
  draft,
12230
14330
  messages: [
12231
- ...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown) : message),
14331
+ ...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown, route) : message),
12232
14332
  ...admitted.window.flatMap((entry) => [entry.request, entry.reply]),
12233
14333
  copy
12234
14334
  ]
@@ -12316,7 +14416,7 @@ var Ledger = class {
12316
14416
  return [{
12317
14417
  leader,
12318
14418
  kept: (leader.calls ?? []).flatMap((call) => {
12319
- const message = group.slice(1).find((result) => findLedgerCall(group, result) === call);
14419
+ const message = group.slice(1).find((result) => findToolCall(group, result) === call);
12320
14420
  const result = message === void 0 ? void 0 : this.#results.get(message.id);
12321
14421
  return message === void 0 || ![...this.#options.lookups ?? [], ...this.#options.writes ?? []].some((entry) => entry.tool.name === call.name) || result?.success === false && result.error === LEDGER_NOTES.repeat ? [] : [{
12322
14422
  call,
@@ -12361,16 +14461,16 @@ var Ledger = class {
12361
14461
  amendments: evidence.amendments
12362
14462
  };
12363
14463
  }
12364
- #renderTailStub(message, scan, shown) {
14464
+ #renderTailStub(message, scan, shown, offered = true) {
12365
14465
  const group = scan.group(message.id);
12366
- const call = group === void 0 ? void 0 : findLedgerCall(group, message);
14466
+ const call = group === void 0 ? void 0 : findToolCall(group, message);
12367
14467
  const write = this.#options.writes?.some((one) => one.tool.name === call?.name) === true;
12368
14468
  const finding = scan.finding(message.id);
12369
14469
  const hidden = renderStub(call?.name ?? "tool", call?.arguments ?? {}, "hidden");
12370
14470
  const visible = renderStub(call?.name ?? "tool", call?.arguments ?? {}, "shown");
12371
14471
  return {
12372
14472
  ...message,
12373
- 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", write)
14473
+ 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", write, offered)
12374
14474
  };
12375
14475
  }
12376
14476
  #release(call, result) {
@@ -12401,26 +14501,71 @@ var Ledger = class {
12401
14501
  }]);
12402
14502
  for (const finding of findings) if ((edges.get(finding.id) ?? []).includes(id) && this.#options.lookups?.some((lookup) => lookup.tool.name === finding.name) === true) request.release(finding.name, finding.arguments);
12403
14503
  }
12404
- #observeTurn() {
14504
+ #observeRequest(sent) {
12405
14505
  this.#flush();
12406
14506
  const request = this.#current();
12407
- const boundary = request?.boundary;
12408
- if (request === void 0 || boundary === void 0 || request.aborted) return;
12409
- const additions = this.#conversation.messages().slice(boundary);
12410
- const messages = this.#agent.context.build(request.selection);
12411
- const names = this.#agent.context.scope?.tools;
12412
- const tools = this.#agent.context.tools.definitions().filter((tool) => names === void 0 || names.includes(tool.name));
14507
+ if (request?.boundary === void 0) return;
14508
+ const tools = sent.tools ?? [];
12413
14509
  request.observe({
12414
- estimate: estimateMessages(stripThinking([...messages, ...additions], this.#replay)),
14510
+ estimate: estimateMessages(sent.messages),
12415
14511
  tools: tools.length,
12416
- spared: this.#spared(tools)
14512
+ ...computeToolPrice(this.#measuredSet(), tools, this.#priced())
12417
14513
  }, this.#conversation.messages().length);
12418
14514
  }
12419
- #spared(tools) {
12420
- const overhead = this.#gauge?.overhead ?? 0;
12421
- if (tools.length === 0) return overhead;
12422
- const missing = this.#agent.context.tools.definitions().filter((tool) => !tools.some((one) => one.name === tool.name));
12423
- return missing.length === 0 ? 0 : Math.min(overhead, estimateTokens(JSON.stringify(missing)) * (this.#gauge?.scale ?? 1));
14515
+ #priced() {
14516
+ return this.#gauge ?? {
14517
+ scale: 1,
14518
+ overhead: 0
14519
+ };
14520
+ }
14521
+ #measuredSet() {
14522
+ return this.#measured ?? this.#calibrated();
14523
+ }
14524
+ #digest() {
14525
+ return this.#measured === void 0 ? void 0 : digestLedgerTools(this.#measured);
14526
+ }
14527
+ #enables(name) {
14528
+ return this.#builtins.some((one) => one === name);
14529
+ }
14530
+ #definitions() {
14531
+ return [...this.#agent.context.tools.definitions().filter((tool) => !this.#enables(tool.name)), ...collectBuiltinDefinitions({ builtins: this.#builtins })];
14532
+ }
14533
+ #calibrated() {
14534
+ const definitions = this.#definitions();
14535
+ if (!this.#enables("load")) return definitions;
14536
+ const held = new Set(this.#agent.context.skills.skills().flatMap((skill) => skill.tools));
14537
+ return definitions.filter((tool) => !held.has(tool.name));
14538
+ }
14539
+ #show(ceiling) {
14540
+ return resolveDisclosure(this.#agent.context, this.#conversation, this.#given, ceiling, this.#builtins);
14541
+ }
14542
+ #advertise(ceiling, shown) {
14543
+ return filterAdvertisedTools(filterBuiltinCandidates(this.#definitions(), ceiling), ceiling.tools, this.#enables("load") ? this.#agent.context.skills.skills() : [], shown);
14544
+ }
14545
+ #ceiling(request) {
14546
+ return intersectFilters(this.#agent.context.scope, this.#live.get(request));
14547
+ }
14548
+ #priceSkill(skill, ceiling, advertised) {
14549
+ const sent = new Set(advertised.map((tool) => tool.name));
14550
+ return computeSkillPrice(skill, filterAllowList(ceiling.tools, this.#definitions().filter((tool) => skill.tools.includes(tool.name) && !sent.has(tool.name)), (tool) => tool.name), this.#measuredSet(), this.#priced());
14551
+ }
14552
+ #priceShown(shown, ceiling) {
14553
+ const skills = shown.skills.flatMap((name) => this.#agent.context.skills.skill(name) ?? []);
14554
+ const named = new Set(skills.flatMap((skill) => skill.tools));
14555
+ const definitions = filterAllowList(ceiling.tools, this.#definitions().filter((tool) => named.has(tool.name)), (tool) => tool.name);
14556
+ const calibrated = this.#measuredSet();
14557
+ const gauge = this.#priced();
14558
+ return skills.reduce((sum, skill) => sum + computeSkillPrice(skill, [], calibrated, gauge), 0) + computeToolPrice(calibrated, [...calibrated, ...definitions], gauge).added;
14559
+ }
14560
+ #priceResult(calls) {
14561
+ const longest = this.#conversation.messages().filter((message) => message.role === "tool").reduce((found, message) => found === void 0 || message.content.length > found.content.length ? message : found, void 0);
14562
+ const gauge = this.#gauge;
14563
+ return Math.max(this.#priced().scale * (longest === void 0 ? 0 : estimateMessages([longest])), gauge === void 0 ? 0 : gauge.reserve(calls, this.#findLongest()));
14564
+ }
14565
+ #holdback(ceiling, shown, advertised, calls) {
14566
+ if (!advertised.some((tool) => tool.name === "load")) return 0;
14567
+ const loadable = filterAllowList(ceiling.skills, this.#agent.context.skills.skills(), (skill) => skill.name).filter((skill) => !shown.skills.includes(skill.name));
14568
+ return loadable.length === 0 ? 0 : Math.max(...loadable.map((skill) => this.#priceSkill(skill, ceiling, advertised))) + this.#priceResult(calls);
12424
14569
  }
12425
14570
  #findLongest() {
12426
14571
  return this.#conversation.messages().filter((message) => message.role === "assistant").reduce((longest, message) => message.content.length > longest.length ? message.content : longest, "");
@@ -12448,12 +14593,7 @@ var Ledger = class {
12448
14593
  request?.recall();
12449
14594
  const guidance = `an owner name, an id, or one of ${renderTopicNames(this.#options.topics)}`;
12450
14595
  if (topic === "") throw new Error(`recall needs a topic: ${guidance}`);
12451
- const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
12452
- const call = calls.at(-1);
12453
- const carried = request === void 0 ? 0 : estimateMessages(stripThinking([...this.#agent.context.build(request.selection), ...this.#conversation.messages().slice(request.boundary)], this.#replay));
12454
- const completion = isFiniteNumber(call?.prompt) ? (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : 0;
12455
- const extra = Math.max(0, (carried - (call?.estimate ?? carried)) * rate - completion);
12456
- const drift = isFiniteNumber(call?.prompt) ? 0 : Math.max(0, (call?.estimate ?? 0) * (rate - (gauge?.scale ?? rate)));
14596
+ const { rate, extra, slack: drift } = this.#carry(request, calls);
12457
14597
  const siblings = estimateMessages((request?.listings ?? []).filter((listing) => listing.turn === calls.length).map((listing) => ({
12458
14598
  id: "result",
12459
14599
  role: "tool",
@@ -12528,7 +14668,7 @@ var Ledger = class {
12528
14668
  this.#flush();
12529
14669
  const groups = [...new Set(this.#collectAfter(entered).flatMap((message) => {
12530
14670
  const result = this.#results.get(message.id);
12531
- if (message.role !== "tool" || result?.success !== true) return [];
14671
+ if (message.role !== "tool" || result?.success !== true || result.name !== "memo" && this.#enables(result.name)) return [];
12532
14672
  const recalled = result.name === "recall";
12533
14673
  if (recalled && /^nothing earlier on /.test(message.content)) return [];
12534
14674
  const listing = recalled ? request.listings.findLast((one) => one.result === message.content) : void 0;
@@ -12537,7 +14677,7 @@ var Ledger = class {
12537
14677
  const unique = groups.filter((group, at) => !splitResultLines(group).every((line) => groups.slice(0, at).some((earlier) => splitResultLines(earlier).includes(line))));
12538
14678
  const digest = unique.length === 0 ? void 0 : `${LEDGER_NOTES.results}\n${unique.join("\n")}`;
12539
14679
  const omitted = /* @__PURE__ */ new Set();
12540
- const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
14680
+ const rate = this.#capacityRate(request) * (1 + LEDGER_SCALE_DRIFT);
12541
14681
  const allowance = (this.#options.capacity - this.#predict) / rate;
12542
14682
  const after = this.#collectAfter(entered);
12543
14683
  const messages = [
@@ -12554,28 +14694,105 @@ var Ledger = class {
12554
14694
  role: "user",
12555
14695
  content: digest
12556
14696
  }];
14697
+ const shown = this.#show(this.#ceiling(request));
12557
14698
  if (this.#gauge === void 0 || estimateMessages(stripThinking(this.#agent.context.build({
12558
- messages: [...messages, ...note],
12559
- judgments: []
14699
+ selection: {
14700
+ messages: [...messages, ...note],
14701
+ judgments: []
14702
+ },
14703
+ disclosure: shown
12560
14704
  }), this.#replay)) <= allowance) return {
12561
14705
  digest,
12562
14706
  omitted
12563
14707
  };
12564
14708
  const partials = new Set(after.filter((message) => message.role === "assistant" && !hasCalls(message)).map((message) => message.id));
12565
- const fitted = cutListing(unique, allowance - estimateMessages(stripThinking(this.#agent.context.build({
12566
- messages: messages.filter((message) => !partials.has(message.id)),
12567
- judgments: []
12568
- }), this.#replay)) - estimateTokens(`${LEDGER_NOTES.results}\n`), "more", false);
14709
+ const base = estimateMessages(stripThinking(this.#agent.context.build({
14710
+ selection: {
14711
+ messages: messages.filter((message) => !partials.has(message.id)),
14712
+ judgments: []
14713
+ },
14714
+ disclosure: shown
14715
+ }), this.#replay));
14716
+ if (base > allowance) throw new LedgerError("OVERFLOW", `the answer pass prices ${Math.ceil(base * rate)} tokens with every cut made, over the capacity ${this.#options.capacity} less predict ${this.#predict}; shorten the request, narrow what the scope shows, or raise capacity`, { context: {
14717
+ capacity: this.#options.capacity,
14718
+ predict: this.#predict,
14719
+ price: base * rate
14720
+ } });
14721
+ const fitted = cutListing(unique, allowance - base - estimateTokens(`${LEDGER_NOTES.results}\n`), "more", false);
14722
+ const cut = fitted === "" ? void 0 : `${LEDGER_NOTES.results}\n${fitted}`;
12569
14723
  return {
12570
- digest: fitted === "" ? void 0 : `${LEDGER_NOTES.results}\n${fitted}`,
14724
+ digest: cut !== void 0 && base + estimateMessages([{
14725
+ id: "results",
14726
+ role: "user",
14727
+ content: cut
14728
+ }]) > allowance ? void 0 : cut,
12571
14729
  omitted: partials
12572
14730
  };
12573
14731
  }
14732
+ #carry(request, calls) {
14733
+ const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
14734
+ const ceiling = request === void 0 ? {} : this.#ceiling(request);
14735
+ const shown = this.#show(ceiling);
14736
+ const carried = request === void 0 ? 0 : estimateMessages(stripThinking([...this.#agent.context.build({
14737
+ ...request.selection === void 0 ? {} : { selection: request.selection },
14738
+ disclosure: shown,
14739
+ switchable: this.#advertise(ceiling, shown.skills).some((tool) => tool.name === "switch")
14740
+ }), ...this.#conversation.messages().slice(request.boundary)], this.#replay));
14741
+ return {
14742
+ rate,
14743
+ ...this.#terms(calls.at(-1), carried, rate)
14744
+ };
14745
+ }
14746
+ #terms(call, carried, rate) {
14747
+ const reply = isFiniteNumber(call?.prompt) ? (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : 0;
14748
+ return {
14749
+ extra: Math.max(0, (carried - (call?.estimate ?? carried)) * rate - reply),
14750
+ slack: isFiniteNumber(call?.prompt) ? 0 : Math.max(0, (call?.estimate ?? 0) * (rate - (this.#gauge?.scale ?? rate)))
14751
+ };
14752
+ }
14753
+ #gate(index, sent) {
14754
+ const request = this.#current();
14755
+ if (request?.boundary === void 0) return true;
14756
+ const left = attempt(() => index === 0 ? this.#first(request, sent) : this.#next(request, sent));
14757
+ if (left.success && left.value >= 0) return true;
14758
+ const limit = this.#options.capacity - this.#predict;
14759
+ this.#refusal = new LedgerError("OVERFLOW", left.success ? `the request prices ${Math.ceil(limit - left.value)} tokens, over the capacity ${this.#options.capacity} less predict ${this.#predict}; shorten the request, narrow what the scope shows, or raise capacity` : `the request could not be priced against the capacity ${this.#options.capacity} less predict ${this.#predict}`, { context: {
14760
+ capacity: this.#options.capacity,
14761
+ predict: this.#predict,
14762
+ price: left.success ? limit - left.value : Number.POSITIVE_INFINITY
14763
+ } });
14764
+ this.#agent.abort("overflow");
14765
+ return false;
14766
+ }
14767
+ #first(request, sent) {
14768
+ const gauge = this.#gauge;
14769
+ if (gauge === void 0) throw new LedgerError("GAUGE", "the gate needs a gauge");
14770
+ const tools = computeToolPrice(this.#measuredSet(), sent.tools ?? [], gauge);
14771
+ const rate = request.calls.length === 0 ? gauge.scale : this.#capacityRate(request);
14772
+ return this.#options.capacity - this.#predict - (Math.max(0, gauge.overhead - tools.spared) + tools.added + rate * (1 + LEDGER_SCALE_DRIFT) * estimateMessages(sent.messages));
14773
+ }
14774
+ #next(request, sent) {
14775
+ request.think(this.#conversation.messages());
14776
+ const gauge = this.#gauge;
14777
+ if (gauge === void 0) throw new LedgerError("GAUGE", "the gate needs a gauge");
14778
+ const calls = request.calls;
14779
+ const last = calls.at(-1);
14780
+ if (last === void 0) throw new Error("a later request has no recorded call before it");
14781
+ const measured = this.#measuredSet();
14782
+ const rate = this.#capacityRate(request) * (1 + LEDGER_SCALE_DRIFT);
14783
+ const { extra, slack } = this.#terms(last, estimateMessages(sent.messages), rate);
14784
+ const tools = computeToolPrice(measured, sent.tools ?? [], gauge);
14785
+ const grown = (Math.min(gauge.overhead, Math.max(0, (last.spared ?? 0) - tools.spared) * computeToolDensity(measured, gauge) / gauge.scale) + Math.max(0, tools.added - (last.added ?? 0))) * (1 + LEDGER_SCALE_DRIFT);
14786
+ return gauge.remainder(calls) - this.#predict - extra - slack - grown;
14787
+ }
14788
+ #capacityRate(request) {
14789
+ return Math.max(this.#recallRate(request), this.#gauge?.scale ?? 1);
14790
+ }
12574
14791
  #recallRate(request) {
12575
14792
  const gauge = this.#gauge;
12576
14793
  if (gauge === void 0) return 1;
12577
14794
  const calls = request?.calls ?? [];
12578
- const last = calls.findLast((call) => isFiniteNumber(call.prompt));
14795
+ const last = calls.findLast((call) => isFiniteNumber(call.prompt) && (call.added ?? 0) === 0);
12579
14796
  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;
12580
14797
  return Math.max(gauge.rate(calls), measured);
12581
14798
  }
@@ -12596,6 +14813,6 @@ var Ledger = class {
12596
14813
  function createLedger(provider, options) {
12597
14814
  return new Ledger(provider, options);
12598
14815
  }
12599
- 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, collectRetiredFindings, collectToolGroups, collectWriteSupersessions, computeLedgerChecksum, 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, digestLedgerTools, 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, mergeSupersessions, 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 };
14816
+ export { AGENT_OPTION_KEYS, AGENT_RUN_KEYS, ARGUMENT_BLANK_PATTERN, ARGUMENT_CONTROL_PATTERN, AUTHORITY_NOTES, AUTHORITY_OPTION_KEYS, Agent, AgentContext, AgentError, AgentFanout, AgentJobError, AgentJudge, AgentProvider, AgentRegistry, Approver, Authority, BUILD_OPTION_KEYS, BUILTIN_LIMITS, BUILTIN_NOTES, BUILTIN_TOOLS, CLASSIFIER_OPTION_KEYS, COMPACT_OPTION_KEYS, CONTEXT_OPTION_KEYS, CONVERSATION_INPUT_KEYS, CONVERSATION_MANAGER_KEYS, CONVERSATION_OPTION_KEYS, 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_OPTION_KEYS, Gauge, IMAGE_TOKEN_ESTIMATE, INSTRUCTION_MANAGER_KEYS, INVALID_RELAY_STATUS, Instruction, InstructionManager, JOB_REFUSAL_PREFIX, JUDGE_INPUT_KEYS, JudgeAbortError, JudgeError, JudgmentManager, LEDGER_AGENT_KEYS, LEDGER_CATEGORIES, LEDGER_NOTES, LEDGER_OPTION_KEYS, LEDGER_OWNER_PREFIX, LEDGER_QUESTIONS, LEDGER_RECALL_LABELS, LEDGER_RULES_KEY, LEDGER_SAMPLE_BYTES, LEDGER_SCALE_DRIFT, LEDGER_SLICE, LEDGER_WORD, LOADED_SECTION_HEADER, Ledger, LedgerError, LedgerRequest, MAX_ERROR_BODY_LENGTH, MEMO_MANAGER_KEYS, MEMO_SECTION_HEADER, MESSAGE_ROLES, MESSAGE_TOKEN_OVERHEAD, MemoManager, MemoryConversationStore, MemoryMemoStore, OVERSIZED_RELAY_STATUS, PLACED_CATEGORIES, PROVIDER_INPUT_KEYS, ProviderAbortError, ProviderError, QUEUE_OPTION_KEYS, QUIET_CATEGORIES, REFERENCE_OPTION_KEYS, REFUSED_RELAY_CONTENT_TYPE, REFUSED_RELAY_STATUS, REGISTRY_OPTION_KEYS, RELAY_CONTENT_TYPE, RELAY_OPTION_KEYS, RELAY_PROVIDER_KEYS, RELAY_PROVIDER_MESSAGE, RELAY_STREAM_KEYS, RUNNER_OPTION_KEYS, RelayProvider, RelayStream, SCOPE_FILTER_KEYS, SCOPE_KEYS, SCOPE_MANAGER_KEYS, SCOPE_NOTE, SKILL_MANAGER_KEYS, SKILL_SECTION_HEADER, STREAM_OPTION_KEYS, SYSTEM_ONE_OPTION_KEYS, SYSTEM_ONE_PATH, Scope, ScopeManager, SkillManager, SystemOneJudge, THINK_CLOSE, THINK_OPEN, ThinkSplitter, UNAUTHORIZED_RELAY_STATUS, UPSTREAM_RELAY_STATUS, WORKSPACE_INDEX_HEADER, 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, builtinToolContracts, chargeUsage, collectAmendments, collectBuiltinDefinitions, collectDisclosure, collectExchanges, collectImageData, collectJobRefusals, collectLedgerEvidence, collectLedgerLines, collectLedgerReplies, collectLive, collectNames, collectProjectionIds, collectRecallMessages, collectRegistry, collectRetiredFindings, collectToolGroups, collectWriteSupersessions, computeLedgerChecksum, computeSkillPrice, computeThinking, computeToolDensity, computeToolPrice, conversationSnapshotContract, conversationSnapshotShape, copyJSON, createAgent, createAgentContext, createAgentQueue, createAgentRegistry, createAgentRunner, createApprover, createAuthority, createBuiltinTool, createChannel, createConversation, createConversationManager, createDatabaseConversationStore, createDatabaseMemoStore, createInstruction, createInstructionManager, createLedger, createMemoManager, createMemoryConversationStore, createMemoryMemoStore, createRelay, createRelayProvider, createScope, createScopeManager, createSkillManager, createSystemOneJudge, createThinkSplitter, cutListing, cutWorkspaceSample, denyCall, describeCandidates, describeQuestionRefusal, describeRelayRefusal, describeSnapshotRefusal, digestLedgerTools, digestLedgerWindow, estimateMessages, estimateRequest, estimateTokens, extractLiterals, extractQueueOptions, extractSystemOneAnswer, extractSystemOneUsage, extractWords, filterAdvertisedTools, filterAllowList, filterBuiltinCandidates, findLedgerCut, findToolCall, fitSlope, freezeJSON, handleAgentQueueJob, hasCalls, hasHiddenRecall, identifyLookup, inferStop, instructionInputContract, instructionInputShape, intersectFilters, intersectKeys, isAgentBuiltin, isAgentCaller, 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, isScope, isSection, isSystemOneAnswer, isSystemOneResponse, joinThinking, judgeAnswerContract, judgeAnswerShape, judgeEntryShape, judgeQuestionContract, judgeQuestionShape, judgmentContract, judgmentShape, ledgerSnapshotContract, ledgerSnapshotShape, ledgerWindowContract, ledgerWindowShape, linkOwners, loadToolContract, loadToolShape, matchEntities, matchesCutLine, matchesHeadLists, matchesJSON, matchesJudgment, matchesLookup, matchesPlainLine, measureAnswer, measureMessage, measureRequest, memoContract, memoInputContract, memoInputShape, memoShape, memoSnapshotContract, memoSnapshotShape, memoToolContract, memoToolShape, mergeSupersessions, messageContract, messageInputContract, messageInputShape, messageShape, normalizeArgument, normalizeWord, placeMember, providerRequestContract, providerRequestShape, providerResultContract, providerResultShape, questionToSystemOne, raceSignal, rankLedgerCut, readBodyText, readChunks, readFailure, readJudgeOutcome, readProviderDelta, relayFrameContract, relayFrameShape, releaseReader, rememberToolContract, rememberToolShape, removeEntries, renderArguments, renderCauseChain, renderFencedFile, renderIndexEntry, renderIndexLine, renderJSON, renderLedgerAnnotation, renderLedgerPinned, renderLedgerRecord, renderLookupCall, renderSection, renderSkillBody, renderStub, renderToolValue, renderTopicNames, requestToRelay, requireAnswer, requireBudget, requireBuiltins, requireConversation, requireConversationSnapshot, requireCumulative, requireEntry, requireFallback, requireJudgeResult, requireJudgment, requireKeep, requireLimit, requireMessageInput, requireProviderResult, requireRecordInput, requireRole, requireRules, requireScope, requireSectionsCap, requireSummary, requireWorkspace, resolveDecision, resolveDisclosure, resolvePredict, resolveWorkspace, sanitizeToken, sanitizeUsage, scanAmendments, scanJobTree, scanJudgmentTies, scanProbability, sectionContract, sectionShape, selectRecords, settleAgentJob, settleToolResult, skillInputContract, skillInputShape, splitResultLines, splitSentences, splitTopic, splitWords, stripThinking, sumUsage, switchToolContract, switchToolShape, tokenUsageShape, tokenUsageToJSON, toolCallShape };
12600
14817
 
12601
14818
  //# sourceMappingURL=index.js.map