@orkestrel/agent 0.0.41 → 0.0.42

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.
@@ -3,13 +3,13 @@ let _orkestrel_contract = require("@orkestrel/contract");
3
3
  let _orkestrel_budget = require("@orkestrel/budget");
4
4
  let _orkestrel_tool = require("@orkestrel/tool");
5
5
  let _orkestrel_ndjson = require("@orkestrel/ndjson");
6
+ let _orkestrel_abort = require("@orkestrel/abort");
6
7
  let _orkestrel_timeout = require("@orkestrel/timeout");
7
8
  let _orkestrel_database = require("@orkestrel/database");
8
9
  let _orkestrel_emitter = require("@orkestrel/emitter");
9
10
  let _orkestrel_workspace = require("@orkestrel/workspace");
10
11
  let _orkestrel_queue = require("@orkestrel/queue");
11
12
  let _orkestrel_workflow = require("@orkestrel/workflow");
12
- let _orkestrel_abort = require("@orkestrel/abort");
13
13
  // src/core/constants.ts
14
14
  /**
15
15
  * Lists the roles any message can play, the built `system` head included, in the order the wire
@@ -726,6 +726,23 @@ function collectToolGroups(messages) {
726
726
  return [...groups, ...orphans];
727
727
  }
728
728
  /**
729
+ * Finds the call that owns a tool message within a collected tool group.
730
+ * @param group - The assistant leader followed by its tool results, as {@link collectToolGroups} returns it
731
+ * @param message - The tool result to pair; distinct leader ids pair by result id when any result has one
732
+ * @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
733
+ * @example
734
+ * ```ts
735
+ * findToolCall(group, result)?.arguments
736
+ * ```
737
+ */
738
+ function findToolCall(group, message) {
739
+ const [leader, ...results] = group;
740
+ const at = results.findIndex((result) => result.id === message.id);
741
+ if (message.role !== "tool" || at < 0) return void 0;
742
+ const calls = leader?.calls ?? [];
743
+ 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];
744
+ }
745
+ /**
729
746
  * Renders a value as the JSON text `JSON.stringify` produces, or `undefined` when JSON cannot
730
747
  * carry the value.
731
748
  *
@@ -1023,6 +1040,60 @@ var REFUSED_RELAY_STATUS = 422;
1023
1040
  var REFUSED_RELAY_CONTENT_TYPE = "text/plain; charset=utf-8";
1024
1041
  /** Names the System One decision endpoint shared by compatible servers. */
1025
1042
  var SYSTEM_ONE_PATH = "/v1/systemone";
1043
+ /** Lists the members `RelayOptions` declares, frozen — the keys `createRelay` admits. */
1044
+ var RELAY_OPTION_KEYS = Object.freeze([
1045
+ "provider",
1046
+ "authorize",
1047
+ "limit",
1048
+ "system",
1049
+ "tools"
1050
+ ]);
1051
+ /** Lists the members `RelayProviderOptions` declares, frozen — the keys the `RelayProvider` constructor admits. */
1052
+ var RELAY_PROVIDER_KEYS = Object.freeze([
1053
+ "url",
1054
+ "parser",
1055
+ "timeout",
1056
+ "replay",
1057
+ "fetch",
1058
+ "headers"
1059
+ ]);
1060
+ /** Lists the members `RelayStreamOptions` declares, frozen — the keys the `RelayStream` constructor admits. */
1061
+ var RELAY_STREAM_KEYS = Object.freeze([
1062
+ "provider",
1063
+ "request",
1064
+ "signal"
1065
+ ]);
1066
+ /** Lists the members `AgentProviderInput` declares, frozen — the keys the `AgentProvider` constructor admits. */
1067
+ var PROVIDER_INPUT_KEYS = Object.freeze([
1068
+ "url",
1069
+ "path",
1070
+ "split",
1071
+ "strict",
1072
+ "timeout",
1073
+ "replay",
1074
+ "fetch",
1075
+ "headers"
1076
+ ]);
1077
+ /** Lists the members `AgentJudgeInput` declares, frozen — the keys the `AgentJudge` constructor admits. */
1078
+ var JUDGE_INPUT_KEYS = Object.freeze([
1079
+ "url",
1080
+ "path",
1081
+ "model",
1082
+ "batch",
1083
+ "timeout",
1084
+ "fetch",
1085
+ "headers"
1086
+ ]);
1087
+ /** Lists the members `SystemOneJudgeOptions` declares, frozen — the keys the `SystemOneJudge` constructor admits. */
1088
+ var SYSTEM_ONE_OPTION_KEYS = Object.freeze([
1089
+ "url",
1090
+ "model",
1091
+ "timeout",
1092
+ "fetch",
1093
+ "headers"
1094
+ ]);
1095
+ /** Lists the members `ProviderStreamOptions` declares, frozen — the keys the `generate` and `stream` methods of an `AgentProvider` admits. */
1096
+ var STREAM_OPTION_KEYS = Object.freeze(["think", "schema"]);
1026
1097
  // src/core/providers/errors.ts
1027
1098
  /**
1028
1099
  * Reports a provider stream cancelled mid-flight by its bound signal — thrown by the
@@ -2039,6 +2110,7 @@ var RelayStream = class {
2039
2110
  #response;
2040
2111
  #settled = false;
2041
2112
  constructor(options) {
2113
+ (0, _orkestrel_contract.refuseOptionKeys)(options, RELAY_STREAM_KEYS, "RelayStream", "RelayStreamOptions");
2042
2114
  this.#replay = options.provider.replay ?? "none";
2043
2115
  this.#signal = options.signal;
2044
2116
  this.#listener = this.#abort.bind(this);
@@ -2305,6 +2377,7 @@ var AgentProvider = class {
2305
2377
  #split;
2306
2378
  #strict;
2307
2379
  constructor(input) {
2380
+ (0, _orkestrel_contract.refuseOptionKeys)(input, PROVIDER_INPUT_KEYS, "AgentProvider", "AgentProviderInput");
2308
2381
  this.#id = crypto.randomUUID();
2309
2382
  this.#replay = input.replay ?? "none";
2310
2383
  this.#url = input.url;
@@ -2352,10 +2425,11 @@ var AgentProvider = class {
2352
2425
  * is malformed or carries an upstream failure, or a strict stream ends without a settled result
2353
2426
  */
2354
2427
  async *stream(messages, signal, tools, options) {
2428
+ (0, _orkestrel_contract.refuseOptionKeys)(options, STREAM_OPTION_KEYS, "AgentProvider", "ProviderStreamOptions");
2355
2429
  if (signal.aborted) throw new ProviderAbortError({ content: "" });
2356
2430
  const timeout = new _orkestrel_timeout.Timeout({ ms: this.#timeout });
2357
2431
  timeout.start();
2358
- const combined = AbortSignal.any([timeout.signal, signal]);
2432
+ const combined = (0, _orkestrel_abort.linkSignal)(timeout.signal, signal);
2359
2433
  try {
2360
2434
  const parser = this.#frame(combined);
2361
2435
  try {
@@ -2507,6 +2581,7 @@ var AgentProvider = class {
2507
2581
  var RelayProvider = class extends AgentProvider {
2508
2582
  #parser;
2509
2583
  constructor(options) {
2584
+ (0, _orkestrel_contract.refuseOptionKeys)(options, RELAY_PROVIDER_KEYS, "RelayProvider", "RelayProviderOptions");
2510
2585
  const { url, timeout, fetch, headers, replay } = options;
2511
2586
  super({
2512
2587
  url,
@@ -2639,6 +2714,7 @@ var AgentJudge = class {
2639
2714
  #headers;
2640
2715
  #batch;
2641
2716
  constructor(input) {
2717
+ (0, _orkestrel_contract.refuseOptionKeys)(input, JUDGE_INPUT_KEYS, "AgentJudge", "AgentJudgeInput");
2642
2718
  this.#id = crypto.randomUUID();
2643
2719
  this.#url = input.url;
2644
2720
  this.#path = input.path ?? "";
@@ -2703,7 +2779,7 @@ var AgentJudge = class {
2703
2779
  async #call(request, body, signal, completed) {
2704
2780
  const timeout = new _orkestrel_timeout.Timeout({ ms: this.#timeout });
2705
2781
  timeout.start();
2706
- const combined = AbortSignal.any([timeout.signal, signal]);
2782
+ const combined = (0, _orkestrel_abort.linkSignal)(timeout.signal, signal);
2707
2783
  try {
2708
2784
  const headers = await buildProviderHeaders(this.#headers, combined);
2709
2785
  combined.throwIfAborted();
@@ -2804,6 +2880,7 @@ var AgentJudge = class {
2804
2880
  */
2805
2881
  var SystemOneJudge = class extends AgentJudge {
2806
2882
  constructor(options) {
2883
+ (0, _orkestrel_contract.refuseOptionKeys)(options, SYSTEM_ONE_OPTION_KEYS, "SystemOneJudge", "SystemOneJudgeOptions");
2807
2884
  super({
2808
2885
  ...options,
2809
2886
  path: SYSTEM_ONE_PATH,
@@ -2903,6 +2980,7 @@ var SystemOneJudge = class extends AgentJudge {
2903
2980
  * ```
2904
2981
  */
2905
2982
  function createRelay(options) {
2983
+ (0, _orkestrel_contract.refuseOptionKeys)(options, RELAY_OPTION_KEYS, "createRelay", "RelayOptions");
2906
2984
  const { provider, authorize, limit } = options;
2907
2985
  const system = { ...options.system };
2908
2986
  const { definitions, accept } = options.tools ?? {};
@@ -3057,6 +3135,43 @@ var DEFAULT_CONVERSATION_KEEP = 0;
3057
3135
  * because it interpolates the per-call provenance `label`).
3058
3136
  */
3059
3137
  var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
3138
+ /** Lists the members `ConversationOptions` declares, frozen — the keys the `Conversation` constructor admits. */
3139
+ var CONVERSATION_OPTION_KEYS = Object.freeze([
3140
+ "id",
3141
+ "on",
3142
+ "error",
3143
+ "summarize",
3144
+ "keep",
3145
+ "sections",
3146
+ "cumulative",
3147
+ "snapshot"
3148
+ ]);
3149
+ /** Lists the members `ConversationManagerOptions` declares, frozen — the keys the `ConversationManager` constructor admits. */
3150
+ var CONVERSATION_MANAGER_KEYS = Object.freeze([
3151
+ "summarize",
3152
+ "keep",
3153
+ "sections",
3154
+ "store"
3155
+ ]);
3156
+ /** Lists the members `ConversationInput` declares, frozen — the keys the `add` method of a `ConversationManager` admits. */
3157
+ var CONVERSATION_INPUT_KEYS = Object.freeze([
3158
+ "id",
3159
+ "summarize",
3160
+ "keep",
3161
+ "sections",
3162
+ "cumulative",
3163
+ "on",
3164
+ "error",
3165
+ "snapshot"
3166
+ ]);
3167
+ /** Lists the members `CompactOptions` declares, frozen — the keys the `compact` method of a `Conversation` admits. */
3168
+ var COMPACT_OPTION_KEYS = Object.freeze([
3169
+ "keep",
3170
+ "sections",
3171
+ "signal"
3172
+ ]);
3173
+ /** Lists the members `ConversationReferenceOptions` declares, frozen — the keys the `reference` method of a `Conversation` admits. */
3174
+ var REFERENCE_OPTION_KEYS = Object.freeze(["label", "messages"]);
3060
3175
  // src/core/conversations/errors.ts
3061
3176
  /**
3062
3177
  * Reports a conversation with no {@link ConversationSummaryHandler} to fold its messages with, a
@@ -3082,7 +3197,8 @@ var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
3082
3197
  * `'SUMMARY'`. A stored message plays a `ConversationRole`: `add` refuses any other role with
3083
3198
  * `'ROLE'` before it stores any message of the batch, a hydrating snapshot that holds one throws
3084
3199
  * `'ROLE'`, and an agent's selection that holds one becomes a `'ROLE'` fault. `add` refuses an input
3085
- * whose other members fail `isMessageInput` with `'MESSAGE'`, its `context` holding the `faults`,
3200
+ * that is not a plain record, before its role is read, or whose other members fail `isMessageInput`
3201
+ * with `'MESSAGE'`, its `context` holding the `faults`,
3086
3202
  * before it stores any message of the batch. A hydrating snapshot that fails
3087
3203
  * `isConversationSnapshot` or repeats an id, and a store row or written snapshot that does, throw
3088
3204
  * `'SNAPSHOT'`; a judgment the guard admits but JSON cannot carry still throws `'JUDGMENT'`. A
@@ -3090,7 +3206,7 @@ var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
3090
3206
  * {@link isConversationError} and branch on `error.code`.
3091
3207
  */
3092
3208
  var ConversationError = class extends Error {
3093
- /** 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. */
3209
+ /** 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. */
3094
3210
  code;
3095
3211
  /** Holds the values that locate the failure, such as the `question` id of a malformed question, or `undefined` when none were supplied. */
3096
3212
  context;
@@ -3225,13 +3341,17 @@ function isMessage(value) {
3225
3341
  return checked.success && checked.value;
3226
3342
  }
3227
3343
  /**
3228
- * Checks whether a value is a message input a conversation can store: a string `content`, and `calls`,
3229
- * `call`, `images`, and `thinking` absent or of their declared types.
3344
+ * Checks whether a value is a message input a conversation can store: a `ConversationRole`, a string
3345
+ * `content`, and `calls`, `call`, `images`, and `thinking` absent or of their declared types.
3230
3346
  *
3231
3347
  * @remarks
3232
- * A hand guard, because `add` checks every message: it reads the members once and composes no record
3233
- * guard. The role is `requireRole`'s to refuse, so its text can name the role received. Unknown members
3234
- * are left behind by the store. An unreadable member returns false.
3348
+ * An exact record, because a stored message is one: a value that is not a plain record, such as a
3349
+ * class instance or an object that inherits its `role`, returns false through `recordOf`'s own plain
3350
+ * record check, as does a member `MessageInput` does not declare or a declared optional member holding
3351
+ * `undefined`. Each tool call crosses `isToolCall` of
3352
+ * `@orkestrel/tool`, whose interface admits more members, so the store keeps `id`, `name`, and
3353
+ * `arguments` and leaves the rest. `add` reads the role through `requireRole` first, so its text can
3354
+ * name the role received. An unreadable member returns false.
3235
3355
  *
3236
3356
  * @param value - The unknown input candidate
3237
3357
  * @returns True if the input's members are valid; false otherwise
@@ -3241,14 +3361,19 @@ function isMessage(value) {
3241
3361
  * isMessageInput({ role: 'user' }) // false
3242
3362
  * ```
3243
3363
  */
3244
- function isMessageInput(value) {
3245
- const checked = (0, _orkestrel_contract.attempt)(() => {
3246
- if (!(0, _orkestrel_contract.isRecord)(value)) return false;
3247
- const { content, calls, call, images, thinking } = value;
3248
- return (0, _orkestrel_contract.isString)(content) && (calls === void 0 || (0, _orkestrel_contract.arrayOf)(_orkestrel_tool.isToolCall)(calls)) && (call === void 0 || (0, _orkestrel_contract.isString)(call)) && (images === void 0 || (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(images)) && (thinking === void 0 || (0, _orkestrel_contract.isString)(thinking));
3249
- });
3250
- return checked.success && checked.value;
3251
- }
3364
+ var isMessageInput = (0, _orkestrel_contract.recordOf)({
3365
+ role: (0, _orkestrel_contract.literalOf)(CONVERSATION_ROLES),
3366
+ content: _orkestrel_contract.isString,
3367
+ calls: (0, _orkestrel_contract.arrayOf)(_orkestrel_tool.isToolCall),
3368
+ call: _orkestrel_contract.isString,
3369
+ images: (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString),
3370
+ thinking: _orkestrel_contract.isString
3371
+ }, [
3372
+ "calls",
3373
+ "call",
3374
+ "images",
3375
+ "thinking"
3376
+ ]);
3252
3377
  /**
3253
3378
  * Checks whether a stored judgment carries a valid question and exactly one answer or refusal.
3254
3379
  *
@@ -3560,14 +3685,17 @@ function requireRole(role) {
3560
3685
  * Returns a message input after checking its role and its members, or throws a coded error.
3561
3686
  *
3562
3687
  * @remarks
3563
- * The role goes first, through {@link requireRole}, so its refusal names what it received. The members
3564
- * cross {@link isMessageInput}, and `audit` runs only after it refused, so a stored message costs one
3565
- * guard read. Tool-call arguments must also be JSON values before a message is stored.
3688
+ * A value that is not a plain record, such as a class instance or an object that inherits its
3689
+ * `role`, refuses first, through `isRecord` of `@orkestrel/contract`, with a text saying so. The role
3690
+ * goes next, through {@link requireRole}, so its refusal names what it received. The members
3691
+ * cross {@link isMessageInput}, an exact record guard, so a member `MessageInput` does not declare or a
3692
+ * declared member holding `undefined` refuses, and `audit` runs only after a refusal. Tool-call
3693
+ * arguments must also be JSON values before a message is stored.
3566
3694
  *
3567
3695
  * @param input - The message input to check
3568
3696
  * @param contract - The message input contract, whose `audit` is read only on a refusal
3569
3697
  * @returns The input, narrowed
3570
- * @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`
3698
+ * @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`
3571
3699
  *
3572
3700
  * @example
3573
3701
  * ```ts
@@ -3576,10 +3704,11 @@ function requireRole(role) {
3576
3704
  * ```
3577
3705
  */
3578
3706
  function requireMessageInput(input, contract) {
3579
- requireRole((0, _orkestrel_contract.isRecord)(input) ? input.role : void 0);
3707
+ if (!(0, _orkestrel_contract.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) } });
3708
+ requireRole(input.role);
3580
3709
  if (isMessageInput(input) && (input.calls === void 0 || input.calls.every((call) => (0, _orkestrel_contract.isJSONValue)(call.arguments)))) return input;
3581
3710
  const faults = contract.audit(input);
3582
- throw new ConversationError("MESSAGE", `a message input must carry string content, and calls, call, images, and thinking of their declared types\n${(0, _orkestrel_contract.describeFaults)(faults, contract.schema)}`, { context: { faults } });
3711
+ 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${(0, _orkestrel_contract.describeFaults)(faults, contract.schema)}`, { context: { faults } });
3583
3712
  }
3584
3713
  /**
3585
3714
  * Returns a conversation snapshot after checking its shape, its stored roles, and its ids, or
@@ -3957,6 +4086,7 @@ var Conversation = class {
3957
4086
  #judgments;
3958
4087
  #pending = /* @__PURE__ */ new Set();
3959
4088
  constructor(options) {
4089
+ (0, _orkestrel_contract.refuseOptionKeys)(options, CONVERSATION_OPTION_KEYS, "Conversation", "ConversationOptions");
3960
4090
  const supplied = options?.snapshot;
3961
4091
  const snapshot = supplied === void 0 ? void 0 : requireConversationSnapshot(supplied, conversationSnapshotContract);
3962
4092
  this.#judgments = new JudgmentManager(snapshot?.judgments);
@@ -4012,6 +4142,7 @@ var Conversation = class {
4012
4142
  return [...this.#sections.map((section) => buildRecapMessage(section)), ...this.#messages.values()];
4013
4143
  }
4014
4144
  async compact(options) {
4145
+ (0, _orkestrel_contract.refuseOptionKeys)(options, COMPACT_OPTION_KEYS, "Conversation", "CompactOptions");
4015
4146
  const summarize = this.#summarize;
4016
4147
  if (summarize === void 0) throw new ConversationError("SUMMARIZER", "cannot compact a conversation without a summarizer");
4017
4148
  const cap = requireSectionsCap(options?.sections ?? this.#cap);
@@ -4036,6 +4167,7 @@ var Conversation = class {
4036
4167
  return [...this.#sections.flatMap((section) => section.messages), ...this.#messages.values()].filter((message) => message.content.toLowerCase().includes(needle));
4037
4168
  }
4038
4169
  reference(options) {
4170
+ (0, _orkestrel_contract.refuseOptionKeys)(options, REFERENCE_OPTION_KEYS, "Conversation", "ConversationReferenceOptions");
4039
4171
  const label = options?.label ?? this.#id;
4040
4172
  const messages = options?.messages ?? [];
4041
4173
  return [`[Reference — conversation "${label}" — NOT part of this conversation]`, ...messages.length > 0 ? ["Relevant messages:", ...Array.from(messages, (message) => `- ${message.role}: ${message.content}`)] : []].join("\n");
@@ -4162,6 +4294,7 @@ var ConversationManager = class {
4162
4294
  #sections;
4163
4295
  #store;
4164
4296
  constructor(options) {
4297
+ (0, _orkestrel_contract.refuseOptionKeys)(options, CONVERSATION_MANAGER_KEYS, "ConversationManager", "ConversationManagerOptions");
4165
4298
  this.#summarize = options?.summarize;
4166
4299
  this.#keep = requireKeep(options?.keep ?? 0);
4167
4300
  this.#sections = options?.sections;
@@ -4180,6 +4313,7 @@ var ConversationManager = class {
4180
4313
  return [...this.#conversations.values()];
4181
4314
  }
4182
4315
  add(input) {
4316
+ (0, _orkestrel_contract.refuseOptionKeys)(input, CONVERSATION_INPUT_KEYS, "ConversationManager", "ConversationInput");
4183
4317
  const held = input?.id ?? input?.snapshot?.id;
4184
4318
  if (held !== void 0 && this.#conversations.has(held)) throw new ConversationError("ID", `conversation ${(0, _orkestrel_contract.preview)(held)} is already live; pick another id, or remove it first`, { context: { conversation: held } });
4185
4319
  const sections = input?.sections ?? this.#sections;
@@ -4643,6 +4777,16 @@ function createDatabaseConversationStore(driver = (0, _orkestrel_database.create
4643
4777
  */
4644
4778
  var WORKSPACE_SECTION_HEADER = "## Workspace";
4645
4779
  /**
4780
+ * Names the section header the loaded skills' bodies render under at the end of the system
4781
+ * message — `'## Loaded skills'`.
4782
+ */
4783
+ var LOADED_SECTION_HEADER = "## Loaded skills";
4784
+ /**
4785
+ * Names the section header the admitted workspace ids render under in the user context message —
4786
+ * `'## Workspaces'`, rendered only while a run can select a workspace.
4787
+ */
4788
+ var WORKSPACE_INDEX_HEADER = "## Workspaces";
4789
+ /**
4646
4790
  * Names the built-in section header a {@link import('./types.js').MemoManagerInterface} renders
4647
4791
  * its index under — `'## Memos'`, the `open` value when no `format.open` overrides it.
4648
4792
  *
@@ -4660,6 +4804,80 @@ var MEMO_SECTION_HEADER = "## Memos";
4660
4804
  * {@link import('./helpers.js').renderIndexEntry}.
4661
4805
  */
4662
4806
  var SKILL_SECTION_HEADER = "## Skills";
4807
+ /**
4808
+ * Lists the members `ScopeInput` declares, frozen — the keys a `Scope` is built from.
4809
+ *
4810
+ * @remarks
4811
+ * The `Scope` constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an
4812
+ * own enumerable member outside it, a misspelled list included, refuses with a `ContractError`
4813
+ * coded `'structure'` instead of building a scope with no constraint.
4814
+ */
4815
+ var SCOPE_KEYS = Object.freeze([
4816
+ "name",
4817
+ "select",
4818
+ "instructions",
4819
+ "skills",
4820
+ "memos",
4821
+ "tools",
4822
+ "files",
4823
+ "workspaces"
4824
+ ]);
4825
+ /**
4826
+ * Lists the members `ScopeFilter` declares, frozen — the keys the `narrow` method of a scope admits.
4827
+ *
4828
+ * @remarks
4829
+ * The `narrow` method passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
4830
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
4831
+ */
4832
+ var SCOPE_FILTER_KEYS = Object.freeze([
4833
+ "instructions",
4834
+ "skills",
4835
+ "memos",
4836
+ "tools",
4837
+ "files",
4838
+ "workspaces"
4839
+ ]);
4840
+ /** Lists the members `AgentContextOptions` declares, frozen — the keys the `AgentContext` constructor admits. */
4841
+ var CONTEXT_OPTION_KEYS = Object.freeze([
4842
+ "system",
4843
+ "tools",
4844
+ "instructions",
4845
+ "skills",
4846
+ "memos",
4847
+ "workspaces",
4848
+ "scope",
4849
+ "conversations",
4850
+ "select"
4851
+ ]);
4852
+ /** Lists the members `ContextBuildOptions` declares, frozen — the keys the `build` method of an `AgentContext` admits. */
4853
+ var BUILD_OPTION_KEYS = Object.freeze([
4854
+ "selection",
4855
+ "scope",
4856
+ "disclosure",
4857
+ "switchable"
4858
+ ]);
4859
+ /** Lists the members `InstructionManagerOptions` declares, frozen — the keys the `InstructionManager` constructor admits. */
4860
+ var INSTRUCTION_MANAGER_KEYS = Object.freeze([
4861
+ "on",
4862
+ "error",
4863
+ "format"
4864
+ ]);
4865
+ /** Lists the members `MemoManagerOptions` declares, frozen — the keys the `MemoManager` constructor admits. */
4866
+ var MEMO_MANAGER_KEYS = Object.freeze([
4867
+ "id",
4868
+ "on",
4869
+ "error",
4870
+ "format",
4871
+ "snapshot"
4872
+ ]);
4873
+ /** Lists the members `ScopeManagerOptions` declares, frozen — the keys the `ScopeManager` constructor admits. */
4874
+ var SCOPE_MANAGER_KEYS = Object.freeze(["on", "error"]);
4875
+ /** Lists the members `SkillManagerOptions` declares, frozen — the keys the `SkillManager` constructor admits. */
4876
+ var SKILL_MANAGER_KEYS = Object.freeze([
4877
+ "on",
4878
+ "error",
4879
+ "format"
4880
+ ]);
4663
4881
  // src/core/contexts/errors.ts
4664
4882
  /**
4665
4883
  * Reports a context snapshot that `isMemoSnapshot` refuses, thrown by the construction of a
@@ -4721,132 +4939,84 @@ function isContextError(value) {
4721
4939
  return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
4722
4940
  });
4723
4941
  }
4724
- // src/core/contexts/instructions/Instruction.ts
4942
+ // src/core/contexts/shapers.ts
4943
+ /** Describes a stored memo revision exactly. */
4944
+ var memoShape = (0, _orkestrel_contract.objectShape)({
4945
+ id: (0, _orkestrel_contract.stringShape)(),
4946
+ name: (0, _orkestrel_contract.stringShape)(),
4947
+ description: (0, _orkestrel_contract.stringShape)(),
4948
+ content: (0, _orkestrel_contract.stringShape)(),
4949
+ time: (0, _orkestrel_contract.numberShape)(),
4950
+ origin: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
4951
+ conversation: (0, _orkestrel_contract.stringShape)(),
4952
+ message: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)())
4953
+ }))
4954
+ });
4725
4955
  /**
4726
- * Represents an immutable named directive — an {@link InstructionInterface} assembled once from
4727
- * its input (`name` / `content` and an optional `priority`), the `id` minted at
4728
- * construction. Default: `0` for `priority`.
4956
+ * Describes a stored memo set exactly: an unknown key refuses.
4729
4957
  *
4730
4958
  * @remarks
4731
- * A thin immutable value object (mirroring {@link import('@orkestrel/tool').Tool}): the
4732
- * constructor mints a fresh `id` (`crypto.randomUUID()`), copies the input's `name` /
4733
- * `content`, resolves `priority` to the input's value or `0`, and carries the input's
4734
- * per-item `override` value when supplied; otherwise that property is `undefined`.
4735
- * Never mutated after construction. An
4736
- * {@link import('./InstructionManager.js').InstructionManager} keys it by `name` and
4737
- * renders it (highest `priority` first) under its section header.
4959
+ * `isMemoSnapshot` is the door a hydrate and a store read, and this shape fills a refusal's faults. A
4960
+ * lockstep test holds that `is` implies the guard, and on the snapshot fixtures the converse.
4961
+ */
4962
+ var memoSnapshotShape = (0, _orkestrel_contract.objectShape)({
4963
+ id: (0, _orkestrel_contract.stringShape)(),
4964
+ memos: (0, _orkestrel_contract.arrayShape)(memoShape)
4965
+ });
4966
+ /**
4967
+ * Describes a memo input exactly: the members of a stored revision less the `id` and `time` the
4968
+ * manager mints, each taken from {@link memoShape}.
4738
4969
  *
4739
- * @example
4740
- * ```ts
4741
- * const instruction = new Instruction({ name: 'tone', content: 'Be concise.', priority: 5 })
4742
- * instruction.priority // 5
4743
- * ```
4970
+ * @remarks
4971
+ * An unknown member, a missing required member, and a declared member holding `undefined` refuse, so
4972
+ * a revision `add` stores is one its own snapshot hydrates.
4744
4973
  */
4745
- var Instruction = class {
4746
- id;
4747
- name;
4748
- content;
4749
- priority;
4750
- override;
4751
- constructor(input) {
4752
- this.id = crypto.randomUUID();
4753
- this.name = input.name;
4754
- this.content = input.content;
4755
- this.priority = input.priority ?? 0;
4756
- if (input.override !== void 0) this.override = input.override;
4757
- }
4758
- };
4759
- // src/core/contexts/instructions/InstructionManager.ts
4974
+ var memoInputShape = (0, _orkestrel_contract.objectShape)({
4975
+ name: memoShape.properties.name,
4976
+ description: memoShape.properties.description,
4977
+ content: memoShape.properties.content,
4978
+ origin: memoShape.properties.origin
4979
+ });
4760
4980
  /**
4761
- * Registers the immutable {@link Instruction} instances a richer context assembles a directives block
4762
- * from — keyed by `name` so adding an instruction whose name is already stored overwrites it,
4763
- * last write wins, and listed by descending `priority`, carrying the `open` / `render` / `close` build contract and an observable `emitter`.
4981
+ * Describes an instruction input exactly: a string `name` and `content`, and an optional finite
4982
+ * `priority` and string `override`.
4764
4983
  *
4765
4984
  * @remarks
4766
- * - **Registry.** Instructions live in an insertion-ordered `Map` keyed by `name`;
4767
- * `add` takes one {@link InstructionInput} or a batch, mints each instruction's
4768
- * `id`, and adding an instruction whose name is already stored overwrites it (last write wins).
4769
- * `count` is the map size, `instruction(name)` looks one up, and `instructions()` lists them sorted by
4770
- * descending `priority` (a stable sort, so equal priorities keep insertion order).
4771
- * - **Build contract (the whole format cascade).** `open` is the section header a context
4772
- * renders the instructions under, `render(instruction)` renders one instruction, and
4773
- * `close` is the line after them. Each resolves the cascade most-specific-first: `render`
4774
- * returns the instruction's {@link InstructionInput.override}, else the
4775
- * `InstructionManagerOptions.format` `render`, else its `content`; `open` returns the
4776
- * options `open`, else the built-in header; `close` returns the options `close`, else
4777
- * `undefined`. A context reads the three and frames the section from them (see
4778
- * {@link import('../AgentContext.js').AgentContext}).
4779
- * - **Removal.** `remove` drops one by name, or a batch — `true` only when every supplied
4780
- * name was removed; `clear` empties the registry.
4781
- * - **Observable.** The owned {@link emitter} ({@link InstructionManagerEventMap})
4782
- * carries `add` (the created instruction) / `remove` (the name) / `clear` for
4783
- * fire-and-forget observers. Every event is emitted directly, strictly after the map
4784
- * mutation completes; the emitter isolates a listener throw and routes it to its `error`
4785
- * handler (the `error` option), so a buggy observer can never corrupt a mutation.
4985
+ * An unknown member, a missing required member, and a declared member holding `undefined` refuse.
4986
+ */
4987
+ var instructionInputShape = (0, _orkestrel_contract.objectShape)({
4988
+ name: (0, _orkestrel_contract.stringShape)(),
4989
+ content: (0, _orkestrel_contract.stringShape)(),
4990
+ priority: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)()),
4991
+ override: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)())
4992
+ });
4993
+ /**
4994
+ * Describes a skill input exactly: a string `name`, `description`, and `content`, optional `tools`
4995
+ * names, and optional `files`.
4786
4996
  *
4787
- * @example
4788
- * ```ts
4789
- * const manager = new InstructionManager()
4790
- * manager.add([
4791
- * { name: 'tone', content: 'Be concise.', priority: 1 },
4792
- * { name: 'safety', content: 'Refuse unsafe requests.', priority: 10 },
4793
- * ])
4794
- * manager.instructions().map((one) => one.name) // ['safety', 'tone'] — highest priority first
4795
- * ```
4997
+ * @remarks
4998
+ * An unknown member, a missing required member, and a declared member holding `undefined` refuse.
4999
+ * Each file is the `FileInput` interface of `@orkestrel/workspace`, which publishes no guard for it,
5000
+ * so the shape admits any defined element and `createFile`, that package's own door, reads it.
4796
5001
  */
4797
- var InstructionManager = class {
4798
- #instructions = /* @__PURE__ */ new Map();
4799
- #emitter;
4800
- #format;
4801
- constructor(options) {
4802
- this.#emitter = new _orkestrel_emitter.Emitter(options);
4803
- this.#format = options?.format;
4804
- }
4805
- get emitter() {
4806
- return this.#emitter;
4807
- }
4808
- get count() {
4809
- return this.#instructions.size;
4810
- }
4811
- get open() {
4812
- return this.#format?.open ?? "## Instructions";
4813
- }
4814
- get close() {
4815
- return this.#format?.close;
4816
- }
4817
- add(input) {
4818
- if ((0, _orkestrel_contract.isArray)(input)) return input.map((one) => this.#create(one));
4819
- return this.#create(input);
4820
- }
4821
- instruction(name) {
4822
- return this.#instructions.get(name);
4823
- }
4824
- instructions() {
4825
- return [...this.#instructions.values()].sort((a, b) => b.priority - a.priority);
4826
- }
4827
- render(instruction) {
4828
- return instruction.override ?? this.#format?.render?.(instruction) ?? instruction.content;
4829
- }
4830
- remove(names) {
4831
- if ((0, _orkestrel_contract.isArray)(names)) return removeEntries(names, (name) => this.#delete(name));
4832
- return this.#delete(names);
4833
- }
4834
- clear() {
4835
- this.#instructions.clear();
4836
- this.#emitter.emit("clear");
4837
- }
4838
- #create(input) {
4839
- const instruction = new Instruction(input);
4840
- this.#instructions.set(instruction.name, instruction);
4841
- this.#emitter.emit("add", instruction);
4842
- return instruction;
4843
- }
4844
- #delete(name) {
4845
- const removed = this.#instructions.delete(name);
4846
- if (removed) this.#emitter.emit("remove", name);
4847
- return removed;
4848
- }
4849
- };
5002
+ var skillInputShape = (0, _orkestrel_contract.objectShape)({
5003
+ name: (0, _orkestrel_contract.stringShape)(),
5004
+ description: (0, _orkestrel_contract.stringShape)(),
5005
+ content: (0, _orkestrel_contract.stringShape)(),
5006
+ tools: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)())),
5007
+ files: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.rawShape)({})))
5008
+ });
5009
+ // src/core/contexts/contracts.ts
5010
+ /** Reads the faults of a refused memo revision. */
5011
+ var memoContract = (0, _orkestrel_contract.createContract)(memoShape);
5012
+ /** Reads the faults of a refused memo snapshot. */
5013
+ var memoSnapshotContract = (0, _orkestrel_contract.createContract)(memoSnapshotShape);
5014
+ /** Admits a memo input exactly, and reads the faults of a refused one. */
5015
+ var memoInputContract = (0, _orkestrel_contract.createContract)(memoInputShape);
5016
+ /** Admits an instruction input exactly, and reads the faults of a refused one. */
5017
+ var instructionInputContract = (0, _orkestrel_contract.createContract)(instructionInputShape);
5018
+ /** Admits a skill input exactly, and reads the faults of a refused one. */
5019
+ var skillInputContract = (0, _orkestrel_contract.createContract)(skillInputShape);
4850
5020
  // src/core/contexts/helpers.ts
4851
5021
  /**
4852
5022
  * Renders a path-addressed text body as a fenced reference block — a `File: <path>` label line
@@ -5020,6 +5190,44 @@ function intersectKeys(parent, child) {
5020
5190
  return child.filter((key) => allowed.has(key));
5021
5191
  }
5022
5192
  /**
5193
+ * Intersects two scope filters list by list; an absent side constrains nothing.
5194
+ *
5195
+ * @remarks
5196
+ * Pure, and it can only tighten. Each list of the result is {@link intersectKeys} over the two
5197
+ * sides' lists, each read one time, in the order `instructions`, `skills`, `memos`, `tools`,
5198
+ * `files`, and `workspaces`. A list neither side constrains is omitted, and every returned list is
5199
+ * a fresh copy.
5200
+ * `Scope.narrow`, a run's build, and a ledger's answer-pass scope compose their layers through it.
5201
+ *
5202
+ * @param parent - The outer filter, such as the context scope (`undefined` ⇒ no constraint)
5203
+ * @param child - The inner filter, such as a run scope (`undefined` ⇒ no constraint)
5204
+ * @returns The intersected filter
5205
+ *
5206
+ * @example
5207
+ * ```ts
5208
+ * import { intersectFilters } from '@orkestrel/agent'
5209
+ *
5210
+ * intersectFilters({ tools: ['read', 'write'] }, { tools: ['write', 'admin'], files: [] })
5211
+ * // { tools: ['write'], files: [] }
5212
+ * ```
5213
+ */
5214
+ function intersectFilters(parent, child) {
5215
+ const instructions = intersectKeys(parent?.instructions, child?.instructions);
5216
+ const skills = intersectKeys(parent?.skills, child?.skills);
5217
+ const memos = intersectKeys(parent?.memos, child?.memos);
5218
+ const tools = intersectKeys(parent?.tools, child?.tools);
5219
+ const files = intersectKeys(parent?.files, child?.files);
5220
+ const workspaces = intersectKeys(parent?.workspaces, child?.workspaces);
5221
+ return {
5222
+ ...instructions === void 0 ? {} : { instructions },
5223
+ ...skills === void 0 ? {} : { skills },
5224
+ ...memos === void 0 ? {} : { memos },
5225
+ ...tools === void 0 ? {} : { tools },
5226
+ ...files === void 0 ? {} : { files },
5227
+ ...workspaces === void 0 ? {} : { workspaces }
5228
+ };
5229
+ }
5230
+ /**
5023
5231
  * Renders one index line for a memo or a skill — `- name: description`, the built-in `render` of
5024
5232
  * the memo and skill managers.
5025
5233
  *
@@ -5039,44 +5247,226 @@ function intersectKeys(parent, child) {
5039
5247
  * ```
5040
5248
  */
5041
5249
  function renderIndexEntry(entry) {
5042
- return `- ${entry.name}: ${entry.description}`.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, " ");
5250
+ return renderIndexLine(`${entry.name}: ${entry.description}`);
5043
5251
  }
5044
- // src/core/contexts/shapers.ts
5045
- /** Describes a stored memo revision exactly. */
5046
- var memoShape = (0, _orkestrel_contract.objectShape)({
5047
- id: (0, _orkestrel_contract.stringShape)(),
5048
- name: (0, _orkestrel_contract.stringShape)(),
5049
- description: (0, _orkestrel_contract.stringShape)(),
5050
- content: (0, _orkestrel_contract.stringShape)(),
5051
- time: (0, _orkestrel_contract.numberShape)(),
5052
- origin: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
5053
- conversation: (0, _orkestrel_contract.stringShape)(),
5054
- message: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)())
5055
- }))
5056
- });
5057
5252
  /**
5058
- * Describes a stored memo set exactly: an unknown key refuses.
5253
+ * Renders one index line from its text as `- text`, with each run of line-break code points as one
5254
+ * space.
5059
5255
  *
5060
5256
  * @remarks
5061
- * `isMemoSnapshot` is the door a hydrate and a store read, and this shape fills a refusal's faults. A
5062
- * lockstep test holds that `is` implies the guard, and on the snapshot fixtures the converse.
5063
- */
5064
- var memoSnapshotShape = (0, _orkestrel_contract.objectShape)({
5065
- id: (0, _orkestrel_contract.stringShape)(),
5066
- memos: (0, _orkestrel_contract.arrayShape)(memoShape)
5067
- });
5068
- // src/core/contexts/contracts.ts
5069
- /** Reads the faults of a refused memo revision. */
5070
- var memoContract = (0, _orkestrel_contract.createContract)(memoShape);
5071
- /** Reads the faults of a refused memo snapshot. */
5072
- var memoSnapshotContract = (0, _orkestrel_contract.createContract)(memoSnapshotShape);
5073
- // src/core/contexts/validators.ts
5074
- /**
5075
- * Checks whether a value is a memo's origin: a record with a string `conversation` and an optional
5076
- * string `message`.
5257
+ * Pure and total. The memo and skill index lines and the workspace index lines render through it,
5258
+ * so text in an index never opens another line or heading.
5077
5259
  *
5078
- * @remarks
5079
- * The guard is exact: an unknown member and an own `undefined` `message` return false. Malformed and
5260
+ * @param text - The line's text
5261
+ * @returns The index line
5262
+ *
5263
+ * @example
5264
+ * ```ts
5265
+ * import { renderIndexLine } from '@orkestrel/agent'
5266
+ *
5267
+ * renderIndexLine('orders') // '- orders'
5268
+ * ```
5269
+ */
5270
+ function renderIndexLine(text) {
5271
+ return `- ${text}`.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, " ");
5272
+ }
5273
+ /**
5274
+ * Renders one loaded skill's body: `### name`, a blank line, then its content.
5275
+ *
5276
+ * @remarks
5277
+ * Pure and total. The context build renders each shown skill through it under the loaded skills
5278
+ * header, last in the system message.
5279
+ *
5280
+ * @param skill - The skill whose name and content the body shows
5281
+ * @returns The body
5282
+ *
5283
+ * @example
5284
+ * ```ts
5285
+ * import { renderSkillBody } from '@orkestrel/agent'
5286
+ *
5287
+ * renderSkillBody({ name: 'refunds', content: 'Check the order first.' })
5288
+ * // '### refunds\n\nCheck the order first.'
5289
+ * ```
5290
+ */
5291
+ function renderSkillBody(skill) {
5292
+ return `### ${skill.name}\n\n${skill.content}`;
5293
+ }
5294
+ /**
5295
+ * Checks a stored record's input against its exact contract, and throws a `ContractError` coded
5296
+ * `'structure'` when it fails.
5297
+ *
5298
+ * @remarks
5299
+ * The input must pass the contract's `is` guard: a missing required member, a member the input type
5300
+ * does not declare, a declared member holding `undefined`, and a member of another type each refuse,
5301
+ * so nothing a refused input carries is stored. A class instance refuses, and the contract reads own
5302
+ * enumerable string keys alone, as every option door does. The contract's `audit` runs only after a refusal, and
5303
+ * the message names the owner, the input type, and every fault with the members to send. The
5304
+ * `context` holds the first fault's `path` and the input type as its `shape`.
5305
+ *
5306
+ * @param input - The input to check
5307
+ * @param contract - The input's compiled contract
5308
+ * @param owner - The entity named at the head of the refusal
5309
+ * @param shape - The input type named in the refusal and its `context`
5310
+ * @throws ContractError Thrown when the input fails the contract (code `'structure'`)
5311
+ *
5312
+ * @example
5313
+ * ```ts
5314
+ * import { memoInputContract, requireRecordInput } from '@orkestrel/agent'
5315
+ *
5316
+ * requireRecordInput({ name: 'tone', description: 'Prefers short replies.', content: 'Answer briefly.' }, memoInputContract, 'MemoManager', 'MemoInput') // returns
5317
+ * requireRecordInput({ name: 'tone', description: undefined, content: 'Answer briefly.' }, memoInputContract, 'MemoManager', 'MemoInput') // throws ContractError ('structure')
5318
+ * ```
5319
+ */
5320
+ function requireRecordInput(input, contract, owner, shape) {
5321
+ if (contract.is(input)) return;
5322
+ const faults = contract.audit(input);
5323
+ throw new _orkestrel_contract.ContractError(`${owner}: a ${shape} must hold each required member and no other, every member of its declared type\n${(0, _orkestrel_contract.describeFaults)(faults, contract.schema)}`, {
5324
+ code: "structure",
5325
+ context: {
5326
+ path: faults[0]?.path ?? [],
5327
+ shape
5328
+ }
5329
+ });
5330
+ }
5331
+ // src/core/contexts/instructions/Instruction.ts
5332
+ /**
5333
+ * Represents an immutable named directive — an {@link InstructionInterface} assembled once from
5334
+ * its input (`name` / `content` and an optional `priority`), the `id` minted at
5335
+ * construction. Default: `0` for `priority`.
5336
+ *
5337
+ * @remarks
5338
+ * A thin immutable value object (mirroring {@link import('@orkestrel/tool').Tool}): the
5339
+ * constructor mints a fresh `id` (`crypto.randomUUID()`), copies the input's `name` /
5340
+ * `content`, resolves `priority` to the input's value or `0`, and carries the input's
5341
+ * per-item `override` value when supplied; otherwise the instance owns no `override` member, so
5342
+ * its own keys and a JSON copy carry no member holding `undefined`. The input meets its exact
5343
+ * contract first, so a missing required member, an undeclared member, and a declared member holding
5344
+ * `undefined` refuse with a `ContractError` coded `'structure'`. Never mutated after construction. An
5345
+ * {@link import('./InstructionManager.js').InstructionManager} keys it by `name` and
5346
+ * renders it (highest `priority` first) under its section header.
5347
+ *
5348
+ * @example
5349
+ * ```ts
5350
+ * const instruction = new Instruction({ name: 'tone', content: 'Be concise.', priority: 5 })
5351
+ * instruction.priority // 5
5352
+ * ```
5353
+ */
5354
+ var Instruction = class {
5355
+ id;
5356
+ name;
5357
+ content;
5358
+ priority;
5359
+ constructor(input) {
5360
+ requireRecordInput(input, instructionInputContract, "Instruction", "InstructionInput");
5361
+ this.id = crypto.randomUUID();
5362
+ this.name = input.name;
5363
+ this.content = input.content;
5364
+ this.priority = input.priority ?? 0;
5365
+ if (input.override !== void 0) this.override = input.override;
5366
+ }
5367
+ };
5368
+ // src/core/contexts/instructions/InstructionManager.ts
5369
+ /**
5370
+ * Registers the immutable {@link Instruction} instances a richer context assembles a directives block
5371
+ * from — keyed by `name` so adding an instruction whose name is already stored overwrites it,
5372
+ * last write wins, and listed by descending `priority`, carrying the `open` / `render` / `close` build contract and an observable `emitter`.
5373
+ *
5374
+ * @remarks
5375
+ * - **Registry.** Instructions live in an insertion-ordered `Map` keyed by `name`;
5376
+ * `add` takes one {@link InstructionInput} or a batch, mints each instruction's
5377
+ * `id`, and adding an instruction whose name is already stored overwrites it (last write wins).
5378
+ * `count` is the map size, `instruction(name)` looks one up, and `instructions()` lists them sorted by
5379
+ * descending `priority` (a stable sort, so equal priorities keep insertion order).
5380
+ * - **Build contract (the whole format cascade).** `open` is the section header a context
5381
+ * renders the instructions under, `render(instruction)` renders one instruction, and
5382
+ * `close` is the line after them. Each resolves the cascade most-specific-first: `render`
5383
+ * returns the instruction's {@link InstructionInput.override}, else the
5384
+ * `InstructionManagerOptions.format` `render`, else its `content`; `open` returns the
5385
+ * options `open`, else the built-in header; `close` returns the options `close`, else
5386
+ * `undefined`. A context reads the three and frames the section from them (see
5387
+ * {@link import('../AgentContext.js').AgentContext}).
5388
+ * - **Removal.** `remove` drops one by name, or a batch — `true` only when every supplied
5389
+ * name was removed; `clear` empties the registry.
5390
+ * - **Observable.** The owned {@link emitter} ({@link InstructionManagerEventMap})
5391
+ * carries `add` (the created instruction) / `remove` (the name) / `clear` for
5392
+ * fire-and-forget observers. Every event is emitted directly, strictly after the map
5393
+ * mutation completes; the emitter isolates a listener throw and routes it to its `error`
5394
+ * handler (the `error` option), so a buggy observer can never corrupt a mutation.
5395
+ *
5396
+ * @example
5397
+ * ```ts
5398
+ * const manager = new InstructionManager()
5399
+ * manager.add([
5400
+ * { name: 'tone', content: 'Be concise.', priority: 1 },
5401
+ * { name: 'safety', content: 'Refuse unsafe requests.', priority: 10 },
5402
+ * ])
5403
+ * manager.instructions().map((one) => one.name) // ['safety', 'tone'] — highest priority first
5404
+ * ```
5405
+ */
5406
+ var InstructionManager = class {
5407
+ #instructions = /* @__PURE__ */ new Map();
5408
+ #emitter;
5409
+ #format;
5410
+ constructor(options) {
5411
+ (0, _orkestrel_contract.refuseOptionKeys)(options, INSTRUCTION_MANAGER_KEYS, "InstructionManager", "InstructionManagerOptions");
5412
+ this.#emitter = new _orkestrel_emitter.Emitter(options);
5413
+ this.#format = options?.format;
5414
+ }
5415
+ get emitter() {
5416
+ return this.#emitter;
5417
+ }
5418
+ get count() {
5419
+ return this.#instructions.size;
5420
+ }
5421
+ get open() {
5422
+ return this.#format?.open ?? "## Instructions";
5423
+ }
5424
+ get close() {
5425
+ return this.#format?.close;
5426
+ }
5427
+ add(input) {
5428
+ if ((0, _orkestrel_contract.isArray)(input)) {
5429
+ input.forEach((one) => requireRecordInput(one, instructionInputContract, "Instruction", "InstructionInput"));
5430
+ return input.map((one) => this.#create(one));
5431
+ }
5432
+ return this.#create(input);
5433
+ }
5434
+ instruction(name) {
5435
+ return this.#instructions.get(name);
5436
+ }
5437
+ instructions() {
5438
+ return [...this.#instructions.values()].sort((a, b) => b.priority - a.priority);
5439
+ }
5440
+ render(instruction) {
5441
+ return instruction.override ?? this.#format?.render?.(instruction) ?? instruction.content;
5442
+ }
5443
+ remove(names) {
5444
+ if ((0, _orkestrel_contract.isArray)(names)) return removeEntries(names, (name) => this.#delete(name));
5445
+ return this.#delete(names);
5446
+ }
5447
+ clear() {
5448
+ this.#instructions.clear();
5449
+ this.#emitter.emit("clear");
5450
+ }
5451
+ #create(input) {
5452
+ const instruction = new Instruction(input);
5453
+ this.#instructions.set(instruction.name, instruction);
5454
+ this.#emitter.emit("add", instruction);
5455
+ return instruction;
5456
+ }
5457
+ #delete(name) {
5458
+ const removed = this.#instructions.delete(name);
5459
+ if (removed) this.#emitter.emit("remove", name);
5460
+ return removed;
5461
+ }
5462
+ };
5463
+ // src/core/contexts/validators.ts
5464
+ /**
5465
+ * Checks whether a value is a memo's origin: a record with a string `conversation` and an optional
5466
+ * string `message`.
5467
+ *
5468
+ * @remarks
5469
+ * The guard is exact: an unknown member and an own `undefined` `message` return false. Malformed and
5080
5470
  * unreadable inputs return false.
5081
5471
  *
5082
5472
  * @param value - The value to test
@@ -5150,6 +5540,35 @@ function isMemoSnapshot(value) {
5150
5540
  return (0, _orkestrel_contract.isString)(id) && (0, _orkestrel_contract.arrayOf)(isMemo)(memos);
5151
5541
  });
5152
5542
  }
5543
+ /**
5544
+ * Checks whether a value implements `ScopeInterface`; total, each member read one time, any
5545
+ * implementation admitted.
5546
+ *
5547
+ * @remarks
5548
+ * A scope is a string `id` and `name`, each of `instructions`, `skills`, `memos`, `tools`, `files`,
5549
+ * and `workspaces` absent or a dense array of strings, `select` absent or a function, and a `narrow`
5550
+ * function. The guard reads each of those members one time and never calls a method. It admits
5551
+ * unknown members, a class instance, and a transparent proxy, and returns false for a throwing
5552
+ * getter, a revoked proxy, and any other malformed input.
5553
+ *
5554
+ * @param value - The value to test
5555
+ * @returns True if `value` carries the members of a {@link ScopeInterface}; false otherwise
5556
+ *
5557
+ * @example
5558
+ * ```ts
5559
+ * import { createScope, isScope } from '@orkestrel/agent'
5560
+ *
5561
+ * isScope(createScope({ name: 'reader', tools: ['search'] })) // true
5562
+ * isScope({ id: 's1', name: 'reader' }) // false
5563
+ * ```
5564
+ */
5565
+ function isScope(value) {
5566
+ return (0, _orkestrel_contract.holds)(() => {
5567
+ if (!(0, _orkestrel_contract.isObject)(value)) return false;
5568
+ const list = (0, _orkestrel_contract.optionalOf)((0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString));
5569
+ return (0, _orkestrel_contract.isString)(Reflect.get(value, "id")) && (0, _orkestrel_contract.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")) && (0, _orkestrel_contract.optionalOf)(_orkestrel_contract.isFunction)(Reflect.get(value, "select")) && (0, _orkestrel_contract.isFunction)(Reflect.get(value, "narrow"));
5570
+ });
5571
+ }
5153
5572
  // src/core/contexts/memos/MemoManager.ts
5154
5573
  /**
5155
5574
  * Registers the revisions of a memo set — durable facts keyed by `name`, where a same-name `add`
@@ -5197,6 +5616,7 @@ var MemoManager = class {
5197
5616
  * @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
5198
5617
  */
5199
5618
  constructor(options) {
5619
+ (0, _orkestrel_contract.refuseOptionKeys)(options, MEMO_MANAGER_KEYS, "MemoManager", "MemoManagerOptions");
5200
5620
  const supplied = options?.snapshot;
5201
5621
  const owned = supplied === void 0 ? void 0 : copyJSON(supplied);
5202
5622
  const snapshot = isMemoSnapshot(owned) ? owned : void 0;
@@ -5225,6 +5645,7 @@ var MemoManager = class {
5225
5645
  return this.#format?.close;
5226
5646
  }
5227
5647
  add(input) {
5648
+ ((0, _orkestrel_contract.isArray)(input) ? input : [input]).forEach((one) => requireRecordInput(one, memoInputContract, "MemoManager", "MemoInput"));
5228
5649
  if ((0, _orkestrel_contract.isArray)(input)) return input.map((one) => this.#append(one));
5229
5650
  return this.#append(input);
5230
5651
  }
@@ -5283,9 +5704,9 @@ var MemoManager = class {
5283
5704
  // src/core/contexts/scopes/Scope.ts
5284
5705
  /**
5285
5706
  * Represents a named, immutable filter over a richer context's items — an optional allow-list per
5286
- * category (`instructions` / `skills` / `memos` / `tools` / `files`), each keyed by that category's
5287
- * identity (an instruction's, a skill's, a memo's, or a tool's `name`, a workspace file's `path`)
5288
- * and read as an allow-list:
5707
+ * category (`instructions` / `skills` / `memos` / `tools` / `files` / `workspaces`), each keyed by
5708
+ * that category's identity (an instruction's, a skill's, a memo's, or a tool's `name`, a workspace
5709
+ * file's `path`, a workspace's `id`) and read as an allow-list:
5289
5710
  * `undefined` lets everything pass, `[]` lets nothing pass, and a non-empty list passes the listed
5290
5711
  * keys alone. `narrow` composes a tighter child by set intersection.
5291
5712
  *
@@ -5303,6 +5724,10 @@ var MemoManager = class {
5303
5724
  * `undefined` with a list yields the list, and `undefined` with `undefined` stays
5304
5725
  * `undefined`. Narrowing can only tighten, never widen — a key excluded by a parent
5305
5726
  * can never be re-admitted by a child. The child keeps this scope's `name` and `select` handler.
5727
+ * - **Exact.** The constructor refuses an own enumerable member of its input outside
5728
+ * `SCOPE_KEYS`, and `narrow` one of its filter outside `SCOPE_FILTER_KEYS`, with a
5729
+ * `ContractError` coded `'structure'`, so a misspelled list never builds an open scope. A
5730
+ * declared member holding `undefined` reads as omitted.
5306
5731
  *
5307
5732
  * @example
5308
5733
  * ```ts
@@ -5322,8 +5747,10 @@ var Scope = class Scope {
5322
5747
  memos;
5323
5748
  tools;
5324
5749
  files;
5750
+ workspaces;
5325
5751
  select;
5326
5752
  constructor(input) {
5753
+ (0, _orkestrel_contract.refuseOptionKeys)(input, SCOPE_KEYS, "Scope", "ScopeInput");
5327
5754
  this.name = input.name;
5328
5755
  if (input.select !== void 0) this.select = input.select;
5329
5756
  if (input.instructions !== void 0) this.instructions = [...input.instructions];
@@ -5331,21 +5758,14 @@ var Scope = class Scope {
5331
5758
  if (input.memos !== void 0) this.memos = [...input.memos];
5332
5759
  if (input.tools !== void 0) this.tools = [...input.tools];
5333
5760
  if (input.files !== void 0) this.files = [...input.files];
5761
+ if (input.workspaces !== void 0) this.workspaces = [...input.workspaces];
5334
5762
  }
5335
5763
  narrow(config) {
5336
- const instructions = intersectKeys(this.instructions, config.instructions);
5337
- const skills = intersectKeys(this.skills, config.skills);
5338
- const memos = intersectKeys(this.memos, config.memos);
5339
- const tools = intersectKeys(this.tools, config.tools);
5340
- const files = intersectKeys(this.files, config.files);
5764
+ (0, _orkestrel_contract.refuseOptionKeys)(config, SCOPE_FILTER_KEYS, "Scope", "ScopeFilter");
5341
5765
  return new Scope({
5342
5766
  name: this.name,
5343
5767
  ...this.select === void 0 ? {} : { select: this.select },
5344
- ...instructions === void 0 ? {} : { instructions },
5345
- ...skills === void 0 ? {} : { skills },
5346
- ...memos === void 0 ? {} : { memos },
5347
- ...tools === void 0 ? {} : { tools },
5348
- ...files === void 0 ? {} : { files }
5768
+ ...intersectFilters(this, config)
5349
5769
  });
5350
5770
  }
5351
5771
  };
@@ -5382,6 +5802,7 @@ var ScopeManager = class {
5382
5802
  #scopes = /* @__PURE__ */ new Map();
5383
5803
  #emitter;
5384
5804
  constructor(options) {
5805
+ (0, _orkestrel_contract.refuseOptionKeys)(options, SCOPE_MANAGER_KEYS, "ScopeManager", "ScopeManagerOptions");
5385
5806
  this.#emitter = new _orkestrel_emitter.Emitter(options);
5386
5807
  }
5387
5808
  get emitter() {
@@ -5455,6 +5876,7 @@ var SkillManager = class {
5455
5876
  #emitter;
5456
5877
  #format;
5457
5878
  constructor(options) {
5879
+ (0, _orkestrel_contract.refuseOptionKeys)(options, SKILL_MANAGER_KEYS, "SkillManager", "SkillManagerOptions");
5458
5880
  this.#emitter = new _orkestrel_emitter.Emitter(options);
5459
5881
  this.#format = options?.format;
5460
5882
  }
@@ -5471,8 +5893,12 @@ var SkillManager = class {
5471
5893
  return this.#format?.close;
5472
5894
  }
5473
5895
  add(input) {
5474
- if ((0, _orkestrel_contract.isArray)(input)) return input.map((one) => this.#create(one));
5475
- return this.#create(input);
5896
+ if ((0, _orkestrel_contract.isArray)(input)) {
5897
+ const skills = input.map((one) => this.#mint(one));
5898
+ skills.forEach((skill) => this.#store(skill));
5899
+ return skills;
5900
+ }
5901
+ return this.#store(this.#mint(input));
5476
5902
  }
5477
5903
  skill(name) {
5478
5904
  return this.#skills.get(name);
@@ -5491,14 +5917,28 @@ var SkillManager = class {
5491
5917
  this.#skills.clear();
5492
5918
  this.#emitter.emit("clear");
5493
5919
  }
5494
- #create(input) {
5495
- const skill = {
5920
+ #mint(input) {
5921
+ const given = (0, _orkestrel_contract.isRecord)(input) ? input.tools : void 0;
5922
+ const tools = (0, _orkestrel_contract.isArray)(given) ? Array.from(given) : given;
5923
+ if (tools !== void 0 && !((0, _orkestrel_contract.isArray)(tools) && tools.every(_orkestrel_contract.isString))) throw new _orkestrel_contract.ContractError("SkillManager: tools must be an array of tool names", {
5924
+ code: "placement",
5925
+ context: {
5926
+ path: ["input", "tools"],
5927
+ limit: "readonly string[]",
5928
+ received: (0, _orkestrel_contract.preview)(given)
5929
+ }
5930
+ });
5931
+ requireRecordInput(input, skillInputContract, "SkillManager", "SkillInput");
5932
+ return {
5496
5933
  id: crypto.randomUUID(),
5497
5934
  name: input.name,
5498
5935
  description: input.description,
5499
5936
  content: input.content,
5500
- files: (input.files ?? []).map((file) => (0, _orkestrel_workspace.createFile)(file))
5937
+ files: (input.files ?? []).map((file) => (0, _orkestrel_workspace.createFile)(file)),
5938
+ tools: tools === void 0 ? [] : tools
5501
5939
  };
5940
+ }
5941
+ #store(skill) {
5502
5942
  this.#skills.set(skill.name, skill);
5503
5943
  this.#emitter.emit("add", skill);
5504
5944
  return skill;
@@ -5695,8 +6135,9 @@ var DatabaseMemoStore = class {
5695
6135
  * {@link ConversationManagerInterface} message source whose active conversation is `messages`, the
5696
6136
  * {@link ToolManagerInterface} registry, and an active {@link ScopeInterface} changed through
5697
6137
  * {@link AgentContextInterface.apply}. `build()` folds the system prompt, the scoped instructions,
5698
- * and the skills index into one system message, the memos index and the active workspace's text into
5699
- * one `user` context message after it, then the conversation, and never reads `tools`.
6138
+ * the skills index, and the shown skills' bodies into one system message, the memos index and the
6139
+ * rendered workspace's text into one `user` context message after it, then the conversation, and
6140
+ * never reads `tools`.
5700
6141
  *
5701
6142
  * @remarks
5702
6143
  * - **Composition.** `system` is the optional system prompt; `instructions` / `skills` / `memos` /
@@ -5715,40 +6156,43 @@ var DatabaseMemoStore = class {
5715
6156
  * reference, no duplication), and `build()` with no selection folds that conversation's `view()`
5716
6157
  * (its per-section summaries + live tail) as the authoritative message inclusion — the scope does
5717
6158
  * not filter the conversation (it owns inclusion through compaction; scope filters only
5718
- * instructions / skills / memos / tools / workspace files). An agent run binds a conversation at
6159
+ * instructions / skills / memos / tools / workspace files / workspaces). An agent run binds a conversation at
5719
6160
  * its call, the active one unless the run names another, and builds through a selection over that
5720
6161
  * conversation, so `conversations.switch(id)` sets the next run's default at any moment and never
5721
6162
  * moves a live run; `select(conversation, request, signal)` checks the conversation it is given.
5722
6163
  * - **`build()` — the scoped assembly + the format cascade.** It folds, in order,
5723
6164
  * the system prompt, the scope-filtered instructions (a block: the section's `open` text,
5724
6165
  * each item's rendering, then any `close` text), then the scope-filtered skills index (a block
5725
- * whose entries join with a single newline) into one leading `system` message (prepended only
5726
- * when at least one part exists), then the scope-filtered memos index (the same block) and the
5727
- * active workspace's text files into one `user` context message (present only when the memos or
5728
- * workspace section renders), then
6166
+ * whose entries join with a single newline), then the shown skills' bodies, into one leading
6167
+ * `system` message (prepended only when at least one part exists), then the scope-filtered memos
6168
+ * index (the same block), the listed workspace ids, and the rendered workspace's text files into
6169
+ * one `user` context message (present only when one of those sections renders), then
5729
6170
  * appends the active conversation's `view()` (the conversation owns message inclusion through
5730
6171
  * compaction — the scope does not filter the conversation). The instruction manager resolves
5731
6172
  * each `open` / item / `close` most-specific-first: `open` = manager-options-override >
5732
6173
  * built-in; per item = item-override > manager-options-override > built-in; `close` =
5733
6174
  * manager-options-override (no built-in ⇒ no closing line when unset) (see
5734
6175
  * {@link AgentContextInterface.build}). With no override set, each section is its built-in
5735
- * header + items, no closing line. The active workspace's scoped-in image files' `base64` payload is attached to
6176
+ * header + items, no closing line. The rendered workspace's scoped-in image files' `base64` payload is attached to
5736
6177
  * the last user message (a vision provider reads images off a user turn); when no user message
5737
6178
  * exists the attachment is skipped. Built fresh each call (recomputed, never cached), so it
5738
- * always reflects the current managers / messages / scope / active workspace; it never mutates a
6179
+ * always reflects the current managers / messages / scope / workspaces; it never mutates a
5739
6180
  * manager or the stored messages. `build(selection)` adds at most one `system` message of its own
5740
6181
  * and reads the selection's messages as conversation parts without a role check; the select path
5741
6182
  * refuses a selected `system` role as a `'ROLE'` fault over `view()`; a host's direct
5742
6183
  * `build(selection)` is host-trusted, and a conversation a host implements itself whose `view()`
5743
6184
  * holds a `system` message is read as host-trusted too.
5744
- * - **The active workspace, rendered by carrier — the sole document/image context.**
5745
- * `workspaces.active` (when set) has its {@link import('@orkestrel/workspace').FileInterface} values scope-filtered by `scope.files`,
6185
+ * - **The rendered workspace, by carrier — the sole document/image context.**
6186
+ * The rendered workspace has its {@link import('@orkestrel/workspace').FileInterface} values scope-filtered by `scope.files`,
5746
6187
  * then split: text files fold into a dedicated `## Workspace` section (fenced reference blocks)
5747
6188
  * in the context message — a `user` message with a minted id, never stored — and image files'
5748
6189
  * `base64` payload attaches to the last user message of the conversation, never to the context
5749
6190
  * message. The `system` message holds host-authored text alone: the system prompt, the
5750
- * instructions, and the skills index. Active-only — never the other registered workspaces; with no
5751
- * active workspace nothing renders for workspaces. `build()` owns this render (a `Workspace` /
6191
+ * instructions, the skills index, and last the bodies of the skills `options.disclosure` shows.
6192
+ * One workspace renders: the one `options.disclosure` selects, else the active one, and only
6193
+ * while the scope's `workspaces` list admits it; with neither, nothing renders for workspaces.
6194
+ * `options.switchable` adds the admitted workspace ids to the context message, after the memos
6195
+ * index. `build()` never moves the active workspace, and it owns this render (a `Workspace` /
5752
6196
  * `WorkspaceManager` stays file-focused).
5753
6197
  * - **Tools are structural, not in the prompt.** The registry is advertised to the provider
5754
6198
  * through `tools.definitions()` (scope-filtered by the loop), never serialized into the
@@ -5775,6 +6219,7 @@ var AgentContext = class {
5775
6219
  #select;
5776
6220
  #scope;
5777
6221
  constructor(options) {
6222
+ (0, _orkestrel_contract.refuseOptionKeys)(options, CONTEXT_OPTION_KEYS, "AgentContext", "AgentContextOptions");
5778
6223
  this.#system = options?.system;
5779
6224
  this.#instructions = options?.instructions ?? new InstructionManager();
5780
6225
  this.#skills = options?.skills ?? new SkillManager();
@@ -5816,31 +6261,46 @@ var AgentContext = class {
5816
6261
  apply(scope) {
5817
6262
  this.#scope = scope;
5818
6263
  }
5819
- select(conversation, request, signal) {
5820
- const handler = this.#scope?.select ?? this.#select;
6264
+ select(conversation, request, signal, scope) {
6265
+ const handler = scope?.select ?? this.#scope?.select ?? this.#select;
5821
6266
  if (handler === void 0) return void 0;
5822
6267
  return this.#check(handler, conversation, request, signal);
5823
6268
  }
5824
- build(selection) {
5825
- const scope = this.#scope;
6269
+ build(options) {
6270
+ (0, _orkestrel_contract.refuseOptionKeys)(options, BUILD_OPTION_KEYS, "AgentContext", "ContextBuildOptions");
6271
+ const scope = intersectFilters(this.#scope, options?.scope);
5826
6272
  const parts = [];
5827
6273
  if (this.#system !== void 0) parts.push(this.#system);
5828
- const instructions = filterAllowList(scope?.instructions, this.#instructions.instructions(), (one) => one.name);
6274
+ const instructions = filterAllowList(scope.instructions, this.#instructions.instructions(), (one) => one.name);
5829
6275
  const instructed = renderSection(this.#instructions.open, instructions, (one) => this.#instructions.render(one), this.#instructions.close);
5830
6276
  if (instructed !== void 0) parts.push(instructed);
5831
- const skills = filterAllowList(scope?.skills, this.#skills.skills(), (one) => one.name);
6277
+ const skills = filterAllowList(scope.skills, this.#skills.skills(), (one) => one.name);
5832
6278
  const indexed = renderSection(this.#skills.open, skills.length === 0 ? [] : [skills.map((one) => this.#skills.render(one)).join("\n")], (block) => block, this.#skills.close);
5833
6279
  if (indexed !== void 0) parts.push(indexed);
5834
- const files = filterAllowList(scope?.files, this.#workspaces.active?.files() ?? [], (one) => one.path);
6280
+ const admitted = new Map(skills.map((one) => [one.name, one]));
6281
+ const bodies = renderSection(LOADED_SECTION_HEADER, [...new Set(options?.disclosure?.skills ?? [])].flatMap((name) => {
6282
+ const skill = admitted.get(name);
6283
+ return skill === void 0 || skill.content.trim() === "" ? [] : [skill];
6284
+ }), renderSkillBody, void 0);
6285
+ if (bodies !== void 0) parts.push(bodies);
6286
+ const selected = options?.disclosure?.workspace;
6287
+ const workspace = selected === void 0 ? this.#workspaces.active : this.#workspaces.workspace(selected);
6288
+ const shown = filterAllowList(scope.workspaces, workspace === void 0 ? [] : [workspace], (one) => one.id);
6289
+ const files = filterAllowList(scope.files, shown.flatMap((one) => one.files()), (one) => one.path);
5835
6290
  const documented = renderSection(WORKSPACE_SECTION_HEADER, files.flatMap((file) => (0, _orkestrel_workspace.isTextContent)(file.content) ? [{
5836
6291
  path: file.path,
5837
6292
  language: file.content.language,
5838
6293
  text: file.content.text
5839
6294
  }] : []), (file) => renderFencedFile(file.path, file.language, file.text), void 0);
5840
- const memos = filterAllowList(scope?.memos, this.#memos.memos(), (one) => one.name);
6295
+ const memos = filterAllowList(scope.memos, this.#memos.memos(), (one) => one.name);
5841
6296
  const remembered = renderSection(this.#memos.open, memos.length === 0 ? [] : [memos.map((one) => this.#memos.render(one)).join("\n")], (block) => block, this.#memos.close);
5842
- const tail = attachUserImages(selection?.messages ?? this.#ensureConversation().view(), collectImageData(files));
5843
- const slots = [remembered, documented].filter((slot) => slot !== void 0);
6297
+ const tail = attachUserImages(options?.selection?.messages ?? this.#ensureConversation().view(), collectImageData(files));
6298
+ const switchable = options?.switchable === true ? filterAllowList(scope.workspaces, this.#workspaces.workspaces(), (one) => one.id) : [];
6299
+ const slots = [
6300
+ remembered,
6301
+ renderSection(WORKSPACE_INDEX_HEADER, switchable.length === 0 ? [] : [switchable.map((one) => renderIndexLine(one.id)).join("\n")], (block) => block, void 0),
6302
+ documented
6303
+ ].filter((slot) => slot !== void 0);
5844
6304
  const context = slots.length === 0 ? [] : [{
5845
6305
  id: crypto.randomUUID(),
5846
6306
  role: "user",
@@ -6161,6 +6621,17 @@ function createAgentContext(options) {
6161
6621
  */
6162
6622
  var DEFAULT_AGENT_LIMIT = 10;
6163
6623
  /**
6624
+ * Opens a job tree finding that refuses a whole job, which sets it apart from a finding that names an exceeded member.
6625
+ *
6626
+ * @remarks
6627
+ * The `scanJobTree` helper writes it before the path of a job the contract refuses or a member read
6628
+ * makes unreadable, and the `AgentFanout` class reads it to open its refusal with the rule that
6629
+ * finding breaks. An
6630
+ * exceeded member's finding opens with `job`, so text later in a finding cannot pass for this
6631
+ * prefix.
6632
+ */
6633
+ var JOB_REFUSAL_PREFIX = "refused ";
6634
+ /**
6164
6635
  * Holds the model-facing texts the loop writes after a tool name in a denial result — the
6165
6636
  * `denied` note for a deny with no reason, an evaluate throw, an invalid decision, or a tool
6166
6637
  * changed after its decision; the `refused` note for an ask the approver refused; and the
@@ -6172,8 +6643,74 @@ var AUTHORITY_NOTES = Object.freeze({
6172
6643
  unanswered: "it needs approval and none was given. Do not call it again; tell the user it needs approval."
6173
6644
  });
6174
6645
  /**
6646
+ * Holds the texts the built-ins return on success — `loaded`, `unloaded`, `remembered`, and
6647
+ * `switched` — frozen.
6648
+ *
6649
+ * @remarks
6650
+ * `loaded`, `unloaded`, and `switched` are the texts the conversation's stored tool messages are
6651
+ * matched against to derive what a run shows, so each is one fixed constant. A stored message
6652
+ * counts only under a call to a built-in the run enables.
6653
+ */
6654
+ var BUILTIN_NOTES = Object.freeze({
6655
+ loaded: "The skill is loaded. Its instructions and tools are yours from your next step.",
6656
+ unloaded: "The skill is unloaded. Its instructions and tools leave from your next step.",
6657
+ remembered: "The memo is saved. Continue with what you were doing.",
6658
+ switched: "The workspace is selected. Its files are yours from your next step."
6659
+ });
6660
+ /**
6661
+ * Caps each built-in argument in code points, as the built-in argument shapes count them — a `name`
6662
+ * (a skill name, a memo name, or a workspace id), a memo `description`, and a memo `content` — and
6663
+ * the `candidates` one refusal lists, frozen.
6664
+ */
6665
+ var BUILTIN_LIMITS = Object.freeze({
6666
+ name: 128,
6667
+ description: 200,
6668
+ content: 4e3,
6669
+ candidates: 20
6670
+ });
6671
+ /**
6672
+ * Declares each built-in tool's model-facing description and annotations, keyed by its
6673
+ * {@link AgentBuiltin} name, frozen.
6674
+ *
6675
+ * @remarks
6676
+ * `memo` is `pure` and `untrusted`, because it returns memo text a model can have written;
6677
+ * `remember` is `consequential`, so an `allow` verdict becomes an `ask`. A run's own tools and the
6678
+ * definitions `collectBuiltinDefinitions` returns both read their text and annotations here.
6679
+ */
6680
+ var BUILTIN_TOOLS = Object.freeze({
6681
+ 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." }),
6682
+ 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." }),
6683
+ memo: Object.freeze({
6684
+ 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.",
6685
+ annotations: Object.freeze({
6686
+ pure: true,
6687
+ untrusted: true
6688
+ })
6689
+ }),
6690
+ remember: Object.freeze({
6691
+ 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.",
6692
+ annotations: Object.freeze({ consequential: true })
6693
+ }),
6694
+ 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." })
6695
+ });
6696
+ /**
6697
+ * Matches one control, format, line-separator, or paragraph-separator code point — the class
6698
+ * `renderArguments` encodes and a memo's one-line name and description may not hold, frozen.
6699
+ *
6700
+ * @remarks
6701
+ * Not global, so a `test` call keeps no state between calls; a global replace builds its own
6702
+ * global copy from `source`.
6703
+ */
6704
+ var ARGUMENT_CONTROL_PATTERN = Object.freeze(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u);
6705
+ /**
6706
+ * Matches a text that is blank: empty after white space and default-ignorable code points, such
6707
+ * as U+3164, are removed, frozen.
6708
+ */
6709
+ var ARGUMENT_BLANK_PATTERN = Object.freeze(/^[\p{White_Space}\p{Default_Ignorable_Code_Point}]*$/u);
6710
+ /**
6175
6711
  * Holds the model-facing text the loop writes after a tool name in the denial result of a call
6176
- * outside the active scope's tool allow-list or whose name no definition advertises this turn.
6712
+ * outside the turn's tool ceiling, the context scope's tool allow-list intersected with the run
6713
+ * scope's, or whose name no definition advertises this turn.
6177
6714
  */
6178
6715
  var SCOPE_NOTE = "it is not one of the listed tools. Use a listed tool or answer with what you have.";
6179
6716
  /**
@@ -6192,6 +6729,92 @@ var MESSAGE_TOKEN_OVERHEAD = 4;
6192
6729
  * exact count.
6193
6730
  */
6194
6731
  var IMAGE_TOKEN_ESTIMATE = 512;
6732
+ /**
6733
+ * Lists the members `AgentOptions` declares, frozen — the keys the `Agent` constructor admits.
6734
+ *
6735
+ * @remarks
6736
+ * The constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
6737
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
6738
+ */
6739
+ var AGENT_OPTION_KEYS = Object.freeze([
6740
+ "on",
6741
+ "error",
6742
+ "system",
6743
+ "tools",
6744
+ "instructions",
6745
+ "skills",
6746
+ "memos",
6747
+ "workspaces",
6748
+ "scope",
6749
+ "limit",
6750
+ "timeout",
6751
+ "budget",
6752
+ "allowance",
6753
+ "scheduler",
6754
+ "signal",
6755
+ "authority",
6756
+ "conversations",
6757
+ "window",
6758
+ "strict",
6759
+ "select",
6760
+ "builtins",
6761
+ "workspace"
6762
+ ]);
6763
+ /**
6764
+ * Lists the members `AgentRunOptions` declares, frozen — the keys the `stream` and `generate`
6765
+ * methods admit.
6766
+ *
6767
+ * @remarks
6768
+ * The `stream` method passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
6769
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
6770
+ */
6771
+ var AGENT_RUN_KEYS = Object.freeze([
6772
+ "think",
6773
+ "schema",
6774
+ "limit",
6775
+ "timeout",
6776
+ "budget",
6777
+ "allowance",
6778
+ "signal",
6779
+ "conversation",
6780
+ "prompt",
6781
+ "scope",
6782
+ "workspace"
6783
+ ]);
6784
+ /** Lists the members `AuthorityOptions` declares, frozen — the keys the `Authority` constructor admits. */
6785
+ var AUTHORITY_OPTION_KEYS = Object.freeze([
6786
+ "rules",
6787
+ "fallback",
6788
+ "ask"
6789
+ ]);
6790
+ /** Lists the members `AgentRegistryOptions` declares, frozen — the keys the `AgentRegistry` constructor admits. */
6791
+ var REGISTRY_OPTION_KEYS = Object.freeze([
6792
+ "providers",
6793
+ "tools",
6794
+ "authorities",
6795
+ "schedulers",
6796
+ "store",
6797
+ "skills",
6798
+ "memos",
6799
+ "workspaces"
6800
+ ]);
6801
+ /** Lists the members `AgentQueueOptions` declares, frozen — the keys `createAgentQueue` admits. */
6802
+ var QUEUE_OPTION_KEYS = Object.freeze([
6803
+ "registry",
6804
+ "partial",
6805
+ "concurrency",
6806
+ "retries",
6807
+ "timeout",
6808
+ "store"
6809
+ ]);
6810
+ /** Lists the members `AgentRunnerOptions` declares, frozen — the keys `createAgentRunner` admits. */
6811
+ var RUNNER_OPTION_KEYS = Object.freeze([
6812
+ "registry",
6813
+ "partial",
6814
+ "concurrency",
6815
+ "retries",
6816
+ "timeout"
6817
+ ]);
6195
6818
  // src/core/agents/errors.ts
6196
6819
  /**
6197
6820
  * Reports an {@link AgentInterface} run that ended {@link AgentResult.partial} under a
@@ -6275,6 +6898,9 @@ function isAgentJobError(value) {
6275
6898
  * `'AUTHORITY'` reports an `Authority` built with no valid `fallback`, a rule with no valid `verdict`
6276
6899
  * (its `context` names the `rule` index), and an agent built with `tools` and no `authority`. Each
6277
6900
  * throws at construction, with the options a host passes to correct it in the message.
6901
+ * `'BUILTIN'` reports a `builtins` entry that is not a built-in or repeats one, `unload` enabled
6902
+ * without `load`, and a tool manager holding a tool under an enabled built-in's name, each thrown
6903
+ * at construction.
6278
6904
  * `'CONCURRENCY'` reports a run started while another run on the same agent is live, when the
6279
6905
  * agent carries a `window` (a shared context budget) or when the run's effective `budget` or
6280
6906
  * `allowance` is an instance a live run holds — the second run's `start()` would replace that
@@ -6283,18 +6909,18 @@ function isAgentJobError(value) {
6283
6909
  * `agent` id, and the `conversation` id for a shared conversation. For concurrent runs, use
6284
6910
  * separate agents, or one agent with no `window`, distinct budget and allowance instances per
6285
6911
  * run, and a distinct conversation per run.
6286
- * `'LIMIT'` reports a construction or per-run `limit` that is not a positive safe integer,
6287
- * including one a rehydrated job carries into `build`. `'REGISTRY'` reports a rehydration name
6288
- * absent from its registry pool, on `provider` / `tool` / `authority` / `scheduler` / `build`,
6289
- * whose `context` names the `category` and the `name`, and a job that names `tools` and no
6290
- * `authority` at `build`, whose `context` names the `tools`. `'WINDOW'` reports a `strict` run whose
6912
+ * `'LIMIT'` reports a construction or per-run `limit` that is not a positive safe integer.
6913
+ * `'REGISTRY'` reports a rehydration name absent from its registry pool, on `provider` / `tool` /
6914
+ * `authority` / `scheduler` / `build`, whose `context` names the `category` and the `name`, and a
6915
+ * job `build` refuses as a record, whose `context` holds the contract's `faults` or the
6916
+ * `refusals` lines `collectJobRefusals` returns. `'WINDOW'` reports a `strict` run whose
6291
6917
  * complete provider request is still over the `window` budget, equal to its `max` included, after
6292
6918
  * the one permitted fold or with no fold possible; it settles the run `error` before the provider
6293
6919
  * is called, and its `context` holds the `agent` id, the measured `consumed`, and the window `max`.
6294
6920
  * Narrow a caught value with {@link isAgentError} and branch on `error.code`.
6295
6921
  */
6296
6922
  var AgentError = class extends Error {
6297
- /** 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. */
6923
+ /** 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. */
6298
6924
  code;
6299
6925
  /** Holds the values that locate the failure, such as the `agent` id of a refused run, or `undefined` when none were supplied. */
6300
6926
  context;
@@ -6333,7 +6959,7 @@ function isAgentError(value) {
6333
6959
  return (0, _orkestrel_contract.holds)(() => {
6334
6960
  if (!(value instanceof Error && _orkestrel_contract.INTRINSICS.prototype(value) !== Error.prototype && _orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/agent.AgentError"))?.value === value)) return false;
6335
6961
  const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
6336
- if (!(code === "APPROVER" || code === "AUTHORITY" || code === "CONCURRENCY" || code === "LIMIT" || code === "REGISTRY" || code === "SCHEMA" || code === "WINDOW")) return false;
6962
+ if (!(code === "APPROVER" || code === "AUTHORITY" || code === "BUILTIN" || code === "CONCURRENCY" || code === "LIMIT" || code === "REGISTRY" || code === "SCHEMA" || code === "WINDOW")) return false;
6337
6963
  const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
6338
6964
  return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
6339
6965
  });
@@ -6344,20 +6970,45 @@ function isAgentError(value) {
6344
6970
  * refuses instead of running an agent without it.
6345
6971
  *
6346
6972
  * @remarks
6347
- * `children` holds JSON records, because each child crosses this shape at its own `build`. `limit`
6348
- * reads as a number here and as a positive safe integer at the agent's construction, which owns that
6349
- * refusal.
6973
+ * `children` holds JSON records, because each child crosses this shape at its own `build`. Each
6974
+ * bound states what the build reads: `limit` an integer from 1 through `Number.MAX_SAFE_INTEGER`,
6975
+ * `timeout` an integer from 0 through `MAX_TIMEOUT_MS`, and `budget` a number of at least 0. A seed
6976
+ * message plays a `ConversationRole`, so a `system` message refuses here. A `scope` holds the
6977
+ * `skills`, `memos`, and `workspaces` lists, each required, and no other member; `builtins` admits
6978
+ * only the names of `AgentBuiltin`, and `collectJobRefusals` reads what a list of them cannot state.
6350
6979
  */
6351
6980
  var agentJobShape = (0, _orkestrel_contract.objectShape)({
6352
6981
  provider: (0, _orkestrel_contract.stringShape)(),
6353
- messages: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(messageInputShape)),
6982
+ messages: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
6983
+ ...messageInputShape.properties,
6984
+ role: (0, _orkestrel_contract.literalShape)(CONVERSATION_ROLES)
6985
+ }))),
6354
6986
  system: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
6355
6987
  tools: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)())),
6356
6988
  authority: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
6357
6989
  scheduler: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
6358
- limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)()),
6359
- timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)()),
6360
- budget: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)()),
6990
+ limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
6991
+ min: 1,
6992
+ max: Number.MAX_SAFE_INTEGER
6993
+ })),
6994
+ timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
6995
+ min: 0,
6996
+ max: _orkestrel_timeout.MAX_TIMEOUT_MS
6997
+ })),
6998
+ budget: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)({ min: 0 })),
6999
+ scope: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
7000
+ skills: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()),
7001
+ memos: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()),
7002
+ workspaces: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)())
7003
+ })),
7004
+ workspace: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
7005
+ builtins: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.literalShape)([
7006
+ "load",
7007
+ "unload",
7008
+ "memo",
7009
+ "remember",
7010
+ "switch"
7011
+ ]))),
6361
7012
  children: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)())))
6362
7013
  });
6363
7014
  /**
@@ -6427,6 +7078,74 @@ var agentAnswerShape = (0, _orkestrel_contract.objectShape)({
6427
7078
  id: (0, _orkestrel_contract.stringShape)(),
6428
7079
  approved: (0, _orkestrel_contract.booleanShape)()
6429
7080
  });
7081
+ /**
7082
+ * Describes the arguments of the `load` and `unload` built-ins — one `skill` name, non-empty and
7083
+ * at most `BUILTIN_LIMITS.name` code points.
7084
+ *
7085
+ * @remarks
7086
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7087
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7088
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7089
+ */
7090
+ var loadToolShape = (0, _orkestrel_contract.objectShape)({ skill: (0, _orkestrel_contract.stringShape)({
7091
+ min: 1,
7092
+ max: BUILTIN_LIMITS.name,
7093
+ description: "A skill name from the skills index"
7094
+ }) });
7095
+ /**
7096
+ * Describes the arguments of the `memo` built-in — one memo `name`, non-empty and at most
7097
+ * `BUILTIN_LIMITS.name` code points.
7098
+ *
7099
+ * @remarks
7100
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7101
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7102
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7103
+ */
7104
+ var memoToolShape = (0, _orkestrel_contract.objectShape)({ name: (0, _orkestrel_contract.stringShape)({
7105
+ min: 1,
7106
+ max: BUILTIN_LIMITS.name,
7107
+ description: "A memo name from the memos index"
7108
+ }) });
7109
+ /**
7110
+ * Describes the arguments of the `remember` built-in — a memo `name`, the one-line `description`
7111
+ * the memos index shows, and the `content`, each non-empty and within its `BUILTIN_LIMITS` cap.
7112
+ *
7113
+ * @remarks
7114
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7115
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7116
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7117
+ */
7118
+ var rememberToolShape = (0, _orkestrel_contract.objectShape)({
7119
+ name: (0, _orkestrel_contract.stringShape)({
7120
+ min: 1,
7121
+ max: BUILTIN_LIMITS.name,
7122
+ description: "A memo name the host lists for you to write"
7123
+ }),
7124
+ description: (0, _orkestrel_contract.stringShape)({
7125
+ min: 1,
7126
+ max: BUILTIN_LIMITS.description,
7127
+ description: "One line the memos index shows"
7128
+ }),
7129
+ content: (0, _orkestrel_contract.stringShape)({
7130
+ min: 1,
7131
+ max: BUILTIN_LIMITS.content,
7132
+ description: "The fact, why it holds, and how to apply it"
7133
+ })
7134
+ });
7135
+ /**
7136
+ * Describes the arguments of the `switch` built-in — one `workspace` id, non-empty and at most
7137
+ * `BUILTIN_LIMITS.name` code points.
7138
+ *
7139
+ * @remarks
7140
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7141
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7142
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7143
+ */
7144
+ var switchToolShape = (0, _orkestrel_contract.objectShape)({ workspace: (0, _orkestrel_contract.stringShape)({
7145
+ min: 1,
7146
+ max: BUILTIN_LIMITS.name,
7147
+ description: "A workspace id from the workspaces index"
7148
+ }) });
6430
7149
  // src/core/agents/contracts.ts
6431
7150
  /** Validates and projects agent frames at a JSON wire boundary. */
6432
7151
  var agentFrameContract = (0, _orkestrel_contract.createContract)(agentFrameShape);
@@ -6436,131 +7155,64 @@ var agentPromptContract = (0, _orkestrel_contract.createContract)(agentPromptSha
6436
7155
  var agentJobContract = (0, _orkestrel_contract.createContract)(agentJobShape);
6437
7156
  /** Validates and projects a page's answers to `ask` frames at a JSON wire boundary. */
6438
7157
  var agentAnswerContract = (0, _orkestrel_contract.createContract)(agentAnswerShape);
6439
- // src/core/agents/ChannelCell.ts
7158
+ /** Checks the arguments of the `load` and `unload` built-ins; its schema is the parameters they advertise. */
7159
+ var loadToolContract = (0, _orkestrel_contract.createContract)(loadToolShape);
7160
+ /** Checks the arguments of the `memo` built-in; its schema is the parameters it advertises. */
7161
+ var memoToolContract = (0, _orkestrel_contract.createContract)(memoToolShape);
7162
+ /** Checks the arguments of the `remember` built-in; its schema is the parameters it advertises. */
7163
+ var rememberToolContract = (0, _orkestrel_contract.createContract)(rememberToolShape);
7164
+ /** Checks the arguments of the `switch` built-in; its schema is the parameters it advertises. */
7165
+ var switchToolContract = (0, _orkestrel_contract.createContract)(switchToolShape);
7166
+ /**
7167
+ * Pairs each built-in's argument shape, which its tool's door compiles, with its compiled contract,
7168
+ * keyed by its {@link AgentBuiltin} name, frozen.
7169
+ *
7170
+ * @remarks
7171
+ * The one map from a built-in to its arguments: `createBuiltinTool` builds the door from `shape`,
7172
+ * and `collectBuiltinDefinitions` and `collectDisclosure` read `contract`, so the advertised
7173
+ * parameters, the door, and the derivation admit by one rule. The record declares no shape: each
7174
+ * `shape` is a reference to the one `shapers.ts` declares, held beside the contract compiled from
7175
+ * it, because the tool door takes a shape and the derivation takes a compiled contract. The record
7176
+ * is data, as the rest of this file is.
7177
+ */
7178
+ var builtinToolContracts = Object.freeze({
7179
+ load: Object.freeze({
7180
+ shape: loadToolShape,
7181
+ contract: loadToolContract
7182
+ }),
7183
+ unload: Object.freeze({
7184
+ shape: loadToolShape,
7185
+ contract: loadToolContract
7186
+ }),
7187
+ memo: Object.freeze({
7188
+ shape: memoToolShape,
7189
+ contract: memoToolContract
7190
+ }),
7191
+ remember: Object.freeze({
7192
+ shape: rememberToolShape,
7193
+ contract: rememberToolContract
7194
+ }),
7195
+ switch: Object.freeze({
7196
+ shape: switchToolShape,
7197
+ contract: switchToolContract
7198
+ })
7199
+ });
7200
+ // src/core/agents/validators.ts
6440
7201
  /**
6441
- * Holds one value a `Channel` buffers and the cell pushed after it.
7202
+ * Checks whether a value names a built-in: `load`, `unload`, `memo`, `remember`, or `switch`.
6442
7203
  *
6443
- * @remarks
6444
- * A `Channel` keeps its unread values as a chain of these cells, from its `#head` cell to its
6445
- * `#tail` cell, so no public signature names this class. The `#next` field is a latch: the
6446
- * `link` method sets it once, and a later call keeps the first cell.
6447
- */
6448
- var ChannelCell = class {
6449
- #value;
6450
- #next;
6451
- /**
6452
- * Holds a value with no next cell.
6453
- *
6454
- * @param value - The value the cell holds
6455
- */
6456
- constructor(value) {
6457
- this.#value = value;
6458
- }
6459
- /**
6460
- * Returns the value the cell holds.
6461
- *
6462
- * @returns The value
6463
- */
6464
- get value() {
6465
- return this.#value;
6466
- }
6467
- /**
6468
- * Returns the cell pushed after this one.
6469
- *
6470
- * @returns The next cell, or undefined before one is linked
6471
- */
6472
- get next() {
6473
- return this.#next;
6474
- }
6475
- /**
6476
- * Links the cell pushed after this one, once.
6477
- *
6478
- * @param cell - The cell pushed after this one
6479
- */
6480
- link(cell) {
6481
- this.#next ??= cell;
6482
- }
6483
- };
6484
- // src/core/agents/Channel.ts
6485
- /**
6486
- * Buffers chunks in a minimal unbounded async channel — the eager pump writes them in (`push`)
6487
- * and ends it (`close` / `fail`) regardless of consumption, while a consumer reads them back live
6488
- * through the `drain` async-iterator. Decoupling write from read is what lets a producer make
6489
- * progress without a consumer pulling, and it is why an agent's `result` settles whether or not
6490
- * its `events` are drained.
6491
- *
6492
- * @remarks
6493
- * A waiting `drain` call parks its void resolver in the `#waiting` field. The `push` method links a
6494
- * cell after the `#tail` cell, and the `close` and `fail` methods flag the end; each then fires and
6495
- * clears every parked resolver, so each parked reader wakes, reads the `#head` cell, and either
6496
- * takes the next chunk, returns (on `close`), throws (on `fail`), or parks again when another
6497
- * reader took the chunk first. Taking a chunk reads its value and moves `#head` to the next cell
6498
- * synchronously, so each value reaches exactly one reader, a push and a take each cost constant
6499
- * time, and every taken cell but the newest is released at once; the `#tail` field holds the newest
6500
- * cell until the next push links after it, so a channel retains at most one taken value. A reader
6501
- * suspended at its `yield` expression holds only the value it took, never a cell, so the bound on
6502
- * retained values covers both the channel's fields and a suspended reader.
6503
- * A `return()` issued while a read is parked waits for the next `push`, `close`, or `fail`.
6504
- * The parked read takes a pushed value before that `return()` settles.
7204
+ * @param value - The unknown name candidate
7205
+ * @returns True if the value names a built-in; false otherwise
6505
7206
  *
6506
7207
  * @example
6507
7208
  * ```ts
6508
- * import { Channel } from '@orkestrel/agent'
6509
- *
6510
- * const channel = new Channel<number>()
6511
- * channel.push(1)
6512
- * channel.close()
6513
- * for await (const value of channel.drain()) {
6514
- * value // 1
6515
- * }
7209
+ * isAgentBuiltin('load') // true
7210
+ * isAgentBuiltin('open') // false
6516
7211
  * ```
6517
7212
  */
6518
- var Channel = class {
6519
- #head;
6520
- #tail;
6521
- #waiting = [];
6522
- #closed = false;
6523
- #failure;
6524
- push(value) {
6525
- const cell = new ChannelCell(value);
6526
- this.#tail?.link(cell);
6527
- this.#tail = cell;
6528
- this.#head ??= cell;
6529
- this.#signal();
6530
- }
6531
- close() {
6532
- this.#closed = true;
6533
- this.#signal();
6534
- }
6535
- fail(error) {
6536
- if (this.#failure === void 0) this.#failure = { error };
6537
- this.#closed = true;
6538
- this.#signal();
6539
- }
6540
- async *drain() {
6541
- for (;;) {
6542
- while (this.#head !== void 0) {
6543
- const value = this.#head.value;
6544
- this.#head = this.#head.next;
6545
- yield value;
6546
- }
6547
- if (this.#failure !== void 0) throw this.#failure.error;
6548
- if (this.#closed) return;
6549
- await this.#park();
6550
- }
6551
- }
6552
- #signal() {
6553
- const waiting = this.#waiting;
6554
- this.#waiting = [];
6555
- for (const wake of waiting) wake();
6556
- }
6557
- #park() {
6558
- return new Promise((resolve) => {
6559
- this.#waiting = [...this.#waiting, resolve];
6560
- });
6561
- }
6562
- };
6563
- // src/core/agents/validators.ts
7213
+ function isAgentBuiltin(value) {
7214
+ return value === "load" || value === "unload" || value === "memo" || value === "remember" || value === "switch";
7215
+ }
6564
7216
  /**
6565
7217
  * Checks whether a value is an authority verdict: `allow`, `ask`, or `deny`.
6566
7218
  *
@@ -7081,6 +7733,118 @@ function handleAgentQueueJob(registry, partial, input, context) {
7081
7733
  return settleAgentJob(registry.build(input, context.signal), partial);
7082
7734
  }
7083
7735
  /**
7736
+ * Collects the refusals a job the contract admitted would meet at its build: tools or built-ins with
7737
+ * no authority, a `builtins` list `requireBuiltins` refuses, and a built-in its own `tools` name.
7738
+ *
7739
+ * @remarks
7740
+ * Each line names the member first, then the refusal and its remedy. The `builtins` line of a list
7741
+ * `requireBuiltins` refuses carries that error's message; a job's tool manager holds exactly its
7742
+ * `tools`, so a built-in its `tools` list also names is the name a manager would already hold.
7743
+ * `AgentRegistry.build` refuses a job with any line, and `scanJobTree` refuses each such job of a
7744
+ * tree before anything spawns.
7745
+ *
7746
+ * @param job - The `tools`, `builtins`, and `authority` of a job `agentJobContract` admitted
7747
+ * @returns One line a refusal, in member order; empty when the job builds
7748
+ *
7749
+ * @example
7750
+ * ```ts
7751
+ * collectJobRefusals({ provider: 'main', tools: ['search'] })
7752
+ * // ["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"]
7753
+ * ```
7754
+ */
7755
+ function collectJobRefusals(job) {
7756
+ const tools = job.tools ?? [];
7757
+ const builtins = job.builtins ?? [];
7758
+ const checked = (0, _orkestrel_contract.attempt)(() => requireBuiltins(job.builtins, { tool: () => void 0 }));
7759
+ const shared = builtins.filter((name) => tools.includes(name));
7760
+ return [
7761
+ (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,
7762
+ checked.success ? void 0 : `builtins: ${(0, _orkestrel_contract.errorToMessage)(checked.error)}`,
7763
+ shared.length === 0 ? void 0 : `builtins: names ${shared.map((name) => (0, _orkestrel_contract.preview)(name)).join(", ")}, which its tools also name; rename that tool or leave the name out of builtins`
7764
+ ].filter((line) => line !== void 0);
7765
+ }
7766
+ /**
7767
+ * Scans every job under a root for a member that exceeds its own parent's; an empty result clears the tree.
7768
+ *
7769
+ * @remarks
7770
+ * The walk audits each job, the root included, through `agentJobContract` before it reads a member,
7771
+ * because a job's `children` cross that contract as JSON records, and then reads each job the
7772
+ * contract admits through `collectJobRefusals`. A job the contract refuses yields one finding with its
7773
+ * faults, or with an unreadable note when a member read throws; a job its build would refuse yields
7774
+ * one finding a refusal, its path, a dot, and the line; and the walk reads nothing under either. A
7775
+ * child is within its parent when each of
7776
+ * its `tools` is one its parent names (a parent with no `tools` names none), each of its `builtins`
7777
+ * is one its parent names (a parent with no `builtins` names none), each name in each list of its
7778
+ * `scope` is in its parent's same list (a job with no `scope` holds the three lists empty), its
7779
+ * `authority` and `scheduler` are absent or its parent's, its `provider` is its parent's, its `limit`
7780
+ * is at most its parent's (an absent `limit` reads `DEFAULT_AGENT_LIMIT`), and, where its parent
7781
+ * holds a `timeout` or a `budget`, it holds one no larger. `system`, `messages`, and `workspace` are
7782
+ * free: a `workspace` renders nothing its own `scope` omits. A refused job's finding
7783
+ * opens with `JOB_REFUSAL_PREFIX` and then the job's path from `job`; an exceeded member's opens
7784
+ * with that path, a dot, and the member. The walk is total: an unreadable job yields a finding,
7785
+ * never a throw. The `AgentFanout` class runs it on every attempt of a unit, before the unit spawns
7786
+ * or runs.
7787
+ *
7788
+ * @param root - The root job of the tree
7789
+ * @returns One finding per refused job and per exceeded member, in walk order
7790
+ *
7791
+ * @example
7792
+ * ```ts
7793
+ * scanJobTree({ provider: 'main', children: [{ provider: 'other' }] })
7794
+ * // ['job.children[0].provider: "other" differs from its parent\'s, which is "main"']
7795
+ * ```
7796
+ */
7797
+ function scanJobTree(root) {
7798
+ const findings = [];
7799
+ const pending = [[
7800
+ root,
7801
+ "job",
7802
+ void 0
7803
+ ]];
7804
+ for (const [job, path, parent] of pending) {
7805
+ if (!agentJobContract.is(job)) {
7806
+ const faults = (0, _orkestrel_contract.attempt)(() => agentJobContract.audit(job));
7807
+ findings.push(`${JOB_REFUSAL_PREFIX}${path}: ${faults.success ? (0, _orkestrel_contract.describeFaults)(faults.value, agentJobContract.schema) : "a member read threw, so the job cannot be read"}`);
7808
+ continue;
7809
+ }
7810
+ const refusals = collectJobRefusals(job);
7811
+ if (refusals.length > 0) {
7812
+ findings.push(...refusals.map((line) => `${JOB_REFUSAL_PREFIX}${path}.${line}`));
7813
+ continue;
7814
+ }
7815
+ if (parent !== void 0) {
7816
+ const held = parent.tools ?? [];
7817
+ const outside = (job.tools ?? []).filter((name) => !held.includes(name));
7818
+ const enabled = parent.builtins ?? [];
7819
+ const extra = (job.builtins ?? []).filter((name) => !enabled.includes(name));
7820
+ const unlisted = [
7821
+ "skills",
7822
+ "memos",
7823
+ "workspaces"
7824
+ ].map((list) => [list, (job.scope?.[list] ?? []).filter((name) => !(parent.scope?.[list] ?? []).includes(name))]);
7825
+ const limit = job.limit ?? 10;
7826
+ const bound = parent.limit ?? 10;
7827
+ findings.push(...[
7828
+ outside.length === 0 ? void 0 : `${path}.tools: names ${outside.map((name) => (0, _orkestrel_contract.preview)(name)).join(", ")}, which its parent does not hold`,
7829
+ extra.length === 0 ? void 0 : `${path}.builtins: names ${extra.map((name) => (0, _orkestrel_contract.preview)(name)).join(", ")}, which its parent does not hold`,
7830
+ ...unlisted.map(([list, names]) => names.length === 0 ? void 0 : `${path}.scope.${list}: names ${names.map((name) => (0, _orkestrel_contract.preview)(name)).join(", ")}, which its parent does not hold`),
7831
+ job.authority === void 0 || job.authority === parent.authority ? void 0 : `${path}.authority: ${(0, _orkestrel_contract.preview)(job.authority)} is neither absent nor its parent's, which is ${parent.authority === void 0 ? "none" : (0, _orkestrel_contract.preview)(parent.authority)}`,
7832
+ job.provider === parent.provider ? void 0 : `${path}.provider: ${(0, _orkestrel_contract.preview)(job.provider)} differs from its parent's, which is ${(0, _orkestrel_contract.preview)(parent.provider)}`,
7833
+ job.scheduler === void 0 || job.scheduler === parent.scheduler ? void 0 : `${path}.scheduler: ${(0, _orkestrel_contract.preview)(job.scheduler)} is neither absent nor its parent's, which is ${parent.scheduler === void 0 ? "none" : (0, _orkestrel_contract.preview)(parent.scheduler)}`,
7834
+ limit <= bound ? void 0 : `${path}.limit: ${job.limit === void 0 ? `none reads ${limit}, which exceeds` : `${limit} exceeds`} its parent's ${bound}`,
7835
+ 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}`,
7836
+ 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}`
7837
+ ].filter((finding) => finding !== void 0));
7838
+ }
7839
+ for (const [index, child] of (job.children ?? []).entries()) pending.push([
7840
+ child,
7841
+ `${path}.children[${index}]`,
7842
+ job
7843
+ ]);
7844
+ }
7845
+ return findings;
7846
+ }
7847
+ /**
7084
7848
  * Assembles the settled {@link AgentResult} from a run's {@link AgentRunResult} — `thinking`,
7085
7849
  * `usage`, and `stop` are carried only when the run surfaced them, and the loop-internal
7086
7850
  * `exhausted` flag is left out.
@@ -7163,7 +7927,7 @@ function denyCall(call, reason) {
7163
7927
  function renderArguments(value) {
7164
7928
  const rendered = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.canonicalStringify)(value));
7165
7929
  if (!rendered.success || rendered.value === void 0) return void 0;
7166
- 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(""));
7930
+ 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(""));
7167
7931
  }
7168
7932
  /**
7169
7933
  * Applies the `consequential` floor to one {@link AuthorityDecision} — an `allow` for a tool
@@ -7382,6 +8146,37 @@ function requireConversation(value) {
7382
8146
  });
7383
8147
  }
7384
8148
  /**
8149
+ * Returns a scope a run can bind, or throws a `ContractError` coded `'placement'`; `undefined`
8150
+ * passes through.
8151
+ *
8152
+ * @remarks
8153
+ * The check is {@link isScope}, a total guard that reads each member one time and admits any
8154
+ * implementation of `ScopeInterface`. `undefined` passes, because an absent option leaves the
8155
+ * context scope alone as the run's ceiling. The error's `context` carries the option path, the
8156
+ * limit `'ScopeInterface'`, and a preview of the refused value.
8157
+ *
8158
+ * @param value - The run's `scope` option, or `undefined`
8159
+ * @returns The value unchanged
8160
+ * @throws {ContractError} Thrown when `value` is defined and does not implement `ScopeInterface` (code `'placement'`)
8161
+ *
8162
+ * @example
8163
+ * ```ts
8164
+ * requireScope(createScope({ name: 'reader', tools: ['search'] })) // the scope
8165
+ * requireScope({ id: 's1', name: 'reader' }) // throws ContractError ('placement')
8166
+ * ```
8167
+ */
8168
+ function requireScope(value) {
8169
+ if (value === void 0 || isScope(value)) return value;
8170
+ throw new _orkestrel_contract.ContractError("Agent: scope must implement ScopeInterface", {
8171
+ code: "placement",
8172
+ context: {
8173
+ path: ["options", "scope"],
8174
+ limit: "ScopeInterface",
8175
+ received: (0, _orkestrel_contract.preview)(value)
8176
+ }
8177
+ });
8178
+ }
8179
+ /**
7385
8180
  * Renders a tool's output as the string content of its tool message, or returns `undefined` when JSON
7386
8181
  * cannot carry it.
7387
8182
  *
@@ -7483,6 +8278,698 @@ function extractQueueOptions(options) {
7483
8278
  ...timeout === void 0 ? {} : { timeout }
7484
8279
  };
7485
8280
  }
8281
+ /**
8282
+ * Collects the definitions of the tools an agent serves itself, as a run advertises them.
8283
+ *
8284
+ * @remarks
8285
+ * Each definition carries the name, description, and annotations `BUILTIN_TOOLS` declares and the
8286
+ * parameters of the built-in's compiled contract in `builtinToolContracts`, in the order `builtins` lists them, so
8287
+ * it equals the definition of the tool `createBuiltinTool` creates. A relay server lists these
8288
+ * among its declared definitions to forward a page's built-ins. A name that is no built-in
8289
+ * contributes nothing.
8290
+ *
8291
+ * @param options - The agent options whose `builtins` to project
8292
+ * @returns The definitions, empty when `builtins` is absent
8293
+ *
8294
+ * @example
8295
+ * ```ts
8296
+ * collectBuiltinDefinitions({ builtins: ['load', 'unload'] }).map((definition) => definition.name) // ['load', 'unload']
8297
+ * ```
8298
+ */
8299
+ function collectBuiltinDefinitions(options) {
8300
+ return (options.builtins ?? []).filter(isAgentBuiltin).map((name) => {
8301
+ const declared = BUILTIN_TOOLS[name];
8302
+ const parameters = (0, _orkestrel_contract.schemaToParameters)(builtinToolContracts[name].contract.schema);
8303
+ return {
8304
+ name,
8305
+ description: declared.description,
8306
+ ...parameters === void 0 ? {} : { parameters },
8307
+ ...declared.annotations === void 0 ? {} : { annotations: declared.annotations }
8308
+ };
8309
+ });
8310
+ }
8311
+ /**
8312
+ * Returns an agent's `builtins` option as an owned, frozen list, or throws a coded error before the
8313
+ * agent is built.
8314
+ *
8315
+ * @remarks
8316
+ * `undefined` returns an empty list. The option must be an array of distinct {@link AgentBuiltin}
8317
+ * names; `unload` needs `load`, because only a loaded skill can be unloaded; and the host's tool
8318
+ * manager may hold no tool under an enabled name, because the run serves that name itself.
8319
+ *
8320
+ * @param value - The `builtins` option
8321
+ * @param tools - The host's tool manager
8322
+ * @returns The enabled names, in the order given
8323
+ * @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'`)
8324
+ *
8325
+ * @example
8326
+ * ```ts
8327
+ * requireBuiltins(['load', 'unload'], createToolManager()) // ['load', 'unload']
8328
+ * requireBuiltins(['unload'], createToolManager()) // throws AgentError ('BUILTIN')
8329
+ * ```
8330
+ */
8331
+ function requireBuiltins(value, tools) {
8332
+ if (value === void 0) return Object.freeze([]);
8333
+ if (!(0, _orkestrel_contract.isArray)(value)) throw new AgentError("BUILTIN", `builtins must be an array; received ${(0, _orkestrel_contract.preview)(value)}`);
8334
+ const names = [...value].filter(isAgentBuiltin);
8335
+ 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: (0, _orkestrel_contract.preview)(value) } });
8336
+ if (new Set(names).size !== names.length) throw new AgentError("BUILTIN", "builtins lists a name more than once", { context: { builtins: names } });
8337
+ if (names.includes("unload") && !names.includes("load")) throw new AgentError("BUILTIN", "builtins lists 'unload' without 'load'; add 'load' too", { context: { builtins: names } });
8338
+ const taken = names.filter((name) => tools.tool(name) !== void 0);
8339
+ 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: {
8340
+ builtins: names,
8341
+ taken
8342
+ } });
8343
+ return Object.freeze(names);
8344
+ }
8345
+ /**
8346
+ * Returns a `workspace` option unchanged, or throws a `ContractError` coded `'placement'`;
8347
+ * `undefined` passes through.
8348
+ *
8349
+ * @remarks
8350
+ * The check is `isString`. An id the workspace registry does not hold, or the ceiling does not
8351
+ * admit, passes here and renders nothing. The error's `context` carries the option path, the limit
8352
+ * `'string'`, and a preview of the refused value.
8353
+ *
8354
+ * @param value - The `workspace` option of an agent, a run, or a ledger's agent, or `undefined`
8355
+ * @returns The value unchanged
8356
+ * @throws {ContractError} Thrown when `value` is defined and is not a string (code `'placement'`)
8357
+ *
8358
+ * @example
8359
+ * ```ts
8360
+ * requireWorkspace('orders') // 'orders'
8361
+ * requireWorkspace(7) // throws ContractError ('placement')
8362
+ * ```
8363
+ */
8364
+ function requireWorkspace(value) {
8365
+ if (value === void 0 || (0, _orkestrel_contract.isString)(value)) return value;
8366
+ throw new _orkestrel_contract.ContractError("Agent: workspace must be a string", {
8367
+ code: "placement",
8368
+ context: {
8369
+ path: ["options", "workspace"],
8370
+ limit: "string",
8371
+ received: (0, _orkestrel_contract.preview)(value)
8372
+ }
8373
+ });
8374
+ }
8375
+ /**
8376
+ * Returns the definitions a turn advertises from the allow-list, the registered skills, and the
8377
+ * shown skills.
8378
+ *
8379
+ * @remarks
8380
+ * The allow-list admits first, as `filterAllowList` does. When no skill is passed, or an allow-list
8381
+ * admits no `load` definition, the admitted list returns as it is. Otherwise a tool any registered
8382
+ * skill names is held back unless a shown skill names it too; `load` and `unload` are never held
8383
+ * back. A caller passes the registered skills only while `load` is enabled, so with no allow-list a
8384
+ * ceiling whose empty `skills` list withholds `load` from the candidates still holds the skills'
8385
+ * tools back. Holding a tool back is presentation, never authority: a call to it is denied as
8386
+ * outside the turn's scope.
8387
+ *
8388
+ * @param definitions - The candidate definitions, in advertisement order
8389
+ * @param names - The turn's `tools` allow-list (`undefined` ⇒ all, `[]` ⇒ none)
8390
+ * @param skills - The registered skills whose `tools` gate, or none while `load` is not enabled
8391
+ * @param shown - The names of the skills the turn shows
8392
+ * @returns The advertised definitions, in candidate order
8393
+ *
8394
+ * @example
8395
+ * ```ts
8396
+ * const skills = [{ id: '1', name: 'refunds', description: 'Refunds', content: '', tools: ['refund'], files: [] }]
8397
+ * filterAdvertisedTools([{ name: 'load' }, { name: 'refund' }], undefined, skills, []) // [{ name: 'load' }]
8398
+ * filterAdvertisedTools([{ name: 'load' }, { name: 'refund' }], undefined, skills, ['refunds']).length // 2
8399
+ * ```
8400
+ */
8401
+ function filterAdvertisedTools(definitions, names, skills, shown) {
8402
+ const admitted = filterAllowList(names, definitions, (definition) => definition.name);
8403
+ if (skills.length === 0) return admitted;
8404
+ if (names !== void 0 && !admitted.some((definition) => definition.name === "load")) return admitted;
8405
+ const held = new Set(skills.flatMap((skill) => skill.tools));
8406
+ const visible = new Set(shown);
8407
+ const open = new Set(skills.filter((skill) => visible.has(skill.name)).flatMap((skill) => skill.tools));
8408
+ return admitted.filter((definition) => definition.name === "load" || definition.name === "unload" || !held.has(definition.name) || open.has(definition.name));
8409
+ }
8410
+ /**
8411
+ * Returns the definitions a run offers before the allow-list, without `remember` unless the
8412
+ * ceiling's `memos` list names a memo, without `switch` unless its `workspaces` list names a
8413
+ * workspace, and without `load` and `unload` while its `skills` list is empty.
8414
+ *
8415
+ * @remarks
8416
+ * `remember` writes and `switch` selects only a name its list holds, so an absent list and an empty
8417
+ * list each leave the built-in nothing to act on, and it is not advertised. An absent `skills` list
8418
+ * admits every registered skill, so `load` and `unload` are withheld only when the list is empty.
8419
+ * Every other definition keeps its place.
8420
+ *
8421
+ * @param definitions - The candidate definitions, in advertisement order
8422
+ * @param ceiling - The turn's ceiling
8423
+ * @returns The definitions, in candidate order
8424
+ *
8425
+ * @example
8426
+ * ```ts
8427
+ * filterBuiltinCandidates([{ name: 'memo' }, { name: 'remember' }], { memos: [] }) // [{ name: 'memo' }]
8428
+ * filterBuiltinCandidates([{ name: 'memo' }, { name: 'remember' }], { memos: ['tone'] }).length // 2
8429
+ * ```
8430
+ */
8431
+ function filterBuiltinCandidates(definitions, ceiling) {
8432
+ const writable = (ceiling.memos ?? []).length > 0;
8433
+ const selectable = (ceiling.workspaces ?? []).length > 0;
8434
+ const loadable = ceiling.skills === void 0 || ceiling.skills.length > 0;
8435
+ return definitions.filter((definition) => (definition.name !== "remember" || writable) && (definition.name !== "switch" || selectable) && (definition.name !== "load" && definition.name !== "unload" || loadable));
8436
+ }
8437
+ /**
8438
+ * Returns the refusal that names a subject and at most `BUILTIN_LIMITS.candidates` of the names a
8439
+ * caller can use.
8440
+ *
8441
+ * @remarks
8442
+ * The subject reads after "No", so it names what the caller may use, such as `'skill you can load'`.
8443
+ * The text echoes no argument, so an absent name and a name outside the ceiling read byte for byte
8444
+ * the same. Each name is JSON-encoded and capped by `preview`, in the order given. Past the cap one
8445
+ * fixed sentence sends the reader to the index in its prompt.
8446
+ *
8447
+ * @param subject - What the caller may name, such as `'skill you can load'`
8448
+ * @param names - The names the caller can use, in registry order
8449
+ * @returns The refusal text
8450
+ *
8451
+ * @example
8452
+ * ```ts
8453
+ * describeCandidates('skill you can load', ['refunds', 'billing'])
8454
+ * // 'No skill you can load has that name. Use one of: "refunds", "billing".'
8455
+ * ```
8456
+ */
8457
+ function describeCandidates(subject, names) {
8458
+ if (names.length === 0) return `No ${subject} has that name, and none is available now.`;
8459
+ return `No ${subject} has that name. Use one of: ${names.slice(0, BUILTIN_LIMITS.candidates).map((name) => (0, _orkestrel_contract.preview)(name)).join(", ")}.${names.length > BUILTIN_LIMITS.candidates ? " The index in your prompt lists the others." : ""}`;
8460
+ }
8461
+ /**
8462
+ * Checks whether a text is one nonblank line: it holds a code point other than white space and
8463
+ * default-ignorable code points (`ARGUMENT_BLANK_PATTERN`), and no control, format, line-separator, or
8464
+ * paragraph-separator code point (`ARGUMENT_CONTROL_PATTERN`).
8465
+ *
8466
+ * @remarks
8467
+ * The class is the one `renderArguments` encodes. A memo's name and description render in the memos
8468
+ * index one line each, so `remember` refuses a value outside this check.
8469
+ *
8470
+ * @param text - The text to check
8471
+ * @returns True if the text is one nonblank line; false otherwise
8472
+ *
8473
+ * @example
8474
+ * ```ts
8475
+ * matchesPlainLine('Reply tone') // true
8476
+ * matchesPlainLine('Reply\ntone') // false
8477
+ * matchesPlainLine(' ') // false
8478
+ * ```
8479
+ */
8480
+ function matchesPlainLine(text) {
8481
+ return !ARGUMENT_BLANK_PATTERN.test(text) && !ARGUMENT_CONTROL_PATTERN.test(text);
8482
+ }
8483
+ /**
8484
+ * Checks whether two ceilings carry the same lists of what a head renders: `instructions`,
8485
+ * `skills`, `memos`, `files`, and `workspaces`.
8486
+ *
8487
+ * @remarks
8488
+ * Each list compares by its members in order, and an absent list equals only an absent list,
8489
+ * because `undefined` admits everything and `[]` admits nothing. The `tools` list is not compared:
8490
+ * it reaches the tools block, never the head.
8491
+ *
8492
+ * @param left - One ceiling's lists
8493
+ * @param right - The other ceiling's lists
8494
+ * @returns True if every compared list matches; false otherwise
8495
+ *
8496
+ * @example
8497
+ * ```ts
8498
+ * matchesHeadLists({ skills: ['refunds'] }, { skills: ['refunds'], tools: [] }) // true
8499
+ * matchesHeadLists({ skills: [] }, {}) // false
8500
+ * ```
8501
+ */
8502
+ function matchesHeadLists(left, right) {
8503
+ return [
8504
+ [left.instructions, right.instructions],
8505
+ [left.skills, right.skills],
8506
+ [left.memos, right.memos],
8507
+ [left.files, right.files],
8508
+ [left.workspaces, right.workspaces]
8509
+ ].every(([one, other]) => one === void 0 || other === void 0 ? one === other : one.length === other.length && one.every((key, index) => key === other[index]));
8510
+ }
8511
+ /**
8512
+ * Resolves what a turn shows: the conversation's record, and the given workspace, within the
8513
+ * ceiling and the registries.
8514
+ *
8515
+ * @remarks
8516
+ * The caller reads the ceiling, so a turn reads each scope list one time. The skills are those
8517
+ * {@link collectDisclosure} reads from the conversation, in load order, keeping each that the skill
8518
+ * registry holds and the ceiling's `skills` list admits; an unregistered or excluded name shows
8519
+ * nothing. The workspace is the conversation's last switch, else the given one, kept only when the
8520
+ * workspace registry holds it and the ceiling's `workspaces` list admits it. The record counts only
8521
+ * the built-ins in `builtins`, as {@link collectDisclosure} states.
8522
+ *
8523
+ * @param context - The agent's context, whose scope, skills, and workspaces it reads
8524
+ * @param conversation - The conversation whose record to read
8525
+ * @param workspace - The `workspace` option of the run or the agent, or `undefined`
8526
+ * @param ceiling - The ceiling the turn read: the context scope intersected with the run scope
8527
+ * @param builtins - The built-ins the run enables
8528
+ * @returns The shown skills, in order, and the shown workspace when one is
8529
+ *
8530
+ * @example
8531
+ * ```ts
8532
+ * resolveDisclosure(agent.context, agent.context.messages, 'orders', {}, []) // { skills: [], workspace: 'orders' } when orders is registered
8533
+ * ```
8534
+ */
8535
+ function resolveDisclosure(context, conversation, workspace, ceiling, builtins) {
8536
+ const recorded = collectDisclosure(conversation, builtins);
8537
+ const admitted = new Set(filterAllowList(ceiling.skills, context.skills.skills(), (skill) => skill.name).map((skill) => skill.name));
8538
+ const skills = recorded.skills.filter((name) => admitted.has(name));
8539
+ const selected = recorded.workspace ?? workspace;
8540
+ return selected !== void 0 && context.workspaces.workspace(selected) !== void 0 && filterAllowList(ceiling.workspaces, [selected], (id) => id).length > 0 ? {
8541
+ skills,
8542
+ workspace: selected
8543
+ } : { skills };
8544
+ }
8545
+ /**
8546
+ * Collects the loaded skills and the selected workspace a conversation's stored built-in calls
8547
+ * record, counting only the built-ins a run enables.
8548
+ *
8549
+ * @remarks
8550
+ * Pure. It reads each section's retained originals, oldest first, then the live tail, and pairs
8551
+ * each tool message with the call that owns it through {@link collectToolGroups} and
8552
+ * {@link findToolCall}. It then folds the pairs in message order: a `load` whose message content
8553
+ * equals `BUILTIN_NOTES.loaded` adds its `skill`, an `unload` with `BUILTIN_NOTES.unloaded` removes
8554
+ * it, and a `switch` with `BUILTIN_NOTES.switched` selects its `workspace`. Each call's arguments
8555
+ * are admitted through the built-in's contract in `builtinToolContracts`, lossless repairs
8556
+ * included, as its tool's door admits them, so a `{ skill: 1 }` the door repaired to `'1'` counts
8557
+ * as `'1'`; arguments the contract refuses change nothing. A skill loaded again while shown keeps
8558
+ * its place, and one loaded again after its unload goes last. Any other message and a denied or
8559
+ * refused call change nothing. The result holds `workspace` only when a switch succeeded.
8560
+ *
8561
+ * A record counts only while its built-in is in `builtins`, so a host tool that holds a built-in's
8562
+ * name on a run that does not enable it shows nothing; with none of `load`, `unload`, and `switch`
8563
+ * enabled, nothing is read. A stored record cannot tell its writer, so one a host tool wrote under a
8564
+ * built-in's name while that built-in was off counts after a run enables it, bounded by the registry
8565
+ * and the ceiling as every record is.
8566
+ *
8567
+ * @param conversation - The conversation whose stored messages record the calls
8568
+ * @param builtins - The built-ins the run enables
8569
+ * @returns The skills in load order and the last selected workspace
8570
+ *
8571
+ * @example
8572
+ * ```ts
8573
+ * import { collectDisclosure, createConversation } from '@orkestrel/agent'
8574
+ *
8575
+ * collectDisclosure(createConversation(), ['load', 'unload']) // { skills: [] }
8576
+ * ```
8577
+ */
8578
+ function collectDisclosure(conversation, builtins) {
8579
+ const counted = new Set(builtins.filter((name) => name === "load" || name === "unload" || name === "switch"));
8580
+ if (counted.size === 0) return { skills: [] };
8581
+ const stored = [...conversation.sections.flatMap((section) => section.messages), ...conversation.messages()];
8582
+ const owners = new Map(collectToolGroups(stored).flatMap((group) => group.flatMap((message) => {
8583
+ const call = findToolCall(group, message);
8584
+ return call === void 0 ? [] : [[message, call]];
8585
+ })));
8586
+ return stored.reduce((shown, message) => {
8587
+ const call = owners.get(message);
8588
+ const name = call?.name;
8589
+ if (call === void 0 || !isAgentBuiltin(name) || !counted.has(name)) return shown;
8590
+ const note = name === "load" ? BUILTIN_NOTES.loaded : name === "unload" ? BUILTIN_NOTES.unloaded : BUILTIN_NOTES.switched;
8591
+ if (message.content !== note) return shown;
8592
+ const admitted = (0, _orkestrel_contract.admitValue)(builtinToolContracts[name].contract, { ...call.arguments });
8593
+ const skill = admitted.success ? admitted.value.value["skill"] : void 0;
8594
+ const workspace = admitted.success ? admitted.value.value["workspace"] : void 0;
8595
+ if (name === "load") return !(0, _orkestrel_contract.isString)(skill) || shown.skills.includes(skill) ? shown : {
8596
+ ...shown,
8597
+ skills: [...shown.skills, skill]
8598
+ };
8599
+ if (name === "unload") return !(0, _orkestrel_contract.isString)(skill) ? shown : {
8600
+ ...shown,
8601
+ skills: shown.skills.filter((one) => one !== skill)
8602
+ };
8603
+ return !(0, _orkestrel_contract.isString)(workspace) ? shown : {
8604
+ skills: shown.skills,
8605
+ workspace
8606
+ };
8607
+ }, { skills: [] });
8608
+ }
8609
+ // src/core/agents/AgentRun.ts
8610
+ /**
8611
+ * Holds one agent run's own tools and the view its turns and builds share: the ceiling a turn
8612
+ * read, what it shows, what it advertises, and what the working head was built with.
8613
+ *
8614
+ * @remarks
8615
+ * `Agent` creates one per run, so no public signature names this class. The run's built-in tools
8616
+ * live in a tool manager the run owns and never changes after construction, so a tool a host
8617
+ * registers under a built-in's name is never read: `tool` and `execute` answer a built-in's name
8618
+ * from the run's manager and every other name from the host's. Each handler reads the ceiling when
8619
+ * it runs, the context scope intersected with the run scope at that moment, never a turn's copy.
8620
+ */
8621
+ var AgentRun = class {
8622
+ #context;
8623
+ #conversation;
8624
+ #scope;
8625
+ #workspace;
8626
+ #enabled;
8627
+ #builtins;
8628
+ #tools;
8629
+ #ceiling = {};
8630
+ #shown = { skills: [] };
8631
+ #ids = [];
8632
+ #advertised = [];
8633
+ #snapshot;
8634
+ #selection;
8635
+ #length = 0;
8636
+ #head;
8637
+ /**
8638
+ * Creates the run's own tools for the enabled built-ins.
8639
+ *
8640
+ * @param context - The agent's context
8641
+ * @param conversation - The conversation the run is bound to
8642
+ * @param scope - The run scope, or `undefined`
8643
+ * @param workspace - The workspace the run renders in place of the active one, already checked, or `undefined`
8644
+ * @param builtins - The enabled built-ins, already checked
8645
+ */
8646
+ constructor(context, conversation, scope, workspace, builtins) {
8647
+ this.#context = context;
8648
+ this.#conversation = conversation;
8649
+ this.#scope = scope;
8650
+ this.#workspace = workspace;
8651
+ this.#enabled = builtins;
8652
+ this.#builtins = new Set(builtins);
8653
+ this.#tools = (0, _orkestrel_tool.createToolManager)();
8654
+ this.#tools.add(builtins.map((name) => createBuiltinTool(name, (args) => this.#handle(name, args))));
8655
+ }
8656
+ /**
8657
+ * Returns the conversation the run is bound to.
8658
+ *
8659
+ * @returns The conversation
8660
+ */
8661
+ get conversation() {
8662
+ return this.#conversation;
8663
+ }
8664
+ /**
8665
+ * Returns the run scope.
8666
+ *
8667
+ * @returns The scope, or undefined when the run has none
8668
+ */
8669
+ get scope() {
8670
+ return this.#scope;
8671
+ }
8672
+ /**
8673
+ * Returns an owned scope holding the run scope's lists as the last `observe` read them, so a build
8674
+ * renders by the turn's one read and never reads the run scope again.
8675
+ *
8676
+ * @returns The owned scope, or undefined when the run has none
8677
+ */
8678
+ get snapshot() {
8679
+ return this.#snapshot;
8680
+ }
8681
+ /**
8682
+ * Returns the ceiling the last `observe` read: the context scope intersected with the run scope.
8683
+ *
8684
+ * @returns The ceiling's lists
8685
+ */
8686
+ get ceiling() {
8687
+ return this.#ceiling;
8688
+ }
8689
+ /**
8690
+ * Returns the definitions the last `observe` advertised.
8691
+ *
8692
+ * @returns The advertised definitions
8693
+ */
8694
+ get advertised() {
8695
+ return this.#advertised;
8696
+ }
8697
+ /**
8698
+ * Returns what a build renders from the last `observe`: the shown skills and workspace, or
8699
+ * undefined when it shows neither.
8700
+ *
8701
+ * @returns The shown value, or undefined
8702
+ */
8703
+ get disclosure() {
8704
+ return this.#shown.skills.length === 0 && this.#shown.workspace === void 0 ? void 0 : this.#shown;
8705
+ }
8706
+ /**
8707
+ * Returns whether the last `observe` advertised `switch`, which lists the workspaces in the head.
8708
+ *
8709
+ * @returns True if `switch` is advertised; false otherwise
8710
+ */
8711
+ get switchable() {
8712
+ return this.#advertised.some((definition) => definition.name === "switch");
8713
+ }
8714
+ /**
8715
+ * Returns the selection the working head rendered.
8716
+ *
8717
+ * @returns The selection, or undefined before the first build
8718
+ */
8719
+ get selection() {
8720
+ return this.#selection;
8721
+ }
8722
+ /**
8723
+ * Returns the length of the array the working head was built as.
8724
+ *
8725
+ * @returns The length, 0 before the first build
8726
+ */
8727
+ get length() {
8728
+ return this.#length;
8729
+ }
8730
+ /**
8731
+ * Reads the ceiling, resolves what the run shows within it, and computes what it advertises.
8732
+ *
8733
+ * @remarks
8734
+ * The ceiling's lists are each read one time. The conversation's record is read only while
8735
+ * `load`, `unload`, or `switch` is enabled, and only the records of an enabled built-in count.
8736
+ * Registered skills hold their tools back only while `load` is enabled. The view compared with
8737
+ * the working head's is the ceiling's `instructions`, `skills`, `memos`, `files`, and
8738
+ * `workspaces` lists, the shown skills by id, the shown workspace, and whether `switch` is
8739
+ * advertised.
8740
+ *
8741
+ * @returns True if the working head was built with another view and must be rebuilt; false otherwise
8742
+ */
8743
+ observe() {
8744
+ const lists = this.#scope === void 0 ? void 0 : intersectFilters(void 0, this.#scope);
8745
+ const ceiling = intersectFilters(this.#context.scope, lists);
8746
+ this.#snapshot = lists === void 0 ? void 0 : createScope({
8747
+ name: "run",
8748
+ ...lists
8749
+ });
8750
+ const shown = resolveDisclosure(this.#context, this.#conversation, this.#workspace, ceiling, this.#enabled);
8751
+ this.#ceiling = ceiling;
8752
+ this.#shown = shown;
8753
+ this.#ids = shown.skills.flatMap((name) => this.#context.skills.skill(name)?.id ?? []);
8754
+ this.#advertised = filterAdvertisedTools(this.#candidates(ceiling), ceiling.tools, this.#builtins.has("load") ? this.#context.skills.skills() : [], shown.skills);
8755
+ const head = this.#head;
8756
+ 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;
8757
+ }
8758
+ /**
8759
+ * Records what the working head was built with: the selection, the built array's length, and
8760
+ * the view the last `observe` resolved.
8761
+ *
8762
+ * @param selection - The selection the build rendered
8763
+ * @param length - The length of the built array
8764
+ */
8765
+ record(selection, length) {
8766
+ this.#selection = selection;
8767
+ this.#length = length;
8768
+ this.#head = {
8769
+ ceiling: this.#ceiling,
8770
+ ids: this.#ids,
8771
+ workspace: this.#shown.workspace,
8772
+ switchable: this.switchable
8773
+ };
8774
+ }
8775
+ /**
8776
+ * Finds the tool a call names: the run's own for a built-in's name, the host's for any other.
8777
+ *
8778
+ * @param name - The tool name
8779
+ * @returns The tool, or undefined when none is registered under the name
8780
+ */
8781
+ tool(name) {
8782
+ return this.#builtins.has(name) ? this.#tools.tool(name) : this.#context.tools.tool(name);
8783
+ }
8784
+ /**
8785
+ * Executes one call through the run's manager for a built-in's name, and the host's for any other.
8786
+ *
8787
+ * @param call - The call to execute
8788
+ * @param context - The execution context
8789
+ * @returns The correlated result
8790
+ */
8791
+ execute(call, context) {
8792
+ return this.#builtins.has(call.name) ? this.#tools.execute(call, context) : this.#context.tools.execute(call, context);
8793
+ }
8794
+ #candidates(ceiling) {
8795
+ return [...this.#context.tools.definitions().filter((one) => !this.#builtins.has(one.name)), ...filterBuiltinCandidates(this.#tools.definitions(), ceiling)];
8796
+ }
8797
+ #live() {
8798
+ return intersectFilters(this.#context.scope, this.#scope);
8799
+ }
8800
+ #handle(name, args) {
8801
+ switch (name) {
8802
+ case "load": return this.#load(args.skill);
8803
+ case "unload": return this.#unload(args.skill);
8804
+ case "memo": return this.#memo(args.name);
8805
+ case "remember": return this.#remember(args.name, args.description, args.content);
8806
+ case "switch": return this.#switch(args.workspace);
8807
+ }
8808
+ }
8809
+ #load(skill) {
8810
+ const admitted = filterAllowList(this.#live().skills, this.#context.skills.skills(), (one) => one.name).map((one) => one.name);
8811
+ if (!(0, _orkestrel_contract.isString)(skill) || !admitted.includes(skill)) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("skill you can load", admitted));
8812
+ return BUILTIN_NOTES.loaded;
8813
+ }
8814
+ #unload(skill) {
8815
+ const loaded = collectDisclosure(this.#conversation, this.#enabled).skills;
8816
+ if (!(0, _orkestrel_contract.isString)(skill) || !loaded.includes(skill)) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("skill you loaded", loaded));
8817
+ return BUILTIN_NOTES.unloaded;
8818
+ }
8819
+ #memo(name) {
8820
+ const admitted = filterAllowList(this.#live().memos, this.#context.memos.memos(), (one) => one.name);
8821
+ const memo = admitted.find((one) => one.name === name);
8822
+ if (memo === void 0) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("memo you can read", admitted.map((one) => one.name)));
8823
+ return memo.content;
8824
+ }
8825
+ #remember(name, description, content) {
8826
+ const allowed = this.#live().memos;
8827
+ if (allowed === void 0 || allowed.length === 0) throw new _orkestrel_tool.ToolError("ARGUMENTS", "No memo can be written here: the host lists no memo name you may write. Continue without saving it.");
8828
+ if (!(0, _orkestrel_contract.isString)(name) || !allowed.includes(name)) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("memo you may write", allowed));
8829
+ if (!(0, _orkestrel_contract.isString)(description) || !(0, _orkestrel_contract.isString)(content)) throw new _orkestrel_tool.ToolError("ARGUMENTS", "Send description and content as text.");
8830
+ if (!matchesPlainLine(name) || !matchesPlainLine(description)) throw new _orkestrel_tool.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.");
8831
+ const current = this.#context.memos.memo(name);
8832
+ if (current !== void 0 && current.origin?.conversation !== this.#conversation.id) throw new _orkestrel_tool.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.");
8833
+ this.#context.memos.add({
8834
+ name,
8835
+ description,
8836
+ content,
8837
+ origin: { conversation: this.#conversation.id }
8838
+ });
8839
+ return BUILTIN_NOTES.remembered;
8840
+ }
8841
+ #switch(workspace) {
8842
+ const allowed = this.#live().workspaces;
8843
+ if (allowed === void 0 || allowed.length === 0) throw new _orkestrel_tool.ToolError("ARGUMENTS", "No workspace can be selected here: the host lists no workspace you may select. Continue with the workspace you have.");
8844
+ const admitted = filterAllowList(allowed, this.#context.workspaces.workspaces(), (one) => one.id).map((one) => one.id);
8845
+ if (!(0, _orkestrel_contract.isString)(workspace) || !admitted.includes(workspace)) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("workspace you can select", admitted));
8846
+ return BUILTIN_NOTES.switched;
8847
+ }
8848
+ };
8849
+ // src/core/agents/ChannelCell.ts
8850
+ /**
8851
+ * Holds one value a `Channel` buffers and the cell pushed after it.
8852
+ *
8853
+ * @remarks
8854
+ * A `Channel` keeps its unread values as a chain of these cells, from its `#head` cell to its
8855
+ * `#tail` cell, so no public signature names this class. The `#next` field is a latch: the
8856
+ * `link` method sets it once, and a later call keeps the first cell.
8857
+ */
8858
+ var ChannelCell = class {
8859
+ #value;
8860
+ #next;
8861
+ /**
8862
+ * Holds a value with no next cell.
8863
+ *
8864
+ * @param value - The value the cell holds
8865
+ */
8866
+ constructor(value) {
8867
+ this.#value = value;
8868
+ }
8869
+ /**
8870
+ * Returns the value the cell holds.
8871
+ *
8872
+ * @returns The value
8873
+ */
8874
+ get value() {
8875
+ return this.#value;
8876
+ }
8877
+ /**
8878
+ * Returns the cell pushed after this one.
8879
+ *
8880
+ * @returns The next cell, or undefined before one is linked
8881
+ */
8882
+ get next() {
8883
+ return this.#next;
8884
+ }
8885
+ /**
8886
+ * Links the cell pushed after this one, once.
8887
+ *
8888
+ * @param cell - The cell pushed after this one
8889
+ */
8890
+ link(cell) {
8891
+ this.#next ??= cell;
8892
+ }
8893
+ };
8894
+ // src/core/agents/Channel.ts
8895
+ /**
8896
+ * Buffers chunks in a minimal unbounded async channel — the eager pump writes them in (`push`)
8897
+ * and ends it (`close` / `fail`) regardless of consumption, while a consumer reads them back live
8898
+ * through the `drain` async-iterator. Decoupling write from read is what lets a producer make
8899
+ * progress without a consumer pulling, and it is why an agent's `result` settles whether or not
8900
+ * its `events` are drained.
8901
+ *
8902
+ * @remarks
8903
+ * A waiting `drain` call parks its void resolver in the `#waiting` field. The `push` method links a
8904
+ * cell after the `#tail` cell, and the `close` and `fail` methods flag the end; each then fires and
8905
+ * clears every parked resolver, so each parked reader wakes, reads the `#head` cell, and either
8906
+ * takes the next chunk, returns (on `close`), throws (on `fail`), or parks again when another
8907
+ * reader took the chunk first. Taking a chunk reads its value and moves `#head` to the next cell
8908
+ * synchronously, so each value reaches exactly one reader, a push and a take each cost constant
8909
+ * time, and every taken cell but the newest is released at once; the `#tail` field holds the newest
8910
+ * cell until the next push links after it, so a channel retains at most one taken value. A reader
8911
+ * suspended at its `yield` expression holds only the value it took, never a cell, so the bound on
8912
+ * retained values covers both the channel's fields and a suspended reader.
8913
+ * A `return()` issued while a read is parked waits for the next `push`, `close`, or `fail`.
8914
+ * The parked read takes a pushed value before that `return()` settles.
8915
+ *
8916
+ * @example
8917
+ * ```ts
8918
+ * import { Channel } from '@orkestrel/agent'
8919
+ *
8920
+ * const channel = new Channel<number>()
8921
+ * channel.push(1)
8922
+ * channel.close()
8923
+ * for await (const value of channel.drain()) {
8924
+ * value // 1
8925
+ * }
8926
+ * ```
8927
+ */
8928
+ var Channel = class {
8929
+ #head;
8930
+ #tail;
8931
+ #waiting = [];
8932
+ #closed = false;
8933
+ #failure;
8934
+ push(value) {
8935
+ const cell = new ChannelCell(value);
8936
+ this.#tail?.link(cell);
8937
+ this.#tail = cell;
8938
+ this.#head ??= cell;
8939
+ this.#signal();
8940
+ }
8941
+ close() {
8942
+ this.#closed = true;
8943
+ this.#signal();
8944
+ }
8945
+ fail(error) {
8946
+ if (this.#failure === void 0) this.#failure = { error };
8947
+ this.#closed = true;
8948
+ this.#signal();
8949
+ }
8950
+ async *drain() {
8951
+ for (;;) {
8952
+ while (this.#head !== void 0) {
8953
+ const value = this.#head.value;
8954
+ this.#head = this.#head.next;
8955
+ yield value;
8956
+ }
8957
+ if (this.#failure !== void 0) throw this.#failure.error;
8958
+ if (this.#closed) return;
8959
+ await this.#park();
8960
+ }
8961
+ }
8962
+ #signal() {
8963
+ const waiting = this.#waiting;
8964
+ this.#waiting = [];
8965
+ for (const wake of waiting) wake();
8966
+ }
8967
+ #park() {
8968
+ return new Promise((resolve) => {
8969
+ this.#waiting = [...this.#waiting, resolve];
8970
+ });
8971
+ }
8972
+ };
7486
8973
  // src/core/agents/Agent.ts
7487
8974
  /**
7488
8975
  * Composes a {@link ProviderInterface}, an {@link AgentContext}, and a
@@ -7547,9 +9034,12 @@ var Agent = class {
7547
9034
  #window;
7548
9035
  #strict;
7549
9036
  #emitter;
9037
+ #builtins;
9038
+ #workspace;
7550
9039
  #settled = "idle";
7551
9040
  #runs = /* @__PURE__ */ new Map();
7552
9041
  constructor(provider, options) {
9042
+ (0, _orkestrel_contract.refuseOptionKeys)(options, AGENT_OPTION_KEYS, "Agent", "AgentOptions");
7553
9043
  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");
7554
9044
  this.#limit = requireLimit(options?.limit ?? 10);
7555
9045
  this.#id = crypto.randomUUID();
@@ -7566,6 +9056,9 @@ var Agent = class {
7566
9056
  ...options?.conversations === void 0 ? {} : { conversations: options.conversations },
7567
9057
  ...options?.select === void 0 ? {} : { select: options.select }
7568
9058
  });
9059
+ this.#builtins = requireBuiltins(options?.builtins, this.#context.tools);
9060
+ 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");
9061
+ this.#workspace = requireWorkspace(options?.workspace);
7569
9062
  this.#timeoutMs = options?.timeout;
7570
9063
  this.#budget = options?.budget;
7571
9064
  this.#allowance = options?.allowance;
@@ -7592,6 +9085,7 @@ var Agent = class {
7592
9085
  return this.stream(options).result;
7593
9086
  }
7594
9087
  stream(options) {
9088
+ (0, _orkestrel_contract.refuseOptionKeys)(options, AGENT_RUN_KEYS, "Agent", "AgentRunOptions");
7595
9089
  const prompt = options?.prompt;
7596
9090
  if (prompt !== void 0 && !agentPromptContract.is(prompt)) {
7597
9091
  const faults = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.describeFaults)(agentPromptContract.audit(prompt), agentPromptContract.schema));
@@ -7604,11 +9098,13 @@ var Agent = class {
7604
9098
  }
7605
9099
  });
7606
9100
  }
9101
+ const workspace = requireWorkspace(options?.workspace) ?? this.#workspace;
7607
9102
  const limit = options?.limit === void 0 ? this.#limit : requireLimit(options.limit);
7608
9103
  const budget = requireBudget(options?.budget ?? this.#budget, "budget");
7609
9104
  const allowance = requireBudget(options?.allowance ?? this.#allowance, "allowance");
7610
9105
  const held = [budget, allowance].filter((one, index, all) => one !== void 0 && all.indexOf(one) === index);
7611
9106
  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 } });
9107
+ const scope = requireScope(options?.scope);
7612
9108
  const conversation = requireConversation(options?.conversation) ?? this.#context.messages;
7613
9109
  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: {
7614
9110
  agent: this.#id,
@@ -7632,7 +9128,8 @@ var Agent = class {
7632
9128
  this.#emitter.emit("start", this.id);
7633
9129
  const channel = new Channel();
7634
9130
  const settled = Promise.withResolvers();
7635
- 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));
9131
+ const run = new AgentRun(this.#context, conversation, scope, workspace, this.#builtins);
9132
+ 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));
7636
9133
  settled.promise.catch(() => {});
7637
9134
  return {
7638
9135
  events: this.#drainEvents(channel, abort),
@@ -7662,10 +9159,10 @@ var Agent = class {
7662
9159
  settled.reject(error);
7663
9160
  this.#emitter.emit("error", error);
7664
9161
  }
7665
- async #drive(abort, conversation, channel, think, schema, limit, budget, allowance) {
7666
- const run = this.#execute(abort, conversation, think, schema, limit, budget, allowance);
9162
+ async #drive(abort, run, channel, think, schema, limit, budget, allowance) {
9163
+ const steps = this.#execute(abort, run, think, schema, limit, budget, allowance);
7667
9164
  for (;;) {
7668
- const next = await run.next();
9165
+ const next = await steps.next();
7669
9166
  if (next.done === true) return next.value;
7670
9167
  channel.push(next.value);
7671
9168
  }
@@ -7677,15 +9174,16 @@ var Agent = class {
7677
9174
  abort.abort();
7678
9175
  }
7679
9176
  }
7680
- async *#execute(abort, conversation, think, schema, limit, budget, allowance) {
9177
+ async *#execute(abort, run, think, schema, limit, budget, allowance) {
9178
+ const conversation = run.conversation;
7681
9179
  const last = conversation.view().at(-1);
7682
9180
  const request = last?.role === "user" ? last : void 0;
7683
- const selection = this.#select(conversation, request, abort, budget);
9181
+ run.observe();
9182
+ const selection = this.#select(run, request, abort, budget);
7684
9183
  const entry = selection === void 0 ? {
7685
- messages: this.#build(conversation),
9184
+ messages: this.#build(run),
7686
9185
  usage: void 0
7687
9186
  } : await selection;
7688
- const tools = this.#context.tools;
7689
9187
  const options = think === void 0 && schema === void 0 ? void 0 : {
7690
9188
  ...think === void 0 ? {} : { think },
7691
9189
  ...schema === void 0 ? {} : { schema }
@@ -7694,14 +9192,14 @@ var Agent = class {
7694
9192
  const turns = [];
7695
9193
  while (turns.length < limit && (turns.at(-1)?.outcome ?? "tools") === "tools") {
7696
9194
  const index = turns.length;
7697
- const base = turns.at(-1)?.messages ?? entry.messages;
9195
+ const prior = turns.at(-1)?.messages ?? entry.messages;
7698
9196
  const futile = turns.at(-1)?.futile ?? false;
7699
9197
  const stopped = {
7700
9198
  content: "",
7701
9199
  thinking: void 0,
7702
9200
  usage: void 0,
7703
9201
  spent: void 0,
7704
- messages: base,
9202
+ messages: prior,
7705
9203
  outcome: "cancel",
7706
9204
  partial: true,
7707
9205
  futile,
@@ -7720,9 +9218,10 @@ var Agent = class {
7720
9218
  turns.push(stopped);
7721
9219
  continue;
7722
9220
  }
7723
- const names = this.#context.scope?.tools?.slice();
7724
- const advertised = filterAllowList(names, tools.definitions(), (definition) => definition.name);
7725
- const held = new Map(advertised.map((definition) => [definition.name, tools.tool(definition.name)]));
9221
+ const base = run.observe() ? this.#refresh(run, prior) : prior;
9222
+ const names = run.ceiling.tools;
9223
+ const advertised = run.advertised;
9224
+ const held = new Map(advertised.map((definition) => [definition.name, run.tool(definition.name)]));
7726
9225
  const definitions = advertised.length > 0 ? advertised : void 0;
7727
9226
  const composed = this.#compose(base, definitions, options);
7728
9227
  const admitted = this.#window === void 0 ? {
@@ -7730,7 +9229,7 @@ var Agent = class {
7730
9229
  messages: base,
7731
9230
  spent: void 0,
7732
9231
  futile
7733
- } : await this.#admit(this.#window, conversation, composed, base, request, index > 0, futile, abort, budget);
9232
+ } : await this.#admit(this.#window, run, composed, base, request, index > 0, futile, abort, budget);
7734
9233
  if (admitted.dispatch === void 0) {
7735
9234
  turns.push({
7736
9235
  ...stopped,
@@ -7740,12 +9239,25 @@ var Agent = class {
7740
9239
  });
7741
9240
  continue;
7742
9241
  }
9242
+ const dispatch = admitted.dispatch;
9243
+ Object.freeze(dispatch.messages);
9244
+ if (dispatch.tools !== void 0) Object.freeze(dispatch.tools);
9245
+ this.#emitter.emit("request", index, Object.freeze(dispatch), conversation);
9246
+ if (abort.signal.aborted) {
9247
+ turns.push({
9248
+ ...stopped,
9249
+ messages: admitted.messages,
9250
+ spent: admitted.spent,
9251
+ futile: admitted.futile
9252
+ });
9253
+ continue;
9254
+ }
7743
9255
  const streamed = [];
7744
9256
  const meter = allowance === void 0 ? void 0 : (0, _orkestrel_budget.createTokenMeter)({
7745
9257
  budget: allowance,
7746
9258
  estimator: estimateTokens
7747
9259
  });
7748
- const provided = yield* this.#provide(admitted.dispatch, abort.signal, streamed, meter);
9260
+ const provided = yield* this.#provide(dispatch, abort.signal, streamed, meter);
7749
9261
  const settled = this.#reconcile(provided, abort, streamed, admitted, meter, budget, allowance);
7750
9262
  if (!provided.success) {
7751
9263
  turns.push({
@@ -7768,8 +9280,8 @@ var Agent = class {
7768
9280
  if (result.tools !== void 0 && result.tools.length > 0) {
7769
9281
  const admittedCalls = filterAllowList(names, result.tools, (call) => call.name);
7770
9282
  const authorization = this.#authorize(result.tools, admittedCalls, advertised, held);
7771
- if (authorization.asked.length > 0 && !abort.signal.aborted) yield* this.#approve(tools, authorization, abort.signal);
7772
- if (!abort.signal.aborted) this.#confirm(tools, authorization, advertised);
9283
+ if (authorization.asked.length > 0 && !abort.signal.aborted) yield* this.#approve(run, authorization, abort.signal);
9284
+ if (!abort.signal.aborted) this.#confirm(run, authorization, advertised);
7773
9285
  if (abort.signal.aborted) {
7774
9286
  turns.push({
7775
9287
  ...settled,
@@ -7784,7 +9296,7 @@ var Agent = class {
7784
9296
  calls: result.tools,
7785
9297
  ...recorded
7786
9298
  });
7787
- const results = await this.#dispatch(tools, result.tools, abort.signal, authorization, advertised);
9299
+ const results = await this.#dispatch(run, result.tools, abort.signal, authorization, advertised);
7788
9300
  const replies = [];
7789
9301
  for (const [position, call] of result.tools.entries()) {
7790
9302
  const dispatched = results[position];
@@ -7880,11 +9392,11 @@ var Agent = class {
7880
9392
  #compose(messages, tools, options) {
7881
9393
  return {
7882
9394
  messages: [...stripThinking(messages, this.#replay)],
7883
- ...tools === void 0 ? {} : { tools },
9395
+ ...tools === void 0 ? {} : { tools: [...tools] },
7884
9396
  ...options === void 0 ? {} : { options }
7885
9397
  };
7886
9398
  }
7887
- async #admit(window, conversation, composed, messages, request, latch, futile, abort, budget) {
9399
+ async #admit(window, run, composed, messages, request, latch, futile, abort, budget) {
7888
9400
  window.clear();
7889
9401
  window.consume(composed);
7890
9402
  if (!window.exhausted) return {
@@ -7899,15 +9411,16 @@ var Agent = class {
7899
9411
  spent: void 0,
7900
9412
  futile
7901
9413
  };
9414
+ const conversation = run.conversation;
7902
9415
  if (!conversation.summarizable || futile) return {
7903
9416
  dispatch: this.#enforce(window, composed),
7904
9417
  messages,
7905
9418
  spent: void 0,
7906
9419
  futile
7907
9420
  };
7908
- 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));
9421
+ 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));
7909
9422
  }
7910
- async #refold(section, window, conversation, composed, messages, request, latch, abort, budget) {
9423
+ async #refold(section, window, run, composed, messages, request, latch, abort, budget) {
7911
9424
  if (abort.signal.aborted) return {
7912
9425
  dispatch: void 0,
7913
9426
  messages,
@@ -7920,9 +9433,9 @@ var Agent = class {
7920
9433
  spent: void 0,
7921
9434
  futile: latch
7922
9435
  };
7923
- const selection = this.#select(conversation, request, abort, budget);
9436
+ const selection = this.#select(run, request, abort, budget);
7924
9437
  const rebuilt = selection === void 0 ? {
7925
- messages: this.#build(conversation),
9438
+ messages: this.#build(run),
7926
9439
  usage: void 0
7927
9440
  } : await selection;
7928
9441
  if (abort.signal.aborted) return {
@@ -7971,47 +9484,61 @@ var Agent = class {
7971
9484
  } });
7972
9485
  return request;
7973
9486
  }
7974
- #select(conversation, request, abort, budget) {
7975
- const pending = request === void 0 ? void 0 : this.#context.select(conversation, request, abort.signal);
7976
- return pending === void 0 ? void 0 : pending.then((selection) => this.#adopt(conversation, selection, abort, budget), (error) => this.#reject(conversation, error, abort));
9487
+ #select(run, request, abort, budget) {
9488
+ const pending = request === void 0 ? void 0 : this.#context.select(run.conversation, request, abort.signal, run.scope);
9489
+ return pending === void 0 ? void 0 : pending.then((selection) => this.#adopt(run, selection, abort, budget), (error) => this.#reject(run, error, abort));
7977
9490
  }
7978
- #adopt(conversation, selection, abort, budget) {
9491
+ #adopt(run, selection, abort, budget) {
7979
9492
  const usage = selection.usage === void 0 ? void 0 : sanitizeUsage(selection.usage);
7980
9493
  if (usage !== void 0) chargeUsage(budget, usage, 0);
7981
9494
  if (abort.signal.aborted) return {
7982
- messages: this.#build(conversation),
9495
+ messages: this.#build(run),
7983
9496
  usage
7984
9497
  };
7985
9498
  if (selection.fault !== void 0) {
7986
9499
  this.#emitter.emit("fault", selection.fault);
7987
9500
  if (abort.signal.aborted) return {
7988
- messages: this.#build(conversation),
9501
+ messages: this.#build(run),
7989
9502
  usage
7990
9503
  };
7991
9504
  if (this.#strict) throw selection.fault;
7992
9505
  }
7993
- const messages = this.#build(conversation, selection);
9506
+ const messages = this.#build(run, selection);
7994
9507
  this.#emitter.emit("select", selection);
7995
9508
  return {
7996
9509
  messages,
7997
9510
  usage
7998
9511
  };
7999
9512
  }
8000
- #reject(conversation, error, abort) {
9513
+ #reject(run, error, abort) {
8001
9514
  if (!abort.signal.aborted) {
8002
9515
  this.#emitter.emit("fault", error);
8003
9516
  if (this.#strict && !abort.signal.aborted) throw error;
8004
9517
  }
8005
9518
  return {
8006
- messages: this.#build(conversation),
9519
+ messages: this.#build(run),
8007
9520
  usage: void 0
8008
9521
  };
8009
9522
  }
8010
- #build(conversation, selection) {
8011
- return this.#context.build(selection ?? {
8012
- messages: conversation.view(),
9523
+ #build(run, selection) {
9524
+ const rendered = selection ?? {
9525
+ messages: run.conversation.view(),
8013
9526
  judgments: []
9527
+ };
9528
+ const scope = run.snapshot;
9529
+ const disclosure = run.disclosure;
9530
+ const messages = this.#context.build({
9531
+ selection: rendered,
9532
+ ...scope === void 0 ? {} : { scope },
9533
+ ...disclosure === void 0 ? {} : { disclosure },
9534
+ ...run.switchable ? { switchable: true } : {}
8014
9535
  });
9536
+ run.record(rendered, messages.length);
9537
+ return messages;
9538
+ }
9539
+ #refresh(run, messages) {
9540
+ const added = messages.slice(run.length);
9541
+ return [...this.#build(run, run.selection), ...added];
8015
9542
  }
8016
9543
  #authorize(calls, admitted, advertised, held) {
8017
9544
  const allowed = /* @__PURE__ */ new Map();
@@ -8084,7 +9611,7 @@ var Agent = class {
8084
9611
  }
8085
9612
  return (0, _orkestrel_contract.success)(evaluated.value);
8086
9613
  }
8087
- async *#approve(tools, authorization, signal) {
9614
+ async *#approve(run, authorization, signal) {
8088
9615
  const authority = this.#authority;
8089
9616
  for (const { index, call, decision, tool } of authorization.asked) {
8090
9617
  if (signal.aborted) return;
@@ -8113,7 +9640,7 @@ var Agent = class {
8113
9640
  };
8114
9641
  const outcome = await raceSignal(answer, signal).catch(() => void 0);
8115
9642
  if (outcome === void 0 || signal.aborted) return;
8116
- if (outcome.approved && tools.tool(call.name) === tool) {
9643
+ if (outcome.approved && run.tool(call.name) === tool) {
8117
9644
  authorization.allowed.set(index, {
8118
9645
  call,
8119
9646
  tool
@@ -8125,29 +9652,29 @@ var Agent = class {
8125
9652
  }
8126
9653
  }
8127
9654
  }
8128
- #changed(tools, call, tool, advertised) {
9655
+ #changed(run, call, tool, advertised) {
8129
9656
  const same = (0, _orkestrel_contract.attempt)(() => {
8130
- const current = tools.tool(call.name);
9657
+ const current = run.tool(call.name);
8131
9658
  const definition = advertised.find((entry) => entry.name === call.name);
8132
9659
  return current === tool && current?.annotations?.consequential === true === (definition?.annotations?.consequential === true);
8133
9660
  });
8134
9661
  return !same.success || !same.value;
8135
9662
  }
8136
- #confirm(tools, authorization, advertised) {
9663
+ #confirm(run, authorization, advertised) {
8137
9664
  for (const [index, { call, tool }] of authorization.allowed) {
8138
- if (!this.#changed(tools, call, tool, advertised)) continue;
9665
+ if (!this.#changed(run, call, tool, advertised)) continue;
8139
9666
  authorization.allowed.delete(index);
8140
9667
  authorization.denials.set(index, denyCall(call, AUTHORITY_NOTES.denied));
8141
9668
  this.#emitter.emit("deny", call, `${call.name} changed after its decision`, void 0);
8142
- this.#confirm(tools, authorization, advertised);
9669
+ this.#confirm(run, authorization, advertised);
8143
9670
  return;
8144
9671
  }
8145
9672
  }
8146
- async #dispatch(tools, calls, signal, authorization, advertised) {
9673
+ async #dispatch(run, calls, signal, authorization, advertised) {
8147
9674
  const { allowed, denials } = authorization;
8148
9675
  const entries = [...allowed].sort(([left], [right]) => left - right);
8149
9676
  const started = entries.map(([, { call, tool }]) => {
8150
- if (!this.#changed(tools, call, tool, advertised)) return tools.execute(call, { signal });
9677
+ if (!this.#changed(run, call, tool, advertised)) return run.execute(call, { signal });
8151
9678
  this.#emitter.emit("deny", call, `${call.name} changed after its decision`, void 0);
8152
9679
  return denyCall(call, AUTHORITY_NOTES.denied);
8153
9680
  });
@@ -8216,6 +9743,17 @@ var Agent = class {
8216
9743
  * awaited inline, so a bounded runner frees the parent's slot for them. `createAgentRunner`
8217
9744
  * creates one instance per runner and binds its `execute` method as the runner's handler.
8218
9745
  *
9746
+ * A child job runs within its parent. Before a unit spawns or runs, on every attempt, the
9747
+ * `execute` method walks the whole tree under it through the `scanJobTree` helper and refuses the
9748
+ * unit when any job exceeds its own parent on `tools`, `builtins`, `scope`, `authority`,
9749
+ * `provider`, `scheduler`, `limit`, `timeout`, or `budget`, or when a job is malformed or its build
9750
+ * would refuse it (the lines `collectJobRefusals` returns), so nothing in a refused tree spawns, builds,
9751
+ * or runs. A child's `workspace` is free, because it renders nothing its own `scope`
9752
+ * omits. Each child is held within its parent alone, so the children of one parent can together
9753
+ * spend more than it. A job that needs another provider, another authority, a skill, memo,
9754
+ * workspace, or built-in its parent lacks, or more room than its parent is a root job the host
9755
+ * enqueues.
9756
+ *
8219
9757
  * @example
8220
9758
  * ```ts
8221
9759
  * import type { AgentJobInput, ProviderInterface } from '@orkestrel/agent'
@@ -8245,14 +9783,32 @@ var AgentFanout = class {
8245
9783
  this.#partial = partial;
8246
9784
  }
8247
9785
  /**
8248
- * Executes one unit: spawns its declared children unless an earlier attempt of the unit already
8249
- * did, then runs the rehydrated agent and settles it under the partial policy.
9786
+ * Executes one unit: refuses its tree when a job in it is malformed or exceeds its parent, spawns
9787
+ * its declared children unless an earlier attempt of the unit already did, then runs the
9788
+ * rehydrated agent and settles it under the partial policy.
9789
+ *
9790
+ * @remarks
9791
+ * The check runs on every attempt, before the unit spawns or runs, and throws synchronously. The
9792
+ * error's message opens with the rule each kind of finding breaks, a refused job's rule when a
9793
+ * finding opens with `JOB_REFUSAL_PREFIX` and the within-parent rule when a finding names an
9794
+ * exceeded member, then lists every finding, one a line. Its `context.findings` holds the same
9795
+ * findings in walk order.
8250
9796
  *
8251
9797
  * @param controller - The runner controller for this unit's attempt
8252
9798
  * @returns The settled agent result of the unit
9799
+ * @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
8253
9800
  * @throws {AgentJobError} Thrown when the run ended partial and the partial policy is `false`
8254
9801
  */
8255
9802
  execute(controller) {
9803
+ const findings = scanJobTree(controller.input);
9804
+ if (findings.length > 0) {
9805
+ const refused = findings.filter((finding) => finding.startsWith(JOB_REFUSAL_PREFIX));
9806
+ throw new AgentError("REGISTRY", [
9807
+ ...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"],
9808
+ ...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"],
9809
+ ...findings
9810
+ ].join("\n"), { context: { findings } });
9811
+ }
8256
9812
  const children = controller.input.children;
8257
9813
  if (children !== void 0 && !this.#spawned.has(controller.id)) {
8258
9814
  this.#spawned = /* @__PURE__ */ new Set([...this.#spawned, controller.id]);
@@ -8306,6 +9862,7 @@ var Authority = class {
8306
9862
  #handler;
8307
9863
  constructor(options) {
8308
9864
  this.#fallback = requireFallback(options);
9865
+ (0, _orkestrel_contract.refuseOptionKeys)(options, AUTHORITY_OPTION_KEYS, "Authority", "AuthorityOptions");
8309
9866
  this.#rules = requireRules(options.rules);
8310
9867
  this.#handler = options.ask;
8311
9868
  }
@@ -8428,16 +9985,22 @@ var Approver = class {
8428
9985
  * the message `unknown <category>: <name>` when it is absent — an unknown name in a
8429
9986
  * rehydrated job is a programmer / config error that must fail loudly at build time, never
8430
9987
  * silently resolve to `undefined` and run an agent missing a dependency.
8431
- * - **`build` rehydrates.** Resolve the job's `provider`; assemble a fresh
8432
- * {@link ToolManager} from the `tools` names, which refuses with `code: 'REGISTRY'` when the job
8433
- * names no `authority`; rebuild the token `budget` from its
8434
- * ceiling (`createTokenBudget({ max })`); resolve the optional `authority` /
9988
+ * - **`build` rehydrates.** Refuse with `code: 'REGISTRY'` a job `agentJobContract` refuses,
9989
+ * its faults under `context.faults`, and a job `collectJobRefusals` refuses, its lines under
9990
+ * `context.refusals`: tools or built-ins with no `authority`, a `builtins` list an agent
9991
+ * refuses, and a built-in the job's `tools` also name. Then resolve the job's `provider`;
9992
+ * assemble a fresh {@link ToolManager} from the `tools` names; rebuild the token `budget` from
9993
+ * its ceiling (`createTokenBudget({ max })`); resolve the optional `authority` /
8435
9994
  * `scheduler` names; construct the {@link Agent} with `system` / `limit` / `timeout` /
8436
9995
  * the threaded `signal`; seed its context with the job's `messages`; return it. The
8437
9996
  * `signal` is the queue attempt's / runner unit's cancel, so a bounded abort propagates
8438
9997
  * into the agent (which commits a partial — the job's `partial` policy then
8439
- * decides success vs. retry). A job `limit` that is not a positive safe integer throws an
8440
- * {@link AgentError} carrying `code: 'LIMIT'` from the agent's construction.
9998
+ * decides success vs. retry). A job is a record, so a malformed `limit`, `timeout`, `budget`,
9999
+ * seed role, or `builtins` list refuses as one, with `code: 'REGISTRY'`.
10000
+ * - **A job holds what it names.** A job with a `scope` gets the registry's `skills`, `memos`, and
10001
+ * `workspaces` managers, which every scoped job shares, and a scope named `job` built from its
10002
+ * three lists; a job with no `scope` gets none of them. Its `workspace` and `builtins` pass to
10003
+ * the agent.
8441
10004
  * - **Event-free.** A pure resolver — no Emitter, no events.
8442
10005
  *
8443
10006
  * @example
@@ -8454,12 +10017,19 @@ var AgentRegistry = class {
8454
10017
  #authorities;
8455
10018
  #schedulers;
8456
10019
  #store;
10020
+ #skills;
10021
+ #memos;
10022
+ #workspaces;
8457
10023
  constructor(options) {
10024
+ (0, _orkestrel_contract.refuseOptionKeys)(options, REGISTRY_OPTION_KEYS, "AgentRegistry", "AgentRegistryOptions");
8458
10025
  this.#providers = new Map(Object.entries(options.providers));
8459
10026
  this.#tools = new Map(Object.entries(options.tools ?? {}));
8460
10027
  this.#authorities = new Map(Object.entries(options.authorities ?? {}));
8461
10028
  this.#schedulers = new Map(Object.entries(options.schedulers ?? {}));
8462
10029
  this.#store = options.store;
10030
+ this.#skills = options.skills;
10031
+ this.#memos = options.memos;
10032
+ this.#workspaces = options.workspaces;
8463
10033
  }
8464
10034
  provider(name) {
8465
10035
  return requireEntry(this.#providers, "provider", name);
@@ -8476,13 +10046,16 @@ var AgentRegistry = class {
8476
10046
  build(input, signal) {
8477
10047
  const faults = agentJobContract.audit(input);
8478
10048
  if (faults.length > 0) throw new AgentError("REGISTRY", `a job must hold only the declared members of AgentJobInput\n${(0, _orkestrel_contract.describeFaults)(faults, agentJobContract.schema)}`, { context: { faults } });
10049
+ const refusals = collectJobRefusals(input);
10050
+ 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 } });
8479
10051
  const agent = new Agent(this.provider(input.provider), this.#buildOptions(input, signal));
8480
10052
  for (const message of input.messages ?? []) agent.context.messages.add(message);
8481
10053
  return agent;
8482
10054
  }
8483
10055
  #buildOptions(input, signal) {
8484
10056
  const names = input.tools ?? [];
8485
- 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 } });
10057
+ const builtins = input.builtins ?? [];
10058
+ const scope = input.scope;
8486
10059
  const budget = input.budget === void 0 ? void 0 : (0, _orkestrel_budget.createTokenBudget)({ max: input.budget });
8487
10060
  return {
8488
10061
  ...names.length === 0 ? {} : { tools: this.#buildToolManager(names) },
@@ -8493,6 +10066,15 @@ var AgentRegistry = class {
8493
10066
  ...input.authority === void 0 ? {} : { authority: this.authority(input.authority) },
8494
10067
  ...input.scheduler === void 0 ? {} : { scheduler: this.scheduler(input.scheduler) },
8495
10068
  ...this.#store === void 0 ? {} : { conversations: new ConversationManager({ store: this.#store }) },
10069
+ ...scope === void 0 || this.#skills === void 0 ? {} : { skills: this.#skills },
10070
+ ...scope === void 0 || this.#memos === void 0 ? {} : { memos: this.#memos },
10071
+ ...scope === void 0 || this.#workspaces === void 0 ? {} : { workspaces: this.#workspaces },
10072
+ ...scope === void 0 ? {} : { scope: createScope({
10073
+ name: "job",
10074
+ ...scope
10075
+ }) },
10076
+ ...input.workspace === void 0 ? {} : { workspace: input.workspace },
10077
+ ...builtins.length === 0 ? {} : { builtins },
8496
10078
  ...signal === void 0 ? {} : { signal }
8497
10079
  };
8498
10080
  }
@@ -8727,6 +10309,7 @@ function createAgentRegistry(options) {
8727
10309
  * ```
8728
10310
  */
8729
10311
  function createAgentQueue(options) {
10312
+ (0, _orkestrel_contract.refuseOptionKeys)(options, QUEUE_OPTION_KEYS, "createAgentQueue", "AgentQueueOptions");
8730
10313
  const { registry, partial = false, store } = options;
8731
10314
  return (0, _orkestrel_queue.createQueue)({
8732
10315
  ...extractQueueOptions(options),
@@ -8773,6 +10356,7 @@ function createAgentQueue(options) {
8773
10356
  * ```
8774
10357
  */
8775
10358
  function createAgentRunner(options) {
10359
+ (0, _orkestrel_contract.refuseOptionKeys)(options, RUNNER_OPTION_KEYS, "createAgentRunner", "AgentRunnerOptions");
8776
10360
  const { registry, partial = false } = options;
8777
10361
  const fanout = new AgentFanout(registry, partial);
8778
10362
  return (0, _orkestrel_workflow.createRunner)({
@@ -8780,6 +10364,38 @@ function createAgentRunner(options) {
8780
10364
  handler: fanout.execute.bind(fanout)
8781
10365
  });
8782
10366
  }
10367
+ /**
10368
+ * Creates the tool a run serves for one built-in: its description and annotations from
10369
+ * `BUILTIN_TOOLS`, its argument shape from `builtinToolContracts`, and the given handler.
10370
+ *
10371
+ * @remarks
10372
+ * The tool's door refuses a missing, empty, over-long, or extra argument before the handler runs,
10373
+ * and repairs a value that converts to a string without loss, such as a number, so the handler
10374
+ * reads only arguments the shape admits. Its definition equals the one `collectBuiltinDefinitions`
10375
+ * returns for the same name.
10376
+ *
10377
+ * @param name - The built-in to create
10378
+ * @param execute - The handler the door hands admitted arguments to
10379
+ * @returns The tool
10380
+ *
10381
+ * @example
10382
+ * ```ts
10383
+ * import { createBuiltinTool } from '@orkestrel/agent'
10384
+ *
10385
+ * const memo = createBuiltinTool('memo', (args) => `read ${String(args.name)}`)
10386
+ * memo.annotations // { pure: true, untrusted: true }
10387
+ * ```
10388
+ */
10389
+ function createBuiltinTool(name, execute) {
10390
+ const declared = BUILTIN_TOOLS[name];
10391
+ return (0, _orkestrel_tool.createTool)({
10392
+ name,
10393
+ description: declared.description,
10394
+ ...declared.annotations === void 0 ? {} : { annotations: declared.annotations },
10395
+ contract: builtinToolContracts[name].shape,
10396
+ execute
10397
+ });
10398
+ }
8783
10399
  // src/core/ledgers/constants.ts
8784
10400
  /**
8785
10401
  * Lists the categories the ledger files a message under, in the order the category question
@@ -8854,19 +10470,25 @@ var LEDGER_QUESTIONS = Object.freeze({
8854
10470
  /**
8855
10471
  * Supplies the text of each ledger note, worded for a model that answers in its final
8856
10472
  * message.
10473
+ *
10474
+ * @remarks
10475
+ * `overflow` names `unload` because a skill the thread shows is the room a model can give back: a
10476
+ * successful `unload` reopens every call the room denied in that request.
8857
10477
  */
8858
10478
  var LEDGER_NOTES = Object.freeze({
8859
10479
  cue: "Give your complete answer to the request now, from what you already have.",
8860
10480
  results: "Your lookups and recalls in this request returned:",
8861
10481
  repeat: "You already have this result earlier in this request; give your complete answer now as your final message.",
8862
- closure: "recall is closed for the rest of this request; give your final answer from what you have"
10482
+ closure: "recall is closed for the rest of this request; give your final answer from what you have",
10483
+ 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."
8863
10484
  });
8864
10485
  /**
8865
- * Supplies the default prompt and tail shares.
10486
+ * Supplies the default prompt, tail, and skills shares.
8866
10487
  */
8867
10488
  var DEFAULT_LEDGER_SHARE = Object.freeze({
8868
10489
  prompt: .7,
8869
- tail: .35
10490
+ tail: .35,
10491
+ skills: .5
8870
10492
  });
8871
10493
  /**
8872
10494
  * Caps the tool-iteration turns of a ledger's agent at 8 turns.
@@ -8903,6 +10525,85 @@ var LEDGER_SCALE_DRIFT = .06;
8903
10525
  var LEDGER_WORD = /[\p{L}\p{N}]+/gu;
8904
10526
  /** Matches the logprob decoding failure `invalid or duplicate top logprob token`, which the ledger holds for the ledger's life. */
8905
10527
  var DETERMINISTIC_JUDGE_ERROR = /invalid or duplicate top logprob token/;
10528
+ /**
10529
+ * Lists the members `LedgerOptions` declares, frozen — the keys the `Ledger` constructor admits.
10530
+ *
10531
+ * @remarks
10532
+ * The constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
10533
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
10534
+ */
10535
+ var LEDGER_OPTION_KEYS = Object.freeze([
10536
+ "judge",
10537
+ "system",
10538
+ "topics",
10539
+ "questions",
10540
+ "thresholds",
10541
+ "capacity",
10542
+ "predict",
10543
+ "think",
10544
+ "gauge",
10545
+ "lookups",
10546
+ "writes",
10547
+ "share",
10548
+ "recall",
10549
+ "correction",
10550
+ "agent",
10551
+ "snapshot"
10552
+ ]);
10553
+ /**
10554
+ * Lists the members the `LedgerAgentOptions` pick declares, frozen — the keys a ledger's `agent`
10555
+ * option admits.
10556
+ *
10557
+ * @remarks
10558
+ * The `Ledger` constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an
10559
+ * own enumerable member outside it, an `AgentOptions` member the pick leaves out included, refuses
10560
+ * with a `ContractError` coded `'structure'`.
10561
+ */
10562
+ var LEDGER_AGENT_KEYS = Object.freeze([
10563
+ "limit",
10564
+ "timeout",
10565
+ "on",
10566
+ "scheduler",
10567
+ "instructions",
10568
+ "skills",
10569
+ "memos",
10570
+ "workspaces",
10571
+ "strict",
10572
+ "authority",
10573
+ "builtins",
10574
+ "workspace"
10575
+ ]);
10576
+ /** Lists the members `ClassifierOptions` declares, frozen — the keys the `Classifier` constructor admits. */
10577
+ var CLASSIFIER_OPTION_KEYS = Object.freeze([
10578
+ "strict",
10579
+ "conversation",
10580
+ "judge",
10581
+ "questions",
10582
+ "topics",
10583
+ "thresholds",
10584
+ "assign",
10585
+ "entities",
10586
+ "correction",
10587
+ "scheduler"
10588
+ ]);
10589
+ /** Lists the members `GaugeOptions` declares, frozen — the keys the `Gauge` constructor admits. */
10590
+ var GAUGE_OPTION_KEYS = Object.freeze([
10591
+ "scale",
10592
+ "overhead",
10593
+ "capacity",
10594
+ "predict",
10595
+ "replay"
10596
+ ]);
10597
+ /**
10598
+ * Sets the 3 UTF-8 bytes one UTF-16 code unit encodes to at most, the divisor that sizes the
10599
+ * workspace text a calibration's third call carries.
10600
+ *
10601
+ * @remarks
10602
+ * A tokenizer that falls back to bytes charges at most one token a byte, so workspace text cut to the
10603
+ * capacity the call without definitions left, divided by this figure, in characters, reports at most
10604
+ * `capacity` apart from a template's few tokens for an added message.
10605
+ */
10606
+ var LEDGER_SAMPLE_BYTES = 3;
8906
10607
  // src/core/ledgers/errors.ts
8907
10608
  /**
8908
10609
  * Reports an invalid ledger configuration, failed calibration, or unowned request selection,
@@ -8911,7 +10612,9 @@ var DETERMINISTIC_JUDGE_ERROR = /invalid or duplicate top logprob token/;
8911
10612
  * @remarks
8912
10613
  * A threshold, share, capacity, limit, topic, lookup, write, question, or gauge outside its bounds is a
8913
10614
  * programmer error and throws this with the matching {@link LedgerErrorCode}. A calibration call that reports
8914
- * no prompt usage throws this with `'GAUGE'`. A snapshot option that `isLedgerSnapshot` refuses
10615
+ * no prompt usage, or a calibration call, with the tool definitions, without them, or with the workspace text, that reports a prompt
10616
+ * over `capacity`, the head included, throws this with `'GAUGE'`; the second holds `capacity` and
10617
+ * `prompt` in its `context`. A snapshot option that `isLedgerSnapshot` refuses
8915
10618
  * throws this with `'SNAPSHOT'`, its `context` holding the `faults`. Narrow a caught value with {@link isLedgerError} and
8916
10619
  * branch on `error.code`. A selection without an active `respond` call, for a conversation other
8917
10620
  * than the ledger's, or for a request other than that call's request or a ledger note, reports
@@ -8953,7 +10656,7 @@ function isLedgerError(value) {
8953
10656
  return (0, _orkestrel_contract.holds)(() => {
8954
10657
  if (!(value instanceof Error && _orkestrel_contract.INTRINSICS.prototype(value) !== Error.prototype && _orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/agent.LedgerError"))?.value === value)) return false;
8955
10658
  const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
8956
- 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;
10659
+ 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;
8957
10660
  const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
8958
10661
  return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
8959
10662
  });
@@ -8989,9 +10692,10 @@ function digestLedgerWindow(exchanges, messages) {
8989
10692
  * Digests tool definitions as the JSON a provider request carries them, in order.
8990
10693
  *
8991
10694
  * @remarks
8992
- * A ledger stores this digest with its gauge, so a hydrate reuses the gauge only for the same
8993
- * definitions: an added, removed, reordered, or edited definition changes the digest. The digest is
8994
- * a {@link computeLedgerChecksum} checksum and makes no security claim.
10695
+ * A ledger stores this digest with its gauge and compares it at a hydrate and at each `respond`
10696
+ * call, so it reuses the gauge only for the same definitions: an added, removed, reordered, or
10697
+ * edited definition changes the digest. The digest is a {@link computeLedgerChecksum} checksum and
10698
+ * makes no security claim.
8995
10699
  *
8996
10700
  * @param definitions - The tool definitions, as a tool manager's `definitions` method returns them
8997
10701
  * @returns The 16-digit hexadecimal digest, or undefined when JSON cannot render the definitions
@@ -9005,6 +10709,82 @@ function digestLedgerTools(definitions) {
9005
10709
  return text === void 0 ? void 0 : computeLedgerChecksum(text);
9006
10710
  }
9007
10711
  /**
10712
+ * Computes the tokens one estimate unit of a definition costs beyond the calibrated set.
10713
+ *
10714
+ * @remarks
10715
+ * The larger of `scale` and the calibrated set's overhead per estimate unit, or `scale` for an
10716
+ * empty set or one that estimates 0. A set's estimate is `estimateTokens` over its JSON text.
10717
+ *
10718
+ * @param calibrated - The definitions the gauge's overhead priced
10719
+ * @param gauge - The gauge's scale and overhead
10720
+ * @returns The tokens one estimate unit of a definition costs
10721
+ * @example
10722
+ * ```ts
10723
+ * computeToolDensity([], { scale: 1.1, overhead: 400 }) // 1.1
10724
+ * ```
10725
+ */
10726
+ function computeToolDensity(calibrated, gauge) {
10727
+ const estimate = calibrated.length === 0 ? 0 : estimateTokens(JSON.stringify(calibrated));
10728
+ return estimate > 0 ? Math.max(gauge.scale, gauge.overhead / estimate) : gauge.scale;
10729
+ }
10730
+ /**
10731
+ * Computes what a call's advertised set spares of the calibrated overhead and adds beyond it.
10732
+ *
10733
+ * @remarks
10734
+ * Definitions compare by their JSON text, so a definition replaced under its name by another text
10735
+ * is both left out and added. An empty advertised set spares the whole overhead. Otherwise the
10736
+ * calibrated definitions the call leaves out spare the lower of `scale` and the calibrated set's
10737
+ * overhead per estimate unit, times their estimate, so a credit never exceeds their proportional
10738
+ * share of the overhead; the definitions it advertises beyond the calibrated set add
10739
+ * {@link computeToolDensity}, the higher of the two, times theirs. A set's estimate is
10740
+ * `estimateTokens` over its JSON text, 0 for an empty set. The call's price is
10741
+ * `overhead - spared + added`.
10742
+ *
10743
+ * @param calibrated - The definitions the gauge's overhead priced
10744
+ * @param advertised - The definitions the call advertises
10745
+ * @param gauge - The gauge's scale and overhead
10746
+ * @returns The tokens the call spares and adds
10747
+ * @example
10748
+ * ```ts
10749
+ * computeToolPrice([], [], { scale: 1.1, overhead: 400 }) // { spared: 400, added: 0 }
10750
+ * ```
10751
+ */
10752
+ function computeToolPrice(calibrated, advertised, gauge) {
10753
+ if (advertised.length === 0) return {
10754
+ spared: gauge.overhead,
10755
+ added: 0
10756
+ };
10757
+ const sent = new Set(advertised.map((definition) => JSON.stringify(definition)));
10758
+ const priced = new Set(calibrated.map((definition) => JSON.stringify(definition)));
10759
+ const hidden = calibrated.filter((definition) => !sent.has(JSON.stringify(definition)));
10760
+ const extra = advertised.filter((definition) => !priced.has(JSON.stringify(definition)));
10761
+ return {
10762
+ spared: hidden.length === 0 ? 0 : Math.min(gauge.scale, gauge.overhead / estimateTokens(JSON.stringify(calibrated))) * estimateTokens(JSON.stringify(hidden)),
10763
+ added: extra.length === 0 ? 0 : computeToolDensity(calibrated, gauge) * estimateTokens(JSON.stringify(extra))
10764
+ };
10765
+ }
10766
+ /**
10767
+ * Computes the tokens loading a skill adds: its body, and the definitions it names beyond an advertised set.
10768
+ *
10769
+ * @remarks
10770
+ * The body is the text `renderSkillBody` renders, priced at `scale`, and a blank `content` renders
10771
+ * no body. The caller passes the definitions the skill names that the turn does not advertise,
10772
+ * priced as {@link computeToolPrice} prices an addition.
10773
+ *
10774
+ * @param skill - The skill to load
10775
+ * @param added - The definitions the skill names that the advertised set leaves out
10776
+ * @param calibrated - The definitions the gauge's overhead priced
10777
+ * @param gauge - The gauge's scale and overhead
10778
+ * @returns The tokens the load adds
10779
+ * @example
10780
+ * ```ts
10781
+ * computeSkillPrice({ name: 'refunds', content: 'Check the order first.' }, [], [], { scale: 1, overhead: 0 }) // 9
10782
+ * ```
10783
+ */
10784
+ function computeSkillPrice(skill, added, calibrated, gauge) {
10785
+ return (skill.content.trim() === "" ? 0 : gauge.scale * estimateTokens(renderSkillBody(skill))) + computeToolPrice(calibrated, [...calibrated, ...added], gauge).added;
10786
+ }
10787
+ /**
9008
10788
  * Computes a checksum of text: two 32-bit FNV-1a lanes over its code points, as 16 hexadecimal digits.
9009
10789
  *
9010
10790
  * @remarks
@@ -9233,23 +11013,6 @@ function splitWords(part) {
9233
11013
  return part.split(/\s+/).map((word) => word.replace(/^[\p{P}\p{S}]+|[\p{P}\p{S}]+$/gu, "").toLowerCase()).filter((word) => word !== "");
9234
11014
  }
9235
11015
  /**
9236
- * Finds the call that owns a tool message within a collected tool group.
9237
- * @param group - The assistant leader followed by its tool results
9238
- * @param message - The tool result to pair; distinct leader ids pair by result id when any result has one
9239
- * @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
9240
- * @example
9241
- * ```ts
9242
- * findLedgerCall(group, result)?.arguments
9243
- * ```
9244
- */
9245
- function findLedgerCall(group, message) {
9246
- const [leader, ...results] = group;
9247
- const at = results.findIndex((result) => result.id === message.id);
9248
- if (message.role !== "tool" || at < 0) return void 0;
9249
- const calls = leader?.calls ?? [];
9250
- 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];
9251
- }
9252
- /**
9253
11016
  * Ranks a source the records render for removal before the next prompt.
9254
11017
  *
9255
11018
  * @param group - The planning group: 1 for a topic match, 2 for an off-topic rule or correction, or 3 for a name match
@@ -9943,23 +11706,34 @@ function matchesCutLine(line) {
9943
11706
  /**
9944
11707
  * Renders the tail stub of a lookup result from an earlier request.
9945
11708
  *
11709
+ * @remarks
11710
+ * A `hidden` stub names the `recall` call that finds its result, with the first string argument,
11711
+ * trimmed, or `its id` when no argument is a string or that one is blank, only while `offered` is `true`. The ledger plans the
11712
+ * tail with every stub naming `recall`, then renders the tail's stubs again after it admits a window
11713
+ * rung, with `offered` set to whether that rung offers `recall` and the scope admits it, so the
11714
+ * second render can only shorten a stub and raises no price. `offered` changes no other state.
11715
+ *
9946
11716
  * @param name - The lookup tool name
9947
11717
  * @param args - The arguments the call carried; arguments JSON cannot carry leave the call as its bare tool name, per {@link renderLookupCall}
9948
11718
  * @param state - What became of the result: `failed`, `empty`, `shown` in the records, or `hidden` from them
9949
11719
  * @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`
11720
+ * @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`
9950
11721
  * @returns The stub text, which carries the call and its state
9951
11722
  *
9952
11723
  * @example
9953
11724
  * ```ts
9954
11725
  * renderStub('lookup_order', { id: 'BW-5512' }, 'hidden')
9955
11726
  * // 'lookup_order {"id":"BW-5512"}: result not shown; call recall with BW-5512'
11727
+ * renderStub('lookup_order', { id: 'BW-5512' }, 'hidden', false, false)
11728
+ * // 'lookup_order {"id":"BW-5512"}: result not shown'
9956
11729
  * ```
9957
11730
  */
9958
- function renderStub(name, args, state, write = false) {
11731
+ function renderStub(name, args, state, write = false, offered = true) {
9959
11732
  const head = renderLookupCall(name, args);
9960
11733
  if (state === "failed") return `${head}: failed`;
9961
11734
  if (state === "empty") return write ? `${head}: changed nothing` : `${head}: no record`;
9962
11735
  if (state === "shown") return `${head}: result shown under Records below`;
11736
+ if (!offered) return `${head}: result not shown`;
9963
11737
  return `${head}: result not shown; call recall with ${(Object.values(args).find(_orkestrel_contract.isString) ?? "").trim() || "its id"}`;
9964
11738
  }
9965
11739
  /**
@@ -10329,6 +12103,51 @@ function collectLedgerEvidence(messages, sources, amendments) {
10329
12103
  ]), { ...edge }]))
10330
12104
  };
10331
12105
  }
12106
+ /**
12107
+ * Cuts the workspace text a calibration sample adds over its head to a number of characters, or
12108
+ * returns `undefined` when the sample adds anything else.
12109
+ *
12110
+ * @remarks
12111
+ * The head and the shown sample are what a context's `build` returns for an empty turn without and
12112
+ * with the workspace: the shown sample holds the head's messages with the workspace section appended
12113
+ * to the context message, or one context message added where the head holds none. The cut keeps the
12114
+ * first `limit` characters of that added text and every other message as the sample holds it. A
12115
+ * sample whose messages differ from the head's in any other way, or that adds no text, returns
12116
+ * `undefined`.
12117
+ *
12118
+ * @param head - The head built with no workspace
12119
+ * @param shown - The head built with the workspace a request renders
12120
+ * @param limit - The characters of added text to keep
12121
+ * @returns The cut sample, or `undefined` when the sample adds no trailing text to one message
12122
+ *
12123
+ * @example
12124
+ * ```ts
12125
+ * import { cutWorkspaceSample } from '@orkestrel/agent'
12126
+ *
12127
+ * const head = [{ id: 's', role: 'system', content: 'Serve the desk.' }] as const
12128
+ * const shown = [...head, { id: 'c', role: 'user', content: '## Workspace\n\nFile: a.md' }] as const
12129
+ * cutWorkspaceSample(head, shown, 12)?.[1]?.content // '## Workspace'
12130
+ * ```
12131
+ */
12132
+ function cutWorkspaceSample(head, shown, limit) {
12133
+ const at = shown.findIndex((message, index) => message.role !== head[index]?.role || message.content !== head[index]?.content);
12134
+ const added = shown[at];
12135
+ const extended = shown.length === head.length ? head[at] : void 0;
12136
+ const prefix = extended?.content ?? "";
12137
+ const rest = shown.slice(at + 1);
12138
+ const held = head.slice(extended === void 0 ? at : at + 1);
12139
+ if (added === void 0 || shown.length - head.length > 1 || shown.length < head.length) return void 0;
12140
+ if (!added.content.startsWith(prefix) || added.content.length === prefix.length) return void 0;
12141
+ if (rest.length !== held.length || rest.some((message, index) => message.role !== held[index]?.role || message.content !== held[index]?.content)) return void 0;
12142
+ return [
12143
+ ...shown.slice(0, at),
12144
+ {
12145
+ ...added,
12146
+ content: prefix + added.content.slice(prefix.length, prefix.length + limit)
12147
+ },
12148
+ ...rest
12149
+ ];
12150
+ }
10332
12151
  // src/core/ledgers/Classifier.ts
10333
12152
  /**
10334
12153
  * Files messages through the conversation's judgment manager and reads their categories and corrections.
@@ -10349,6 +12168,7 @@ var Classifier = class {
10349
12168
  * @param options - The conversation, judge, questions, cutoffs, and handlers
10350
12169
  */
10351
12170
  constructor(options) {
12171
+ (0, _orkestrel_contract.refuseOptionKeys)(options, CLASSIFIER_OPTION_KEYS, "Classifier", "ClassifierOptions");
10352
12172
  this.#options = options;
10353
12173
  }
10354
12174
  /**
@@ -10725,6 +12545,7 @@ var Gauge = class {
10725
12545
  * @throws {LedgerError} Thrown when `predict` is not a nonnegative safe integer less than `capacity` (code `'CAPACITY'`)
10726
12546
  */
10727
12547
  constructor(options) {
12548
+ (0, _orkestrel_contract.refuseOptionKeys)(options, GAUGE_OPTION_KEYS, "Gauge", "GaugeOptions");
10728
12549
  if (!(0, _orkestrel_contract.isFiniteNumber)(options.scale) || options.scale <= 0) throw new LedgerError("GAUGE", "gauge scale must be finite and greater than 0");
10729
12550
  if (!(0, _orkestrel_contract.isFiniteNumber)(options.overhead) || options.overhead < 0) throw new LedgerError("GAUGE", "gauge overhead must be finite and at least 0");
10730
12551
  if (!isPositiveSafeInteger(options.capacity)) throw new LedgerError("CAPACITY", "gauge capacity must be a positive safe integer");
@@ -10755,7 +12576,7 @@ var Gauge = class {
10755
12576
  }
10756
12577
  remainder(calls) {
10757
12578
  const call = calls.at(-1);
10758
- const used = (0, _orkestrel_contract.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);
12579
+ const used = (0, _orkestrel_contract.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);
10759
12580
  return Math.max(0, this.#capacity - used);
10760
12581
  }
10761
12582
  reserve(calls, longest) {
@@ -10775,7 +12596,7 @@ var Gauge = class {
10775
12596
  }
10776
12597
  observe(calls, reply) {
10777
12598
  const first = calls[0];
10778
- if (first !== void 0 && (0, _orkestrel_contract.isFiniteNumber)(first.prompt) && first.estimate > 0 && ((first.spared ?? 0) === 0 || (first.spared ?? 0) >= this.#overhead)) {
12599
+ if (first !== void 0 && (0, _orkestrel_contract.isFiniteNumber)(first.prompt) && first.estimate > 0 && (first.added ?? 0) === 0 && ((first.spared ?? 0) === 0 || (first.spared ?? 0) >= this.#overhead)) {
10779
12600
  const priced = first.prompt - Math.max(0, this.#overhead - (first.spared ?? 0));
10780
12601
  if (priced > 0) this.#scale = priced / first.estimate;
10781
12602
  }
@@ -10983,6 +12804,7 @@ var LedgerRequest = class {
10983
12804
  #admitted = /* @__PURE__ */ new Set();
10984
12805
  #denials = /* @__PURE__ */ new Set();
10985
12806
  #listed = [];
12807
+ #commits = [];
10986
12808
  #recalls = 0;
10987
12809
  #closed = false;
10988
12810
  /**
@@ -11169,6 +12991,23 @@ var LedgerRequest = class {
11169
12991
  lines(result) {
11170
12992
  return this.#listed.findLast((listing) => listing.result === result)?.lines;
11171
12993
  }
12994
+ commit(price, skill) {
12995
+ this.#commits = [...this.#commits, {
12996
+ price,
12997
+ turn: this.calls.length,
12998
+ ...skill === void 0 ? {} : { skill }
12999
+ }];
13000
+ }
13001
+ get committed() {
13002
+ return this.#inflight().reduce((sum, entry) => sum + entry.price, 0);
13003
+ }
13004
+ get loads() {
13005
+ return this.#inflight().flatMap((entry) => entry.skill === void 0 ? [] : [entry.skill]);
13006
+ }
13007
+ #inflight() {
13008
+ const turn = this.calls.length;
13009
+ return this.#commits.filter((entry) => entry.turn === turn);
13010
+ }
11172
13011
  };
11173
13012
  // src/core/ledgers/LedgerScan.ts
11174
13013
  /**
@@ -11446,16 +13285,20 @@ var Ledger = class {
11446
13285
  #share;
11447
13286
  #conversation;
11448
13287
  #agent;
13288
+ #record;
13289
+ #builtins;
13290
+ #given;
11449
13291
  #classifier;
11450
13292
  #requests = /* @__PURE__ */ new Set();
11451
13293
  #annotations = /* @__PURE__ */ new Set();
11452
13294
  #results = /* @__PURE__ */ new Map();
11453
13295
  #pending = /* @__PURE__ */ new Map();
11454
- #live = /* @__PURE__ */ new Set();
13296
+ #live = /* @__PURE__ */ new Map();
11455
13297
  #scans = /* @__PURE__ */ new Set();
11456
13298
  #calibrations = /* @__PURE__ */ new Set();
11457
13299
  #gauge;
11458
- #digest;
13300
+ #measured;
13301
+ #refusal;
11459
13302
  #window = [];
11460
13303
  #evidence = collectLedgerEvidence([], /* @__PURE__ */ new Set(), []);
11461
13304
  /**
@@ -11470,6 +13313,8 @@ var Ledger = class {
11470
13313
  * @throws {ConversationError} Thrown when `isLedgerSnapshot` admits `snapshot` but its conversation holds a `system` message (code `'ROLE'`) or repeats an id (code `'SNAPSHOT'`)
11471
13314
  */
11472
13315
  constructor(provider, options) {
13316
+ (0, _orkestrel_contract.refuseOptionKeys)(options, LEDGER_OPTION_KEYS, "Ledger", "LedgerOptions");
13317
+ (0, _orkestrel_contract.refuseOptionKeys)(options.agent, LEDGER_AGENT_KEYS, "Ledger agent", "LedgerAgentOptions");
11473
13318
  for (const key of [
11474
13319
  "category",
11475
13320
  "topic",
@@ -11547,6 +13392,9 @@ var Ledger = class {
11547
13392
  }) }),
11548
13393
  execute: (args) => this.#recall(args)
11549
13394
  }));
13395
+ this.#builtins = requireBuiltins(options.agent?.builtins, tools);
13396
+ 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");
13397
+ this.#given = requireWorkspace(options.agent?.workspace);
11550
13398
  const advertised = new Authority({
11551
13399
  rules: [{
11552
13400
  match: (context) => names.includes(context.call.name),
@@ -11565,17 +13413,14 @@ var Ledger = class {
11565
13413
  system: options.system,
11566
13414
  tools,
11567
13415
  authority: {
11568
- evaluate: (context) => this.#current()?.denied(context.call.name, context.call.arguments) === true ? {
11569
- zone: "ledger",
11570
- verdict: "deny",
11571
- reason: LEDGER_NOTES.repeat
11572
- } : host === void 0 || context.call.name === "recall" || !names.includes(context.call.name) ? advertised.evaluate(context) : host.evaluate(context),
13416
+ evaluate: (context) => this.#evaluate(context, host, advertised, names),
11573
13417
  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)
11574
13418
  },
11575
13419
  limit: options.agent?.limit ?? 8,
11576
13420
  select: (conversation, request, signal) => this.#select(conversation, request, signal)
11577
13421
  });
11578
- const digest = digestLedgerTools(this.#agent.context.tools.definitions());
13422
+ const calibrated = this.#calibrated();
13423
+ const digest = digestLedgerTools(calibrated);
11579
13424
  const gauge = options.gauge ?? (snapshot?.gauge?.digest === digest ? snapshot?.gauge : void 0);
11580
13425
  if (gauge !== void 0) {
11581
13426
  this.#gauge = new Gauge({
@@ -11585,10 +13430,10 @@ var Ledger = class {
11585
13430
  predict: this.#predict,
11586
13431
  replay: this.#replay
11587
13432
  });
11588
- this.#digest = digest;
13433
+ this.#measured = calibrated;
11589
13434
  }
11590
13435
  this.#agent.emitter.on("deny", (call, _reason, decision) => {
11591
- if (decision !== void 0 && call.name !== "recall" && names.includes(call.name)) this.#current()?.deny(call.name, call.arguments);
13436
+ 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);
11592
13437
  });
11593
13438
  this.#agent.emitter.on("tool", (call, result) => {
11594
13439
  this.#flush();
@@ -11601,21 +13446,34 @@ var Ledger = class {
11601
13446
  const request = this.#current();
11602
13447
  if (request?.calls.at(-1)?.tools === 0) request.abort();
11603
13448
  this.#release(call, result);
13449
+ this.#reopen(call, result);
11604
13450
  });
11605
13451
  this.#agent.emitter.on("select", (selection) => {
11606
13452
  if (isLedgerError(selection.fault) && selection.fault.code === "REQUEST") return;
11607
13453
  this.#current()?.adopt(selection, this.#conversation.messages().length);
11608
13454
  });
11609
13455
  this.#agent.emitter.on("fault", (error) => this.#current()?.fault(error));
11610
- this.#agent.emitter.on("turn", () => this.#observeTurn());
13456
+ this.#agent.emitter.on("request", (index, sent) => {
13457
+ if (this.#gate(index, sent)) this.#observeRequest(sent);
13458
+ });
11611
13459
  this.#agent.emitter.on("usage", (usage) => this.#current()?.spend(usage));
13460
+ const agent = this.#agent;
13461
+ this.#record = Object.freeze({
13462
+ emitter: agent.emitter,
13463
+ id: agent.id,
13464
+ get status() {
13465
+ return agent.status;
13466
+ },
13467
+ context: agent.context,
13468
+ abort: (reason) => agent.abort(reason)
13469
+ });
11612
13470
  }
11613
13471
  /**
11614
- * Returns the owned request engine.
11615
- * @returns The engine serving this conversation
13472
+ * Returns the record of the owned request engine, which starts no run.
13473
+ * @returns The frozen record of the agent serving this conversation
11616
13474
  */
11617
13475
  get agent() {
11618
- return this.#agent;
13476
+ return this.#record;
11619
13477
  }
11620
13478
  /**
11621
13479
  * Returns the owned message history.
@@ -11636,9 +13494,10 @@ var Ledger = class {
11636
13494
  }
11637
13495
  /**
11638
13496
  * Measures message scale and tool overhead from provider prompt usage.
11639
- * @param signal - The signal bounding both calibration calls; an abort rejects with its reason
13497
+ * @param signal - The signal bounding every calibration call; an abort rejects with its reason
11640
13498
  * @returns The stored scale and overhead cost
11641
- * @throws {LedgerError} Thrown when either call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
13499
+ * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
13500
+ * @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'`)
11642
13501
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
11643
13502
  */
11644
13503
  async calibrate(signal) {
@@ -11661,7 +13520,7 @@ var Ledger = class {
11661
13520
  const conversation = this.#conversation.snapshot();
11662
13521
  const ids = conversation.messages.map((message) => message.id);
11663
13522
  const gauge = this.gauge;
11664
- const priced = this.#digest;
13523
+ const priced = this.#digest();
11665
13524
  const window = {
11666
13525
  exchanges: this.#window.map((entry) => ({
11667
13526
  request: entry.request.id,
@@ -11695,13 +13554,15 @@ var Ledger = class {
11695
13554
  /**
11696
13555
  * Appends a request and serves it, recovering an unfinished or rejected first pass with one answer pass.
11697
13556
  * 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.
11698
- * 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.
13557
+ * 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.
11699
13558
  * 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.
11700
13559
  * @param content - The request text
11701
13560
  * @param signal - The caller's cancellation signal; an abort during calibration rejects with its reason
11702
13561
  * @returns The final pass, the usage of every pass, and the filing attempts of this request alone
11703
13562
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
11704
13563
  * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
13564
+ * @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'`)
13565
+ * @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'`)
11705
13566
  * @throws {ContractError} Thrown when `signal` is defined and is not a native `AbortSignal`
11706
13567
  */
11707
13568
  async respond(content, signal) {
@@ -11709,12 +13570,15 @@ var Ledger = class {
11709
13570
  const caller = (0, _orkestrel_abort.createAbort)({ ...signal === void 0 ? {} : { signal } }).signal;
11710
13571
  const request = new LedgerRequest(content, this.#options.recall?.limit);
11711
13572
  const scope = this.#agent.context.scope;
11712
- this.#live.add(request);
13573
+ this.#live.set(request, scope);
11713
13574
  try {
11714
- if (this.#gauge === void 0) await this.#measureGauge(caller);
13575
+ if (this.#gauge === void 0 || digestLedgerTools(this.#calibrated()) !== this.#digest()) {
13576
+ this.#gauge = void 0;
13577
+ await this.#measureGauge(caller);
13578
+ }
11715
13579
  const entered = request.enter(this.#conversation);
11716
13580
  this.#requests.add(entered.id);
11717
- const first = this.#dropStrayCalls(request, await this.#generatePass(request, caller, this.#options.think).catch((error) => {
13581
+ const first = this.#dropStrayCalls(request, await this.#generatePass(request, caller, this.#options.think, scope).catch((error) => {
11718
13582
  if (request.faulted(error)) throw error;
11719
13583
  return {
11720
13584
  content: "",
@@ -11733,13 +13597,12 @@ var Ledger = class {
11733
13597
  role: "user",
11734
13598
  content: LEDGER_NOTES.cue
11735
13599
  }).id);
11736
- const previous = this.#agent.context.scope;
13600
+ const filter = intersectFilters(scope, { tools: [] });
11737
13601
  const answer = createScope({
11738
- ...previous,
13602
+ ...filter,
11739
13603
  name: "answer",
11740
- tools: [],
11741
13604
  select: async (conversation, message, runSignal) => {
11742
- const selection = await this.#select(conversation, message, runSignal);
13605
+ const selection = await this.#select(conversation, message, runSignal, filter);
11743
13606
  if (selection.fault !== void 0) return selection;
11744
13607
  return {
11745
13608
  ...selection,
@@ -11747,12 +13610,9 @@ var Ledger = class {
11747
13610
  };
11748
13611
  }
11749
13612
  });
11750
- this.#agent.context.apply(answer);
11751
- try {
11752
- passes.push(this.#dropStrayCalls(request, await this.#generatePass(request, caller, false)));
11753
- } finally {
11754
- if (this.#agent.context.scope === answer) this.#agent.context.apply(previous);
11755
- }
13613
+ passes.push(this.#dropStrayCalls(request, await this.#generatePass(request, caller, false, answer)));
13614
+ const refusal = this.#refusal;
13615
+ if (refusal !== void 0 && !caller.aborted) throw refusal;
11756
13616
  }
11757
13617
  const last = passes.at(-1) ?? first;
11758
13618
  request.think(this.#conversation.messages());
@@ -11778,26 +13638,31 @@ var Ledger = class {
11778
13638
  }
11779
13639
  async #measureGauge(signal) {
11780
13640
  try {
11781
- const messages = stripThinking(this.#agent.context.build({
11782
- messages: [...this.#conversation.view(), {
11783
- id: "calibration",
11784
- role: "user",
11785
- content: ""
11786
- }],
11787
- judgments: []
11788
- }), this.#replay).slice(0, -1);
11789
- const definitions = this.#agent.context.tools.definitions();
13641
+ const messages = this.#agent.context.build({
13642
+ selection: {
13643
+ messages: [],
13644
+ judgments: []
13645
+ },
13646
+ scope: createScope({
13647
+ name: "calibration",
13648
+ workspaces: []
13649
+ })
13650
+ });
13651
+ const definitions = this.#calibrated();
11790
13652
  const priced = await this.#provider.generate(messages, signal, definitions, { think: false });
13653
+ this.#refuseOverCapacity("with", priced.usage?.prompt);
11791
13654
  const bare = await this.#provider.generate(messages, signal, void 0, { think: false });
11792
- if (!(0, _orkestrel_contract.isFiniteNumber)(priced.usage?.prompt) || priced.usage.prompt <= 0 || !(0, _orkestrel_contract.isFiniteNumber)(bare.usage?.prompt) || bare.usage.prompt <= 0) throw new LedgerError("GAUGE", "calibration requires prompt usage from both calls");
13655
+ this.#refuseOverCapacity("without", bare.usage?.prompt);
13656
+ if (!(0, _orkestrel_contract.isFiniteNumber)(priced.usage?.prompt) || priced.usage.prompt <= 0 || !(0, _orkestrel_contract.isFiniteNumber)(bare.usage?.prompt) || bare.usage.prompt <= 0) throw new LedgerError("GAUGE", "calibration requires prompt usage from each call");
13657
+ const scale = await this.#measureScale(messages, bare.usage.prompt, signal);
11793
13658
  this.#gauge = new Gauge({
11794
13659
  capacity: this.#options.capacity,
11795
13660
  predict: this.#predict,
11796
13661
  replay: this.#replay,
11797
- scale: bare.usage.prompt / estimateMessages(messages),
13662
+ scale,
11798
13663
  overhead: Math.max(0, priced.usage.prompt - bare.usage.prompt)
11799
13664
  });
11800
- this.#digest = digestLedgerTools(definitions);
13665
+ this.#measured = definitions;
11801
13666
  return {
11802
13667
  scale: this.#gauge.scale,
11803
13668
  overhead: this.#gauge.overhead
@@ -11807,13 +13672,53 @@ var Ledger = class {
11807
13672
  throw error;
11808
13673
  }
11809
13674
  }
11810
- async #generatePass(request, signal, think) {
13675
+ async #measureScale(head, bare, signal) {
13676
+ const rate = bare / estimateMessages(head);
13677
+ const workspace = this.#show(intersectFilters(this.#agent.context.scope, void 0)).workspace;
13678
+ const workspaces = this.#agent.context.workspaces;
13679
+ const files = ((workspace === void 0 ? workspaces.active : workspaces.workspace(workspace))?.files() ?? []).flatMap((file) => (0, _orkestrel_workspace.isTextContent)(file.content) ? [file.path] : []);
13680
+ const shown = this.#agent.context.build({
13681
+ selection: {
13682
+ messages: [],
13683
+ judgments: []
13684
+ },
13685
+ scope: createScope({
13686
+ name: "calibration",
13687
+ files
13688
+ }),
13689
+ disclosure: {
13690
+ skills: [],
13691
+ ...workspace === void 0 ? {} : { workspace }
13692
+ }
13693
+ });
13694
+ const whole = estimateMessages(shown) - estimateMessages(head);
13695
+ const cut = cutWorkspaceSample(head, shown, Math.floor((this.#options.capacity - bare) / 3));
13696
+ const part = cut === void 0 ? 0 : estimateMessages(cut) - estimateMessages(head);
13697
+ if (cut === void 0 || whole <= 0 || part <= 0) return rate;
13698
+ const third = await this.#provider.generate(cut, signal, void 0, { think: false });
13699
+ this.#refuseOverCapacity("workspace", third.usage?.prompt);
13700
+ if (!(0, _orkestrel_contract.isFiniteNumber)(third.usage?.prompt) || third.usage.prompt <= 0) throw new LedgerError("GAUGE", "calibration requires prompt usage from each call");
13701
+ const tokens = bare + (third.usage.prompt - bare) * whole / part;
13702
+ return Math.max(rate, tokens / estimateMessages(shown));
13703
+ }
13704
+ #refuseOverCapacity(call, prompt) {
13705
+ const capacity = this.#options.capacity;
13706
+ const subject = call === "workspace" ? "with the workspace text" : `${call} the tool definitions`;
13707
+ 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";
13708
+ if ((0, _orkestrel_contract.isFiniteNumber)(prompt) && prompt > capacity) throw new LedgerError("GAUGE", `the calibration call ${subject} prices ${prompt} tokens, the head included, over the capacity ${capacity}; ${remedy}`, { context: {
13709
+ capacity,
13710
+ prompt
13711
+ } });
13712
+ }
13713
+ async #generatePass(request, signal, think, scope) {
13714
+ this.#refusal = void 0;
11811
13715
  request.open(this.#conversation.messages().length);
11812
13716
  const stop = request.signal;
11813
13717
  return await this.#agent.generate({
11814
- signal: stop === void 0 ? signal : AbortSignal.any([signal, stop]),
13718
+ signal: stop === void 0 ? signal : (0, _orkestrel_abort.linkSignal)(signal, stop),
11815
13719
  conversation: this.#conversation,
11816
- ...think === void 0 ? {} : { think }
13720
+ ...think === void 0 ? {} : { think },
13721
+ ...scope === void 0 ? {} : { scope }
11817
13722
  });
11818
13723
  }
11819
13724
  #dropStrayCalls(request, pass) {
@@ -11839,12 +13744,103 @@ var Ledger = class {
11839
13744
  };
11840
13745
  }
11841
13746
  #current() {
11842
- const [request] = this.#live;
13747
+ const [request] = this.#live.keys();
11843
13748
  return request?.message === void 0 ? void 0 : request;
11844
13749
  }
11845
13750
  #busy() {
11846
13751
  return this.#live.size > 0 || this.#calibrations.size > 0;
11847
13752
  }
13753
+ #evaluate(context, host, advertised, names) {
13754
+ const request = this.#current();
13755
+ const { name, arguments: args } = context.call;
13756
+ if (request?.denied(name, args) === true) return {
13757
+ zone: "ledger",
13758
+ verdict: "deny",
13759
+ reason: LEDGER_NOTES.repeat
13760
+ };
13761
+ if (this.#enables(name)) {
13762
+ const admitted = (0, _orkestrel_contract.attempt)(() => request?.admit(name, args));
13763
+ if (!admitted.success) {
13764
+ if ((0, _orkestrel_contract.isError)(admitted.error) && admitted.error.message === LEDGER_NOTES.repeat) return {
13765
+ zone: "ledger",
13766
+ verdict: "deny",
13767
+ reason: LEDGER_NOTES.repeat
13768
+ };
13769
+ throw admitted.error;
13770
+ }
13771
+ if (request !== void 0) {
13772
+ const commitment = this.#room(request, name, args);
13773
+ if (commitment === void 0) return {
13774
+ zone: "ledger",
13775
+ verdict: "deny",
13776
+ reason: LEDGER_NOTES.overflow
13777
+ };
13778
+ request.commit(commitment.price, commitment.skill);
13779
+ }
13780
+ return host === void 0 ? advertised.evaluate(context) : host.evaluate(context);
13781
+ }
13782
+ const decision = host === void 0 || name === "recall" || !names.includes(name) ? advertised.evaluate(context) : host.evaluate(context);
13783
+ if (request !== void 0 && decision.verdict !== "deny" && name !== "recall" && names.includes(name)) request.commit(this.#priceResult(request.calls));
13784
+ return decision;
13785
+ }
13786
+ #room(request, name, args) {
13787
+ if (name !== "load" && name !== "remember" && name !== "switch") return { price: 0 };
13788
+ const gauge = this.#gauge;
13789
+ if (gauge === void 0) return void 0;
13790
+ const room = gauge.remainder(request.calls) - this.#predict;
13791
+ const admitted = (0, _orkestrel_contract.admitValue)(builtinToolContracts[name].contract, { ...args });
13792
+ const values = admitted.success ? admitted.value.value : {};
13793
+ if (name === "remember") {
13794
+ const line = (0, _orkestrel_contract.isString)(values["name"]) && (0, _orkestrel_contract.isString)(values["description"]) ? renderIndexEntry({
13795
+ name: values["name"],
13796
+ description: values["description"]
13797
+ }) : "";
13798
+ const price = gauge.scale * estimateTokens(line);
13799
+ return (request.committed + price) * 1.06 > room ? void 0 : { price };
13800
+ }
13801
+ const ceiling = this.#ceiling(request);
13802
+ const shown = this.#show(ceiling);
13803
+ if (name === "switch") {
13804
+ const workspace = values["workspace"];
13805
+ const price = (0, _orkestrel_contract.isString)(workspace) ? gauge.scale * Math.max(0, this.#estimateHead(request, ceiling, {
13806
+ skills: shown.skills,
13807
+ workspace
13808
+ }) - this.#estimateHead(request, ceiling, shown)) : 0;
13809
+ return (request.committed + price) * 1.06 > room ? void 0 : { price };
13810
+ }
13811
+ const thread = [.../* @__PURE__ */ new Set([...shown.skills, ...request.loads])];
13812
+ const skill = filterAllowList(ceiling.skills, this.#agent.context.skills.skills(), (one) => one.name).find((one) => one.name === values["skill"]);
13813
+ if (skill === void 0 || thread.includes(skill.name)) return { price: 0 };
13814
+ const price = this.#priceSkill(skill, ceiling, this.#advertise(ceiling, thread));
13815
+ const budget = (this.#options.capacity - this.#predict) * this.#share.prompt;
13816
+ return (request.committed + price + this.#priceResult(request.calls)) * 1.06 > room || this.#priceShown({ skills: thread }, ceiling) + price > this.#share.skills * budget ? void 0 : {
13817
+ price,
13818
+ skill: skill.name
13819
+ };
13820
+ }
13821
+ #estimateHead(request, ceiling, shown) {
13822
+ return estimateMessages(stripThinking(this.#agent.context.build({
13823
+ ...request.selection === void 0 ? {} : { selection: request.selection },
13824
+ disclosure: shown,
13825
+ switchable: this.#advertise(ceiling, shown.skills).some((tool) => tool.name === "switch")
13826
+ }), this.#replay));
13827
+ }
13828
+ #reopen(call, result) {
13829
+ const request = this.#current();
13830
+ const entered = request?.message;
13831
+ if (request === void 0 || entered === void 0 || !result.success || !this.#enables(call.name)) return;
13832
+ const messages = this.#conversation.messages();
13833
+ const pairs = collectToolGroups(messages.slice(messages.findIndex((message) => message.id === entered.id) + 1)).flatMap((group) => group.slice(1).flatMap((message) => {
13834
+ const owner = findToolCall(group, message);
13835
+ return owner === void 0 ? [] : [{
13836
+ call: owner,
13837
+ content: message.content
13838
+ }];
13839
+ }));
13840
+ const key = (0, _orkestrel_contract.canonicalStringify)(call.arguments);
13841
+ 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" && (0, _orkestrel_contract.canonicalStringify)(earlier.arguments) !== key : call.name === "remember" && earlier.name === "memo" && earlier.arguments.name === call.arguments.name);
13842
+ for (const { call: earlier } of reopened) request.release(earlier.name, earlier.arguments);
13843
+ }
11848
13844
  #flush() {
11849
13845
  if (this.#pending.size === 0) return;
11850
13846
  const messages = this.#conversation.messages();
@@ -11895,7 +13891,7 @@ var Ledger = class {
11895
13891
  for (const message of messages) {
11896
13892
  if (!snapshot.failures.includes(message.id)) continue;
11897
13893
  const group = groups.find((entries) => entries.some((one) => one.id === message.id));
11898
- const call = group === void 0 ? void 0 : findLedgerCall(group, message);
13894
+ const call = group === void 0 ? void 0 : findToolCall(group, message);
11899
13895
  this.#results.set(message.id, {
11900
13896
  success: false,
11901
13897
  id: call?.id ?? message.call ?? message.id,
@@ -11925,7 +13921,7 @@ var Ledger = class {
11925
13921
  #readLookups(messages) {
11926
13922
  this.#flush();
11927
13923
  return collectToolGroups(messages).flatMap((group) => group.slice(1).flatMap((message) => {
11928
- const call = findLedgerCall(group, message);
13924
+ const call = findToolCall(group, message);
11929
13925
  const lookup = this.#options.lookups?.find((one) => one.tool.name === call?.name);
11930
13926
  const write = this.#options.writes?.find((one) => one.tool.name === call?.name);
11931
13927
  if (call === void 0 || lookup === void 0 && write === void 0 || this.#results.get(message.id)?.success === false) return [];
@@ -11992,7 +13988,7 @@ var Ledger = class {
11992
13988
  const start = messages.findIndex((message) => message.id === request.id);
11993
13989
  return messages.slice(start + 1).filter((message) => message.role !== "assistant" || hasCalls(message) || message.content.trim() !== "");
11994
13990
  }
11995
- async #select(conversation, message, signal) {
13991
+ async #select(conversation, message, signal, filter) {
11996
13992
  const request = this.#current();
11997
13993
  const selected = request?.message;
11998
13994
  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);
@@ -12004,7 +14000,7 @@ var Ledger = class {
12004
14000
  const scanned = (0, _orkestrel_contract.attempt)(() => this.#scan(this.#conversation.messages()));
12005
14001
  if (scanned.success) this.#hold(scanned.value);
12006
14002
  try {
12007
- return await this.#classifier.classify(this.#requests, signal).then((classified) => this.#file(classified, request, selected, message), (error) => this.#refuse(error, { judgments: [] }, this.#conversation));
14003
+ 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));
12008
14004
  } finally {
12009
14005
  const [scan] = this.#scans;
12010
14006
  if (scan !== void 0) this.#scans.delete(scan);
@@ -12026,11 +14022,11 @@ var Ledger = class {
12026
14022
  this.#hold(scan);
12027
14023
  return scan;
12028
14024
  }
12029
- #file(classified, request, selected, message) {
14025
+ #file(classified, request, selected, message, filter) {
12030
14026
  const { attempts, failure, ...filed } = classified;
12031
14027
  const planned = (0, _orkestrel_contract.attempt)(() => {
12032
14028
  if (filed.fault !== void 0) throw filed.fault;
12033
- const { draft, ...plan } = this.#plan(selected, this.#rescan());
14029
+ const { draft, ...plan } = this.#plan(selected, this.#rescan(), filter);
12034
14030
  return {
12035
14031
  draft,
12036
14032
  selection: {
@@ -12063,7 +14059,7 @@ var Ledger = class {
12063
14059
  const category = this.#classifier.category(source);
12064
14060
  return !(role === "user" && !this.#classifier.decisive(source)) && (category === "rule" || category === "correction");
12065
14061
  }
12066
- #plan(request, scan) {
14062
+ #plan(request, scan, filter) {
12067
14063
  const { input, projection } = this.#project(scan);
12068
14064
  const registry = scan.registry;
12069
14065
  const links = linkOwners(input.findings, registry.owners);
@@ -12075,10 +14071,20 @@ var Ledger = class {
12075
14071
  }),
12076
14072
  topics: [...near]
12077
14073
  });
12078
- const total = Math.max(0, (this.#options.capacity - this.#predict) * this.#share.prompt - (this.#gauge?.overhead ?? 0)) / (1 + LEDGER_SCALE_DRIFT);
14074
+ const lists = intersectFilters(void 0, filter);
14075
+ const bound = lists.tools;
14076
+ const disclosure = this.#show(lists);
14077
+ const advertised = this.#advertise(lists, disclosure.skills);
14078
+ const calibrated = this.#measuredSet();
14079
+ const price = computeToolPrice(calibrated, advertised, this.#priced());
14080
+ 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);
12079
14081
  const built = this.#agent.context.build({
12080
- messages: [request],
12081
- judgments: []
14082
+ selection: {
14083
+ messages: [request],
14084
+ judgments: []
14085
+ },
14086
+ disclosure,
14087
+ switchable: advertised.some((tool) => tool.name === "switch")
12082
14088
  });
12083
14089
  const tail = this.#selectTail(request, total, input, scan);
12084
14090
  const tailIds = new Set(tail.map((message) => message.id));
@@ -12187,23 +14193,24 @@ var Ledger = class {
12187
14193
  ...rung.window.flatMap((entry) => [entry.request, entry.reply]),
12188
14194
  buildRequestMessage(built.at(-1) ?? request, rung.rendered.text)
12189
14195
  ], this.#replay)]);
12190
- const names = this.#agent.context.scope?.tools;
12191
- const definitions = this.#agent.context.tools.definitions();
12192
14196
  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]));
12193
14197
  const longest = this.#findLongest();
12194
14198
  const closed = probes.map((messages, at) => {
12195
- const tools = definitions.filter((tool) => (names === void 0 || names.includes(tool.name)) && (tool.name !== "recall" || offered[at]));
14199
+ const tools = advertised.filter((tool) => tool.name !== "recall" || offered[at]);
12196
14200
  const calls = [{
12197
14201
  estimate: estimateMessages(messages),
12198
14202
  tools: tools.length,
12199
- spared: this.#spared(tools)
14203
+ ...computeToolPrice(calibrated, tools, this.#priced())
12200
14204
  }];
12201
14205
  return offered[at] === true && this.#gauge !== void 0 && this.#gauge.remainder(calls) - this.#predict < 2 * this.#gauge.reserve(calls, longest);
12202
14206
  });
12203
14207
  const prices = probes.map((messages) => this.#price(messages));
12204
14208
  const admitted = rungs.find((_rung, at) => (prices[at] ?? Infinity) <= total && (closed[2] === true || closed[at] === false)) ?? empty;
12205
- if (offered[rungs.indexOf(admitted)] === false && (names === void 0 || names.includes("recall"))) {
12206
- const tools = definitions.filter((tool) => tool.name !== "recall").map((tool) => tool.name);
14209
+ const recall = offered[rungs.indexOf(admitted)] === true;
14210
+ const ceiling = intersectKeys(this.#agent.context.scope?.tools, bound);
14211
+ const admits = ceiling === void 0 || ceiling.includes("recall");
14212
+ if (!recall && admits) {
14213
+ const tools = this.#definitions().filter((tool) => tool.name !== "recall").map((tool) => tool.name);
12207
14214
  const narrowed = this.#agent.context.scope?.narrow({ tools }) ?? createScope({
12208
14215
  name: "ledger",
12209
14216
  tools
@@ -12226,10 +14233,11 @@ var Ledger = class {
12226
14233
  window: admitted.window
12227
14234
  };
12228
14235
  const shown = buildLedgerRenderContext(scan.messages, [...kept, ...admitted.rendered.lines], []).complete;
14236
+ const route = recall && admits;
12229
14237
  return {
12230
14238
  draft,
12231
14239
  messages: [
12232
- ...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown) : message),
14240
+ ...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown, route) : message),
12233
14241
  ...admitted.window.flatMap((entry) => [entry.request, entry.reply]),
12234
14242
  copy
12235
14243
  ]
@@ -12317,7 +14325,7 @@ var Ledger = class {
12317
14325
  return [{
12318
14326
  leader,
12319
14327
  kept: (leader.calls ?? []).flatMap((call) => {
12320
- const message = group.slice(1).find((result) => findLedgerCall(group, result) === call);
14328
+ const message = group.slice(1).find((result) => findToolCall(group, result) === call);
12321
14329
  const result = message === void 0 ? void 0 : this.#results.get(message.id);
12322
14330
  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 ? [] : [{
12323
14331
  call,
@@ -12362,16 +14370,16 @@ var Ledger = class {
12362
14370
  amendments: evidence.amendments
12363
14371
  };
12364
14372
  }
12365
- #renderTailStub(message, scan, shown) {
14373
+ #renderTailStub(message, scan, shown, offered = true) {
12366
14374
  const group = scan.group(message.id);
12367
- const call = group === void 0 ? void 0 : findLedgerCall(group, message);
14375
+ const call = group === void 0 ? void 0 : findToolCall(group, message);
12368
14376
  const write = this.#options.writes?.some((one) => one.tool.name === call?.name) === true;
12369
14377
  const finding = scan.finding(message.id);
12370
14378
  const hidden = renderStub(call?.name ?? "tool", call?.arguments ?? {}, "hidden");
12371
14379
  const visible = renderStub(call?.name ?? "tool", call?.arguments ?? {}, "shown");
12372
14380
  return {
12373
14381
  ...message,
12374
- 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)
14382
+ 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)
12375
14383
  };
12376
14384
  }
12377
14385
  #release(call, result) {
@@ -12402,26 +14410,71 @@ var Ledger = class {
12402
14410
  }]);
12403
14411
  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);
12404
14412
  }
12405
- #observeTurn() {
14413
+ #observeRequest(sent) {
12406
14414
  this.#flush();
12407
14415
  const request = this.#current();
12408
- const boundary = request?.boundary;
12409
- if (request === void 0 || boundary === void 0 || request.aborted) return;
12410
- const additions = this.#conversation.messages().slice(boundary);
12411
- const messages = this.#agent.context.build(request.selection);
12412
- const names = this.#agent.context.scope?.tools;
12413
- const tools = this.#agent.context.tools.definitions().filter((tool) => names === void 0 || names.includes(tool.name));
14416
+ if (request?.boundary === void 0) return;
14417
+ const tools = sent.tools ?? [];
12414
14418
  request.observe({
12415
- estimate: estimateMessages(stripThinking([...messages, ...additions], this.#replay)),
14419
+ estimate: estimateMessages(sent.messages),
12416
14420
  tools: tools.length,
12417
- spared: this.#spared(tools)
14421
+ ...computeToolPrice(this.#measuredSet(), tools, this.#priced())
12418
14422
  }, this.#conversation.messages().length);
12419
14423
  }
12420
- #spared(tools) {
12421
- const overhead = this.#gauge?.overhead ?? 0;
12422
- if (tools.length === 0) return overhead;
12423
- const missing = this.#agent.context.tools.definitions().filter((tool) => !tools.some((one) => one.name === tool.name));
12424
- return missing.length === 0 ? 0 : Math.min(overhead, estimateTokens(JSON.stringify(missing)) * (this.#gauge?.scale ?? 1));
14424
+ #priced() {
14425
+ return this.#gauge ?? {
14426
+ scale: 1,
14427
+ overhead: 0
14428
+ };
14429
+ }
14430
+ #measuredSet() {
14431
+ return this.#measured ?? this.#calibrated();
14432
+ }
14433
+ #digest() {
14434
+ return this.#measured === void 0 ? void 0 : digestLedgerTools(this.#measured);
14435
+ }
14436
+ #enables(name) {
14437
+ return this.#builtins.some((one) => one === name);
14438
+ }
14439
+ #definitions() {
14440
+ return [...this.#agent.context.tools.definitions().filter((tool) => !this.#enables(tool.name)), ...collectBuiltinDefinitions({ builtins: this.#builtins })];
14441
+ }
14442
+ #calibrated() {
14443
+ const definitions = this.#definitions();
14444
+ if (!this.#enables("load")) return definitions;
14445
+ const held = new Set(this.#agent.context.skills.skills().flatMap((skill) => skill.tools));
14446
+ return definitions.filter((tool) => !held.has(tool.name));
14447
+ }
14448
+ #show(ceiling) {
14449
+ return resolveDisclosure(this.#agent.context, this.#conversation, this.#given, ceiling, this.#builtins);
14450
+ }
14451
+ #advertise(ceiling, shown) {
14452
+ return filterAdvertisedTools(filterBuiltinCandidates(this.#definitions(), ceiling), ceiling.tools, this.#enables("load") ? this.#agent.context.skills.skills() : [], shown);
14453
+ }
14454
+ #ceiling(request) {
14455
+ return intersectFilters(this.#agent.context.scope, this.#live.get(request));
14456
+ }
14457
+ #priceSkill(skill, ceiling, advertised) {
14458
+ const sent = new Set(advertised.map((tool) => tool.name));
14459
+ 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());
14460
+ }
14461
+ #priceShown(shown, ceiling) {
14462
+ const skills = shown.skills.flatMap((name) => this.#agent.context.skills.skill(name) ?? []);
14463
+ const named = new Set(skills.flatMap((skill) => skill.tools));
14464
+ const definitions = filterAllowList(ceiling.tools, this.#definitions().filter((tool) => named.has(tool.name)), (tool) => tool.name);
14465
+ const calibrated = this.#measuredSet();
14466
+ const gauge = this.#priced();
14467
+ return skills.reduce((sum, skill) => sum + computeSkillPrice(skill, [], calibrated, gauge), 0) + computeToolPrice(calibrated, [...calibrated, ...definitions], gauge).added;
14468
+ }
14469
+ #priceResult(calls) {
14470
+ 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);
14471
+ const gauge = this.#gauge;
14472
+ return Math.max(this.#priced().scale * (longest === void 0 ? 0 : estimateMessages([longest])), gauge === void 0 ? 0 : gauge.reserve(calls, this.#findLongest()));
14473
+ }
14474
+ #holdback(ceiling, shown, advertised, calls) {
14475
+ if (!advertised.some((tool) => tool.name === "load")) return 0;
14476
+ const loadable = filterAllowList(ceiling.skills, this.#agent.context.skills.skills(), (skill) => skill.name).filter((skill) => !shown.skills.includes(skill.name));
14477
+ return loadable.length === 0 ? 0 : Math.max(...loadable.map((skill) => this.#priceSkill(skill, ceiling, advertised))) + this.#priceResult(calls);
12425
14478
  }
12426
14479
  #findLongest() {
12427
14480
  return this.#conversation.messages().filter((message) => message.role === "assistant").reduce((longest, message) => message.content.length > longest.length ? message.content : longest, "");
@@ -12449,12 +14502,7 @@ var Ledger = class {
12449
14502
  request?.recall();
12450
14503
  const guidance = `an owner name, an id, or one of ${renderTopicNames(this.#options.topics)}`;
12451
14504
  if (topic === "") throw new Error(`recall needs a topic: ${guidance}`);
12452
- const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
12453
- const call = calls.at(-1);
12454
- const carried = request === void 0 ? 0 : estimateMessages(stripThinking([...this.#agent.context.build(request.selection), ...this.#conversation.messages().slice(request.boundary)], this.#replay));
12455
- const completion = (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : 0;
12456
- const extra = Math.max(0, (carried - (call?.estimate ?? carried)) * rate - completion);
12457
- const drift = (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? 0 : Math.max(0, (call?.estimate ?? 0) * (rate - (gauge?.scale ?? rate)));
14505
+ const { rate, extra, slack: drift } = this.#carry(request, calls);
12458
14506
  const siblings = estimateMessages((request?.listings ?? []).filter((listing) => listing.turn === calls.length).map((listing) => ({
12459
14507
  id: "result",
12460
14508
  role: "tool",
@@ -12529,7 +14577,7 @@ var Ledger = class {
12529
14577
  this.#flush();
12530
14578
  const groups = [...new Set(this.#collectAfter(entered).flatMap((message) => {
12531
14579
  const result = this.#results.get(message.id);
12532
- if (message.role !== "tool" || result?.success !== true) return [];
14580
+ if (message.role !== "tool" || result?.success !== true || result.name !== "memo" && this.#enables(result.name)) return [];
12533
14581
  const recalled = result.name === "recall";
12534
14582
  if (recalled && /^nothing earlier on /.test(message.content)) return [];
12535
14583
  const listing = recalled ? request.listings.findLast((one) => one.result === message.content) : void 0;
@@ -12538,7 +14586,7 @@ var Ledger = class {
12538
14586
  const unique = groups.filter((group, at) => !splitResultLines(group).every((line) => groups.slice(0, at).some((earlier) => splitResultLines(earlier).includes(line))));
12539
14587
  const digest = unique.length === 0 ? void 0 : `${LEDGER_NOTES.results}\n${unique.join("\n")}`;
12540
14588
  const omitted = /* @__PURE__ */ new Set();
12541
- const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
14589
+ const rate = this.#capacityRate(request) * (1 + LEDGER_SCALE_DRIFT);
12542
14590
  const allowance = (this.#options.capacity - this.#predict) / rate;
12543
14591
  const after = this.#collectAfter(entered);
12544
14592
  const messages = [
@@ -12555,28 +14603,105 @@ var Ledger = class {
12555
14603
  role: "user",
12556
14604
  content: digest
12557
14605
  }];
14606
+ const shown = this.#show(this.#ceiling(request));
12558
14607
  if (this.#gauge === void 0 || estimateMessages(stripThinking(this.#agent.context.build({
12559
- messages: [...messages, ...note],
12560
- judgments: []
14608
+ selection: {
14609
+ messages: [...messages, ...note],
14610
+ judgments: []
14611
+ },
14612
+ disclosure: shown
12561
14613
  }), this.#replay)) <= allowance) return {
12562
14614
  digest,
12563
14615
  omitted
12564
14616
  };
12565
14617
  const partials = new Set(after.filter((message) => message.role === "assistant" && !hasCalls(message)).map((message) => message.id));
12566
- const fitted = cutListing(unique, allowance - estimateMessages(stripThinking(this.#agent.context.build({
12567
- messages: messages.filter((message) => !partials.has(message.id)),
12568
- judgments: []
12569
- }), this.#replay)) - estimateTokens(`${LEDGER_NOTES.results}\n`), "more", false);
14618
+ const base = estimateMessages(stripThinking(this.#agent.context.build({
14619
+ selection: {
14620
+ messages: messages.filter((message) => !partials.has(message.id)),
14621
+ judgments: []
14622
+ },
14623
+ disclosure: shown
14624
+ }), this.#replay));
14625
+ 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: {
14626
+ capacity: this.#options.capacity,
14627
+ predict: this.#predict,
14628
+ price: base * rate
14629
+ } });
14630
+ const fitted = cutListing(unique, allowance - base - estimateTokens(`${LEDGER_NOTES.results}\n`), "more", false);
14631
+ const cut = fitted === "" ? void 0 : `${LEDGER_NOTES.results}\n${fitted}`;
12570
14632
  return {
12571
- digest: fitted === "" ? void 0 : `${LEDGER_NOTES.results}\n${fitted}`,
14633
+ digest: cut !== void 0 && base + estimateMessages([{
14634
+ id: "results",
14635
+ role: "user",
14636
+ content: cut
14637
+ }]) > allowance ? void 0 : cut,
12572
14638
  omitted: partials
12573
14639
  };
12574
14640
  }
14641
+ #carry(request, calls) {
14642
+ const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
14643
+ const ceiling = request === void 0 ? {} : this.#ceiling(request);
14644
+ const shown = this.#show(ceiling);
14645
+ const carried = request === void 0 ? 0 : estimateMessages(stripThinking([...this.#agent.context.build({
14646
+ ...request.selection === void 0 ? {} : { selection: request.selection },
14647
+ disclosure: shown,
14648
+ switchable: this.#advertise(ceiling, shown.skills).some((tool) => tool.name === "switch")
14649
+ }), ...this.#conversation.messages().slice(request.boundary)], this.#replay));
14650
+ return {
14651
+ rate,
14652
+ ...this.#terms(calls.at(-1), carried, rate)
14653
+ };
14654
+ }
14655
+ #terms(call, carried, rate) {
14656
+ const reply = (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : 0;
14657
+ return {
14658
+ extra: Math.max(0, (carried - (call?.estimate ?? carried)) * rate - reply),
14659
+ slack: (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? 0 : Math.max(0, (call?.estimate ?? 0) * (rate - (this.#gauge?.scale ?? rate)))
14660
+ };
14661
+ }
14662
+ #gate(index, sent) {
14663
+ const request = this.#current();
14664
+ if (request?.boundary === void 0) return true;
14665
+ const left = (0, _orkestrel_contract.attempt)(() => index === 0 ? this.#first(request, sent) : this.#next(request, sent));
14666
+ if (left.success && left.value >= 0) return true;
14667
+ const limit = this.#options.capacity - this.#predict;
14668
+ 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: {
14669
+ capacity: this.#options.capacity,
14670
+ predict: this.#predict,
14671
+ price: left.success ? limit - left.value : Number.POSITIVE_INFINITY
14672
+ } });
14673
+ this.#agent.abort("overflow");
14674
+ return false;
14675
+ }
14676
+ #first(request, sent) {
14677
+ const gauge = this.#gauge;
14678
+ if (gauge === void 0) throw new LedgerError("GAUGE", "the gate needs a gauge");
14679
+ const tools = computeToolPrice(this.#measuredSet(), sent.tools ?? [], gauge);
14680
+ const rate = request.calls.length === 0 ? gauge.scale : this.#capacityRate(request);
14681
+ return this.#options.capacity - this.#predict - (Math.max(0, gauge.overhead - tools.spared) + tools.added + rate * (1 + LEDGER_SCALE_DRIFT) * estimateMessages(sent.messages));
14682
+ }
14683
+ #next(request, sent) {
14684
+ request.think(this.#conversation.messages());
14685
+ const gauge = this.#gauge;
14686
+ if (gauge === void 0) throw new LedgerError("GAUGE", "the gate needs a gauge");
14687
+ const calls = request.calls;
14688
+ const last = calls.at(-1);
14689
+ if (last === void 0) throw new Error("a later request has no recorded call before it");
14690
+ const measured = this.#measuredSet();
14691
+ const rate = this.#capacityRate(request) * (1 + LEDGER_SCALE_DRIFT);
14692
+ const { extra, slack } = this.#terms(last, estimateMessages(sent.messages), rate);
14693
+ const tools = computeToolPrice(measured, sent.tools ?? [], gauge);
14694
+ 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);
14695
+ return gauge.remainder(calls) - this.#predict - extra - slack - grown;
14696
+ }
14697
+ #capacityRate(request) {
14698
+ return Math.max(this.#recallRate(request), this.#gauge?.scale ?? 1);
14699
+ }
12575
14700
  #recallRate(request) {
12576
14701
  const gauge = this.#gauge;
12577
14702
  if (gauge === void 0) return 1;
12578
14703
  const calls = request?.calls ?? [];
12579
- const last = calls.findLast((call) => (0, _orkestrel_contract.isFiniteNumber)(call.prompt));
14704
+ const last = calls.findLast((call) => (0, _orkestrel_contract.isFiniteNumber)(call.prompt) && (call.added ?? 0) === 0);
12580
14705
  const measured = last !== void 0 && (0, _orkestrel_contract.isFiniteNumber)(last.prompt) && last.estimate > 0 && ((last.spared ?? 0) === 0 || (last.spared ?? 0) >= gauge.overhead) ? (last.prompt - Math.max(0, gauge.overhead - (last.spared ?? 0))) / last.estimate : 0;
12581
14706
  return Math.max(gauge.rate(calls), measured);
12582
14707
  }
@@ -12597,7 +14722,12 @@ var Ledger = class {
12597
14722
  function createLedger(provider, options) {
12598
14723
  return new Ledger(provider, options);
12599
14724
  }
14725
+ exports.AGENT_OPTION_KEYS = AGENT_OPTION_KEYS;
14726
+ exports.AGENT_RUN_KEYS = AGENT_RUN_KEYS;
14727
+ exports.ARGUMENT_BLANK_PATTERN = ARGUMENT_BLANK_PATTERN;
14728
+ exports.ARGUMENT_CONTROL_PATTERN = ARGUMENT_CONTROL_PATTERN;
12600
14729
  exports.AUTHORITY_NOTES = AUTHORITY_NOTES;
14730
+ exports.AUTHORITY_OPTION_KEYS = AUTHORITY_OPTION_KEYS;
12601
14731
  exports.Agent = Agent;
12602
14732
  exports.AgentContext = AgentContext;
12603
14733
  exports.AgentError = AgentError;
@@ -12608,6 +14738,16 @@ exports.AgentProvider = AgentProvider;
12608
14738
  exports.AgentRegistry = AgentRegistry;
12609
14739
  exports.Approver = Approver;
12610
14740
  exports.Authority = Authority;
14741
+ exports.BUILD_OPTION_KEYS = BUILD_OPTION_KEYS;
14742
+ exports.BUILTIN_LIMITS = BUILTIN_LIMITS;
14743
+ exports.BUILTIN_NOTES = BUILTIN_NOTES;
14744
+ exports.BUILTIN_TOOLS = BUILTIN_TOOLS;
14745
+ exports.CLASSIFIER_OPTION_KEYS = CLASSIFIER_OPTION_KEYS;
14746
+ exports.COMPACT_OPTION_KEYS = COMPACT_OPTION_KEYS;
14747
+ exports.CONTEXT_OPTION_KEYS = CONTEXT_OPTION_KEYS;
14748
+ exports.CONVERSATION_INPUT_KEYS = CONVERSATION_INPUT_KEYS;
14749
+ exports.CONVERSATION_MANAGER_KEYS = CONVERSATION_MANAGER_KEYS;
14750
+ exports.CONVERSATION_OPTION_KEYS = CONVERSATION_OPTION_KEYS;
12611
14751
  exports.CONVERSATION_RECAP_PREFIX = CONVERSATION_RECAP_PREFIX;
12612
14752
  exports.CONVERSATION_ROLES = CONVERSATION_ROLES;
12613
14753
  exports.Channel = Channel;
@@ -12627,27 +14767,36 @@ exports.DEFAULT_RELAY_LIMIT = DEFAULT_RELAY_LIMIT;
12627
14767
  exports.DETERMINISTIC_JUDGE_ERROR = DETERMINISTIC_JUDGE_ERROR;
12628
14768
  exports.DatabaseConversationStore = DatabaseConversationStore;
12629
14769
  exports.DatabaseMemoStore = DatabaseMemoStore;
14770
+ exports.GAUGE_OPTION_KEYS = GAUGE_OPTION_KEYS;
12630
14771
  exports.Gauge = Gauge;
12631
14772
  exports.IMAGE_TOKEN_ESTIMATE = IMAGE_TOKEN_ESTIMATE;
14773
+ exports.INSTRUCTION_MANAGER_KEYS = INSTRUCTION_MANAGER_KEYS;
12632
14774
  exports.INVALID_RELAY_STATUS = INVALID_RELAY_STATUS;
12633
14775
  exports.Instruction = Instruction;
12634
14776
  exports.InstructionManager = InstructionManager;
14777
+ exports.JOB_REFUSAL_PREFIX = JOB_REFUSAL_PREFIX;
14778
+ exports.JUDGE_INPUT_KEYS = JUDGE_INPUT_KEYS;
12635
14779
  exports.JudgeAbortError = JudgeAbortError;
12636
14780
  exports.JudgeError = JudgeError;
12637
14781
  exports.JudgmentManager = JudgmentManager;
14782
+ exports.LEDGER_AGENT_KEYS = LEDGER_AGENT_KEYS;
12638
14783
  exports.LEDGER_CATEGORIES = LEDGER_CATEGORIES;
12639
14784
  exports.LEDGER_NOTES = LEDGER_NOTES;
14785
+ exports.LEDGER_OPTION_KEYS = LEDGER_OPTION_KEYS;
12640
14786
  exports.LEDGER_OWNER_PREFIX = LEDGER_OWNER_PREFIX;
12641
14787
  exports.LEDGER_QUESTIONS = LEDGER_QUESTIONS;
12642
14788
  exports.LEDGER_RECALL_LABELS = LEDGER_RECALL_LABELS;
12643
14789
  exports.LEDGER_RULES_KEY = LEDGER_RULES_KEY;
14790
+ exports.LEDGER_SAMPLE_BYTES = LEDGER_SAMPLE_BYTES;
12644
14791
  exports.LEDGER_SCALE_DRIFT = LEDGER_SCALE_DRIFT;
12645
14792
  exports.LEDGER_SLICE = LEDGER_SLICE;
12646
14793
  exports.LEDGER_WORD = LEDGER_WORD;
14794
+ exports.LOADED_SECTION_HEADER = LOADED_SECTION_HEADER;
12647
14795
  exports.Ledger = Ledger;
12648
14796
  exports.LedgerError = LedgerError;
12649
14797
  exports.LedgerRequest = LedgerRequest;
12650
14798
  exports.MAX_ERROR_BODY_LENGTH = MAX_ERROR_BODY_LENGTH;
14799
+ exports.MEMO_MANAGER_KEYS = MEMO_MANAGER_KEYS;
12651
14800
  exports.MEMO_SECTION_HEADER = MEMO_SECTION_HEADER;
12652
14801
  exports.MESSAGE_ROLES = MESSAGE_ROLES;
12653
14802
  exports.MESSAGE_TOKEN_OVERHEAD = MESSAGE_TOKEN_OVERHEAD;
@@ -12656,17 +14805,31 @@ exports.MemoryConversationStore = MemoryConversationStore;
12656
14805
  exports.MemoryMemoStore = MemoryMemoStore;
12657
14806
  exports.OVERSIZED_RELAY_STATUS = OVERSIZED_RELAY_STATUS;
12658
14807
  exports.PLACED_CATEGORIES = PLACED_CATEGORIES;
14808
+ exports.PROVIDER_INPUT_KEYS = PROVIDER_INPUT_KEYS;
12659
14809
  exports.ProviderAbortError = ProviderAbortError;
12660
14810
  exports.ProviderError = ProviderError;
14811
+ exports.QUEUE_OPTION_KEYS = QUEUE_OPTION_KEYS;
12661
14812
  exports.QUIET_CATEGORIES = QUIET_CATEGORIES;
14813
+ exports.REFERENCE_OPTION_KEYS = REFERENCE_OPTION_KEYS;
12662
14814
  exports.REFUSED_RELAY_CONTENT_TYPE = REFUSED_RELAY_CONTENT_TYPE;
12663
14815
  exports.REFUSED_RELAY_STATUS = REFUSED_RELAY_STATUS;
14816
+ exports.REGISTRY_OPTION_KEYS = REGISTRY_OPTION_KEYS;
12664
14817
  exports.RELAY_CONTENT_TYPE = RELAY_CONTENT_TYPE;
14818
+ exports.RELAY_OPTION_KEYS = RELAY_OPTION_KEYS;
14819
+ exports.RELAY_PROVIDER_KEYS = RELAY_PROVIDER_KEYS;
12665
14820
  exports.RELAY_PROVIDER_MESSAGE = RELAY_PROVIDER_MESSAGE;
14821
+ exports.RELAY_STREAM_KEYS = RELAY_STREAM_KEYS;
14822
+ exports.RUNNER_OPTION_KEYS = RUNNER_OPTION_KEYS;
12666
14823
  exports.RelayProvider = RelayProvider;
12667
14824
  exports.RelayStream = RelayStream;
14825
+ exports.SCOPE_FILTER_KEYS = SCOPE_FILTER_KEYS;
14826
+ exports.SCOPE_KEYS = SCOPE_KEYS;
14827
+ exports.SCOPE_MANAGER_KEYS = SCOPE_MANAGER_KEYS;
12668
14828
  exports.SCOPE_NOTE = SCOPE_NOTE;
14829
+ exports.SKILL_MANAGER_KEYS = SKILL_MANAGER_KEYS;
12669
14830
  exports.SKILL_SECTION_HEADER = SKILL_SECTION_HEADER;
14831
+ exports.STREAM_OPTION_KEYS = STREAM_OPTION_KEYS;
14832
+ exports.SYSTEM_ONE_OPTION_KEYS = SYSTEM_ONE_OPTION_KEYS;
12670
14833
  exports.SYSTEM_ONE_PATH = SYSTEM_ONE_PATH;
12671
14834
  exports.Scope = Scope;
12672
14835
  exports.ScopeManager = ScopeManager;
@@ -12677,6 +14840,7 @@ exports.THINK_OPEN = THINK_OPEN;
12677
14840
  exports.ThinkSplitter = ThinkSplitter;
12678
14841
  exports.UNAUTHORIZED_RELAY_STATUS = UNAUTHORIZED_RELAY_STATUS;
12679
14842
  exports.UPSTREAM_RELAY_STATUS = UPSTREAM_RELAY_STATUS;
14843
+ exports.WORKSPACE_INDEX_HEADER = WORKSPACE_INDEX_HEADER;
12680
14844
  exports.WORKSPACE_SECTION_HEADER = WORKSPACE_SECTION_HEADER;
12681
14845
  exports.agentAnswerContract = agentAnswerContract;
12682
14846
  exports.agentAnswerShape = agentAnswerShape;
@@ -12709,10 +14873,14 @@ exports.buildRecords = buildRecords;
12709
14873
  exports.buildRelayRequest = buildRelayRequest;
12710
14874
  exports.buildRequestMessage = buildRequestMessage;
12711
14875
  exports.buildSummaryMessage = buildSummaryMessage;
14876
+ exports.builtinToolContracts = builtinToolContracts;
12712
14877
  exports.chargeUsage = chargeUsage;
12713
14878
  exports.collectAmendments = collectAmendments;
14879
+ exports.collectBuiltinDefinitions = collectBuiltinDefinitions;
14880
+ exports.collectDisclosure = collectDisclosure;
12714
14881
  exports.collectExchanges = collectExchanges;
12715
14882
  exports.collectImageData = collectImageData;
14883
+ exports.collectJobRefusals = collectJobRefusals;
12716
14884
  exports.collectLedgerEvidence = collectLedgerEvidence;
12717
14885
  exports.collectLedgerLines = collectLedgerLines;
12718
14886
  exports.collectLedgerReplies = collectLedgerReplies;
@@ -12725,7 +14893,10 @@ exports.collectRetiredFindings = collectRetiredFindings;
12725
14893
  exports.collectToolGroups = collectToolGroups;
12726
14894
  exports.collectWriteSupersessions = collectWriteSupersessions;
12727
14895
  exports.computeLedgerChecksum = computeLedgerChecksum;
14896
+ exports.computeSkillPrice = computeSkillPrice;
12728
14897
  exports.computeThinking = computeThinking;
14898
+ exports.computeToolDensity = computeToolDensity;
14899
+ exports.computeToolPrice = computeToolPrice;
12729
14900
  exports.conversationSnapshotContract = conversationSnapshotContract;
12730
14901
  exports.conversationSnapshotShape = conversationSnapshotShape;
12731
14902
  exports.copyJSON = copyJSON;
@@ -12736,6 +14907,7 @@ exports.createAgentRegistry = createAgentRegistry;
12736
14907
  exports.createAgentRunner = createAgentRunner;
12737
14908
  exports.createApprover = createApprover;
12738
14909
  exports.createAuthority = createAuthority;
14910
+ exports.createBuiltinTool = createBuiltinTool;
12739
14911
  exports.createChannel = createChannel;
12740
14912
  exports.createConversation = createConversation;
12741
14913
  exports.createConversationManager = createConversationManager;
@@ -12755,7 +14927,9 @@ exports.createSkillManager = createSkillManager;
12755
14927
  exports.createSystemOneJudge = createSystemOneJudge;
12756
14928
  exports.createThinkSplitter = createThinkSplitter;
12757
14929
  exports.cutListing = cutListing;
14930
+ exports.cutWorkspaceSample = cutWorkspaceSample;
12758
14931
  exports.denyCall = denyCall;
14932
+ exports.describeCandidates = describeCandidates;
12759
14933
  exports.describeQuestionRefusal = describeQuestionRefusal;
12760
14934
  exports.describeRelayRefusal = describeRelayRefusal;
12761
14935
  exports.describeSnapshotRefusal = describeSnapshotRefusal;
@@ -12769,9 +14943,11 @@ exports.extractQueueOptions = extractQueueOptions;
12769
14943
  exports.extractSystemOneAnswer = extractSystemOneAnswer;
12770
14944
  exports.extractSystemOneUsage = extractSystemOneUsage;
12771
14945
  exports.extractWords = extractWords;
14946
+ exports.filterAdvertisedTools = filterAdvertisedTools;
12772
14947
  exports.filterAllowList = filterAllowList;
12773
- exports.findLedgerCall = findLedgerCall;
14948
+ exports.filterBuiltinCandidates = filterBuiltinCandidates;
12774
14949
  exports.findLedgerCut = findLedgerCut;
14950
+ exports.findToolCall = findToolCall;
12775
14951
  exports.fitSlope = fitSlope;
12776
14952
  exports.freezeJSON = freezeJSON;
12777
14953
  exports.handleAgentQueueJob = handleAgentQueueJob;
@@ -12779,7 +14955,11 @@ exports.hasCalls = hasCalls;
12779
14955
  exports.hasHiddenRecall = hasHiddenRecall;
12780
14956
  exports.identifyLookup = identifyLookup;
12781
14957
  exports.inferStop = inferStop;
14958
+ exports.instructionInputContract = instructionInputContract;
14959
+ exports.instructionInputShape = instructionInputShape;
14960
+ exports.intersectFilters = intersectFilters;
12782
14961
  exports.intersectKeys = intersectKeys;
14962
+ exports.isAgentBuiltin = isAgentBuiltin;
12783
14963
  exports.isAgentError = isAgentError;
12784
14964
  exports.isAgentJobError = isAgentJobError;
12785
14965
  exports.isAuthorityDecision = isAuthorityDecision;
@@ -12809,6 +14989,7 @@ exports.isPositiveSafeInteger = isPositiveSafeInteger;
12809
14989
  exports.isProviderAbortError = isProviderAbortError;
12810
14990
  exports.isProviderError = isProviderError;
12811
14991
  exports.isProviderResult = isProviderResult;
14992
+ exports.isScope = isScope;
12812
14993
  exports.isSection = isSection;
12813
14994
  exports.isSystemOneAnswer = isSystemOneAnswer;
12814
14995
  exports.isSystemOneResponse = isSystemOneResponse;
@@ -12825,18 +15006,26 @@ exports.ledgerSnapshotShape = ledgerSnapshotShape;
12825
15006
  exports.ledgerWindowContract = ledgerWindowContract;
12826
15007
  exports.ledgerWindowShape = ledgerWindowShape;
12827
15008
  exports.linkOwners = linkOwners;
15009
+ exports.loadToolContract = loadToolContract;
15010
+ exports.loadToolShape = loadToolShape;
12828
15011
  exports.matchEntities = matchEntities;
12829
15012
  exports.matchesCutLine = matchesCutLine;
15013
+ exports.matchesHeadLists = matchesHeadLists;
12830
15014
  exports.matchesJSON = matchesJSON;
12831
15015
  exports.matchesJudgment = matchesJudgment;
12832
15016
  exports.matchesLookup = matchesLookup;
15017
+ exports.matchesPlainLine = matchesPlainLine;
12833
15018
  exports.measureAnswer = measureAnswer;
12834
15019
  exports.measureMessage = measureMessage;
12835
15020
  exports.measureRequest = measureRequest;
12836
15021
  exports.memoContract = memoContract;
15022
+ exports.memoInputContract = memoInputContract;
15023
+ exports.memoInputShape = memoInputShape;
12837
15024
  exports.memoShape = memoShape;
12838
15025
  exports.memoSnapshotContract = memoSnapshotContract;
12839
15026
  exports.memoSnapshotShape = memoSnapshotShape;
15027
+ exports.memoToolContract = memoToolContract;
15028
+ exports.memoToolShape = memoToolShape;
12840
15029
  exports.mergeSupersessions = mergeSupersessions;
12841
15030
  exports.messageContract = messageContract;
12842
15031
  exports.messageInputContract = messageInputContract;
@@ -12860,23 +15049,28 @@ exports.readProviderDelta = readProviderDelta;
12860
15049
  exports.relayFrameContract = relayFrameContract;
12861
15050
  exports.relayFrameShape = relayFrameShape;
12862
15051
  exports.releaseReader = releaseReader;
15052
+ exports.rememberToolContract = rememberToolContract;
15053
+ exports.rememberToolShape = rememberToolShape;
12863
15054
  exports.removeEntries = removeEntries;
12864
15055
  exports.renderArguments = renderArguments;
12865
15056
  exports.renderCauseChain = renderCauseChain;
12866
15057
  exports.renderFencedFile = renderFencedFile;
12867
15058
  exports.renderIndexEntry = renderIndexEntry;
15059
+ exports.renderIndexLine = renderIndexLine;
12868
15060
  exports.renderJSON = renderJSON;
12869
15061
  exports.renderLedgerAnnotation = renderLedgerAnnotation;
12870
15062
  exports.renderLedgerPinned = renderLedgerPinned;
12871
15063
  exports.renderLedgerRecord = renderLedgerRecord;
12872
15064
  exports.renderLookupCall = renderLookupCall;
12873
15065
  exports.renderSection = renderSection;
15066
+ exports.renderSkillBody = renderSkillBody;
12874
15067
  exports.renderStub = renderStub;
12875
15068
  exports.renderToolValue = renderToolValue;
12876
15069
  exports.renderTopicNames = renderTopicNames;
12877
15070
  exports.requestToRelay = requestToRelay;
12878
15071
  exports.requireAnswer = requireAnswer;
12879
15072
  exports.requireBudget = requireBudget;
15073
+ exports.requireBuiltins = requireBuiltins;
12880
15074
  exports.requireConversation = requireConversation;
12881
15075
  exports.requireConversationSnapshot = requireConversationSnapshot;
12882
15076
  exports.requireCumulative = requireCumulative;
@@ -12888,15 +15082,20 @@ exports.requireKeep = requireKeep;
12888
15082
  exports.requireLimit = requireLimit;
12889
15083
  exports.requireMessageInput = requireMessageInput;
12890
15084
  exports.requireProviderResult = requireProviderResult;
15085
+ exports.requireRecordInput = requireRecordInput;
12891
15086
  exports.requireRole = requireRole;
12892
15087
  exports.requireRules = requireRules;
15088
+ exports.requireScope = requireScope;
12893
15089
  exports.requireSectionsCap = requireSectionsCap;
12894
15090
  exports.requireSummary = requireSummary;
15091
+ exports.requireWorkspace = requireWorkspace;
12895
15092
  exports.resolveDecision = resolveDecision;
15093
+ exports.resolveDisclosure = resolveDisclosure;
12896
15094
  exports.resolvePredict = resolvePredict;
12897
15095
  exports.sanitizeToken = sanitizeToken;
12898
15096
  exports.sanitizeUsage = sanitizeUsage;
12899
15097
  exports.scanAmendments = scanAmendments;
15098
+ exports.scanJobTree = scanJobTree;
12900
15099
  exports.scanJudgmentTies = scanJudgmentTies;
12901
15100
  exports.scanProbability = scanProbability;
12902
15101
  exports.sectionContract = sectionContract;
@@ -12904,12 +15103,16 @@ exports.sectionShape = sectionShape;
12904
15103
  exports.selectRecords = selectRecords;
12905
15104
  exports.settleAgentJob = settleAgentJob;
12906
15105
  exports.settleToolResult = settleToolResult;
15106
+ exports.skillInputContract = skillInputContract;
15107
+ exports.skillInputShape = skillInputShape;
12907
15108
  exports.splitResultLines = splitResultLines;
12908
15109
  exports.splitSentences = splitSentences;
12909
15110
  exports.splitTopic = splitTopic;
12910
15111
  exports.splitWords = splitWords;
12911
15112
  exports.stripThinking = stripThinking;
12912
15113
  exports.sumUsage = sumUsage;
15114
+ exports.switchToolContract = switchToolContract;
15115
+ exports.switchToolShape = switchToolShape;
12913
15116
  exports.tokenUsageShape = tokenUsageShape;
12914
15117
  exports.tokenUsageToJSON = tokenUsageToJSON;
12915
15118
  exports.toolCallShape = toolCallShape;