@orkestrel/agent 0.0.41 → 0.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,70 @@ 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
+ /**
5231
+ * Resolves the workspace a run renders and acts on: the one `selected` names, else the registry's
5232
+ * active one, kept only while `allowed` admits its id.
5233
+ *
5234
+ * @remarks
5235
+ * A selection the registry does not hold resolves to `undefined` and never falls back to the active
5236
+ * workspace. `allowed` is three-way, as {@link import('../helpers.js').filterAllowList} reads it.
5237
+ * `AgentContext.build`, the caller an agent run hands each tool call, and a ledger's calibration
5238
+ * resolve through it, so what a request renders and what a call receives come from one rule.
5239
+ *
5240
+ * @param workspaces - The workspace registry to read
5241
+ * @param selected - The id a `switch` call or the `workspace` option selected, or `undefined`
5242
+ * @param allowed - The ceiling's `workspaces` list (`undefined` ⇒ every id, `[]` ⇒ none)
5243
+ * @returns The workspace, or `undefined` when none resolves
5244
+ *
5245
+ * @example
5246
+ * ```ts
5247
+ * import { resolveWorkspace } from '@orkestrel/agent'
5248
+ *
5249
+ * resolveWorkspace(agent.context.workspaces, 'returns', ['returns']) // the registered returns workspace
5250
+ * ```
5251
+ */
5252
+ function resolveWorkspace(workspaces, selected, allowed) {
5253
+ const workspace = selected === void 0 ? workspaces.active : workspaces.workspace(selected);
5254
+ return workspace !== void 0 && filterAllowList(allowed, [workspace.id], (id) => id).length > 0 ? workspace : void 0;
5255
+ }
5256
+ /**
5023
5257
  * Renders one index line for a memo or a skill — `- name: description`, the built-in `render` of
5024
5258
  * the memo and skill managers.
5025
5259
  *
@@ -5039,37 +5273,219 @@ function intersectKeys(parent, child) {
5039
5273
  * ```
5040
5274
  */
5041
5275
  function renderIndexEntry(entry) {
5042
- return `- ${entry.name}: ${entry.description}`.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, " ");
5276
+ return renderIndexLine(`${entry.name}: ${entry.description}`);
5043
5277
  }
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
5278
  /**
5058
- * Describes a stored memo set exactly: an unknown key refuses.
5279
+ * Renders one index line from its text as `- text`, with each run of line-break code points as one
5280
+ * space.
5059
5281
  *
5060
5282
  * @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);
5283
+ * Pure and total. The memo and skill index lines and the workspace index lines render through it,
5284
+ * so text in an index never opens another line or heading.
5285
+ *
5286
+ * @param text - The line's text
5287
+ * @returns The index line
5288
+ *
5289
+ * @example
5290
+ * ```ts
5291
+ * import { renderIndexLine } from '@orkestrel/agent'
5292
+ *
5293
+ * renderIndexLine('orders') // '- orders'
5294
+ * ```
5295
+ */
5296
+ function renderIndexLine(text) {
5297
+ return `- ${text}`.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, " ");
5298
+ }
5299
+ /**
5300
+ * Renders one loaded skill's body: `### name`, a blank line, then its content.
5301
+ *
5302
+ * @remarks
5303
+ * Pure and total. The context build renders each shown skill through it under the loaded skills
5304
+ * header, last in the system message.
5305
+ *
5306
+ * @param skill - The skill whose name and content the body shows
5307
+ * @returns The body
5308
+ *
5309
+ * @example
5310
+ * ```ts
5311
+ * import { renderSkillBody } from '@orkestrel/agent'
5312
+ *
5313
+ * renderSkillBody({ name: 'refunds', content: 'Check the order first.' })
5314
+ * // '### refunds\n\nCheck the order first.'
5315
+ * ```
5316
+ */
5317
+ function renderSkillBody(skill) {
5318
+ return `### ${skill.name}\n\n${skill.content}`;
5319
+ }
5320
+ /**
5321
+ * Checks a stored record's input against its exact contract, and throws a `ContractError` coded
5322
+ * `'structure'` when it fails.
5323
+ *
5324
+ * @remarks
5325
+ * The input must pass the contract's `is` guard: a missing required member, a member the input type
5326
+ * does not declare, a declared member holding `undefined`, and a member of another type each refuse,
5327
+ * so nothing a refused input carries is stored. A class instance refuses, and the contract reads own
5328
+ * enumerable string keys alone, as every option door does. The contract's `audit` runs only after a refusal, and
5329
+ * the message names the owner, the input type, and every fault with the members to send. The
5330
+ * `context` holds the first fault's `path` and the input type as its `shape`.
5331
+ *
5332
+ * @param input - The input to check
5333
+ * @param contract - The input's compiled contract
5334
+ * @param owner - The entity named at the head of the refusal
5335
+ * @param shape - The input type named in the refusal and its `context`
5336
+ * @throws ContractError Thrown when the input fails the contract (code `'structure'`)
5337
+ *
5338
+ * @example
5339
+ * ```ts
5340
+ * import { memoInputContract, requireRecordInput } from '@orkestrel/agent'
5341
+ *
5342
+ * requireRecordInput({ name: 'tone', description: 'Prefers short replies.', content: 'Answer briefly.' }, memoInputContract, 'MemoManager', 'MemoInput') // returns
5343
+ * requireRecordInput({ name: 'tone', description: undefined, content: 'Answer briefly.' }, memoInputContract, 'MemoManager', 'MemoInput') // throws ContractError ('structure')
5344
+ * ```
5345
+ */
5346
+ function requireRecordInput(input, contract, owner, shape) {
5347
+ if (contract.is(input)) return;
5348
+ const faults = contract.audit(input);
5349
+ 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)}`, {
5350
+ code: "structure",
5351
+ context: {
5352
+ path: faults[0]?.path ?? [],
5353
+ shape
5354
+ }
5355
+ });
5356
+ }
5357
+ // src/core/contexts/instructions/Instruction.ts
5358
+ /**
5359
+ * Represents an immutable named directive — an {@link InstructionInterface} assembled once from
5360
+ * its input (`name` / `content` and an optional `priority`), the `id` minted at
5361
+ * construction. Default: `0` for `priority`.
5362
+ *
5363
+ * @remarks
5364
+ * A thin immutable value object (mirroring {@link import('@orkestrel/tool').Tool}): the
5365
+ * constructor mints a fresh `id` (`crypto.randomUUID()`), copies the input's `name` /
5366
+ * `content`, resolves `priority` to the input's value or `0`, and carries the input's
5367
+ * per-item `override` value when supplied; otherwise the instance owns no `override` member, so
5368
+ * its own keys and a JSON copy carry no member holding `undefined`. The input meets its exact
5369
+ * contract first, so a missing required member, an undeclared member, and a declared member holding
5370
+ * `undefined` refuse with a `ContractError` coded `'structure'`. Never mutated after construction. An
5371
+ * {@link import('./InstructionManager.js').InstructionManager} keys it by `name` and
5372
+ * renders it (highest `priority` first) under its section header.
5373
+ *
5374
+ * @example
5375
+ * ```ts
5376
+ * const instruction = new Instruction({ name: 'tone', content: 'Be concise.', priority: 5 })
5377
+ * instruction.priority // 5
5378
+ * ```
5379
+ */
5380
+ var Instruction = class {
5381
+ id;
5382
+ name;
5383
+ content;
5384
+ priority;
5385
+ constructor(input) {
5386
+ requireRecordInput(input, instructionInputContract, "Instruction", "InstructionInput");
5387
+ this.id = crypto.randomUUID();
5388
+ this.name = input.name;
5389
+ this.content = input.content;
5390
+ this.priority = input.priority ?? 0;
5391
+ if (input.override !== void 0) this.override = input.override;
5392
+ }
5393
+ };
5394
+ // src/core/contexts/instructions/InstructionManager.ts
5395
+ /**
5396
+ * Registers the immutable {@link Instruction} instances a richer context assembles a directives block
5397
+ * from — keyed by `name` so adding an instruction whose name is already stored overwrites it,
5398
+ * last write wins, and listed by descending `priority`, carrying the `open` / `render` / `close` build contract and an observable `emitter`.
5399
+ *
5400
+ * @remarks
5401
+ * - **Registry.** Instructions live in an insertion-ordered `Map` keyed by `name`;
5402
+ * `add` takes one {@link InstructionInput} or a batch, mints each instruction's
5403
+ * `id`, and adding an instruction whose name is already stored overwrites it (last write wins).
5404
+ * `count` is the map size, `instruction(name)` looks one up, and `instructions()` lists them sorted by
5405
+ * descending `priority` (a stable sort, so equal priorities keep insertion order).
5406
+ * - **Build contract (the whole format cascade).** `open` is the section header a context
5407
+ * renders the instructions under, `render(instruction)` renders one instruction, and
5408
+ * `close` is the line after them. Each resolves the cascade most-specific-first: `render`
5409
+ * returns the instruction's {@link InstructionInput.override}, else the
5410
+ * `InstructionManagerOptions.format` `render`, else its `content`; `open` returns the
5411
+ * options `open`, else the built-in header; `close` returns the options `close`, else
5412
+ * `undefined`. A context reads the three and frames the section from them (see
5413
+ * {@link import('../AgentContext.js').AgentContext}).
5414
+ * - **Removal.** `remove` drops one by name, or a batch — `true` only when every supplied
5415
+ * name was removed; `clear` empties the registry.
5416
+ * - **Observable.** The owned {@link emitter} ({@link InstructionManagerEventMap})
5417
+ * carries `add` (the created instruction) / `remove` (the name) / `clear` for
5418
+ * fire-and-forget observers. Every event is emitted directly, strictly after the map
5419
+ * mutation completes; the emitter isolates a listener throw and routes it to its `error`
5420
+ * handler (the `error` option), so a buggy observer can never corrupt a mutation.
5421
+ *
5422
+ * @example
5423
+ * ```ts
5424
+ * const manager = new InstructionManager()
5425
+ * manager.add([
5426
+ * { name: 'tone', content: 'Be concise.', priority: 1 },
5427
+ * { name: 'safety', content: 'Refuse unsafe requests.', priority: 10 },
5428
+ * ])
5429
+ * manager.instructions().map((one) => one.name) // ['safety', 'tone'] — highest priority first
5430
+ * ```
5431
+ */
5432
+ var InstructionManager = class {
5433
+ #instructions = /* @__PURE__ */ new Map();
5434
+ #emitter;
5435
+ #format;
5436
+ constructor(options) {
5437
+ (0, _orkestrel_contract.refuseOptionKeys)(options, INSTRUCTION_MANAGER_KEYS, "InstructionManager", "InstructionManagerOptions");
5438
+ this.#emitter = new _orkestrel_emitter.Emitter(options);
5439
+ this.#format = options?.format;
5440
+ }
5441
+ get emitter() {
5442
+ return this.#emitter;
5443
+ }
5444
+ get count() {
5445
+ return this.#instructions.size;
5446
+ }
5447
+ get open() {
5448
+ return this.#format?.open ?? "## Instructions";
5449
+ }
5450
+ get close() {
5451
+ return this.#format?.close;
5452
+ }
5453
+ add(input) {
5454
+ if ((0, _orkestrel_contract.isArray)(input)) {
5455
+ input.forEach((one) => requireRecordInput(one, instructionInputContract, "Instruction", "InstructionInput"));
5456
+ return input.map((one) => this.#create(one));
5457
+ }
5458
+ return this.#create(input);
5459
+ }
5460
+ instruction(name) {
5461
+ return this.#instructions.get(name);
5462
+ }
5463
+ instructions() {
5464
+ return [...this.#instructions.values()].sort((a, b) => b.priority - a.priority);
5465
+ }
5466
+ render(instruction) {
5467
+ return instruction.override ?? this.#format?.render?.(instruction) ?? instruction.content;
5468
+ }
5469
+ remove(names) {
5470
+ if ((0, _orkestrel_contract.isArray)(names)) return removeEntries(names, (name) => this.#delete(name));
5471
+ return this.#delete(names);
5472
+ }
5473
+ clear() {
5474
+ this.#instructions.clear();
5475
+ this.#emitter.emit("clear");
5476
+ }
5477
+ #create(input) {
5478
+ const instruction = new Instruction(input);
5479
+ this.#instructions.set(instruction.name, instruction);
5480
+ this.#emitter.emit("add", instruction);
5481
+ return instruction;
5482
+ }
5483
+ #delete(name) {
5484
+ const removed = this.#instructions.delete(name);
5485
+ if (removed) this.#emitter.emit("remove", name);
5486
+ return removed;
5487
+ }
5488
+ };
5073
5489
  // src/core/contexts/validators.ts
5074
5490
  /**
5075
5491
  * Checks whether a value is a memo's origin: a record with a string `conversation` and an optional
@@ -5150,6 +5566,35 @@ function isMemoSnapshot(value) {
5150
5566
  return (0, _orkestrel_contract.isString)(id) && (0, _orkestrel_contract.arrayOf)(isMemo)(memos);
5151
5567
  });
5152
5568
  }
5569
+ /**
5570
+ * Checks whether a value implements `ScopeInterface`; total, each member read one time, any
5571
+ * implementation admitted.
5572
+ *
5573
+ * @remarks
5574
+ * A scope is a string `id` and `name`, each of `instructions`, `skills`, `memos`, `tools`, `files`,
5575
+ * and `workspaces` absent or a dense array of strings, `select` absent or a function, and a `narrow`
5576
+ * function. The guard reads each of those members one time and never calls a method. It admits
5577
+ * unknown members, a class instance, and a transparent proxy, and returns false for a throwing
5578
+ * getter, a revoked proxy, and any other malformed input.
5579
+ *
5580
+ * @param value - The value to test
5581
+ * @returns True if `value` carries the members of a {@link ScopeInterface}; false otherwise
5582
+ *
5583
+ * @example
5584
+ * ```ts
5585
+ * import { createScope, isScope } from '@orkestrel/agent'
5586
+ *
5587
+ * isScope(createScope({ name: 'reader', tools: ['search'] })) // true
5588
+ * isScope({ id: 's1', name: 'reader' }) // false
5589
+ * ```
5590
+ */
5591
+ function isScope(value) {
5592
+ return (0, _orkestrel_contract.holds)(() => {
5593
+ if (!(0, _orkestrel_contract.isObject)(value)) return false;
5594
+ const list = (0, _orkestrel_contract.optionalOf)((0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString));
5595
+ 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"));
5596
+ });
5597
+ }
5153
5598
  // src/core/contexts/memos/MemoManager.ts
5154
5599
  /**
5155
5600
  * Registers the revisions of a memo set — durable facts keyed by `name`, where a same-name `add`
@@ -5197,6 +5642,7 @@ var MemoManager = class {
5197
5642
  * @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
5643
  */
5199
5644
  constructor(options) {
5645
+ (0, _orkestrel_contract.refuseOptionKeys)(options, MEMO_MANAGER_KEYS, "MemoManager", "MemoManagerOptions");
5200
5646
  const supplied = options?.snapshot;
5201
5647
  const owned = supplied === void 0 ? void 0 : copyJSON(supplied);
5202
5648
  const snapshot = isMemoSnapshot(owned) ? owned : void 0;
@@ -5225,6 +5671,7 @@ var MemoManager = class {
5225
5671
  return this.#format?.close;
5226
5672
  }
5227
5673
  add(input) {
5674
+ ((0, _orkestrel_contract.isArray)(input) ? input : [input]).forEach((one) => requireRecordInput(one, memoInputContract, "MemoManager", "MemoInput"));
5228
5675
  if ((0, _orkestrel_contract.isArray)(input)) return input.map((one) => this.#append(one));
5229
5676
  return this.#append(input);
5230
5677
  }
@@ -5283,9 +5730,9 @@ var MemoManager = class {
5283
5730
  // src/core/contexts/scopes/Scope.ts
5284
5731
  /**
5285
5732
  * 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:
5733
+ * category (`instructions` / `skills` / `memos` / `tools` / `files` / `workspaces`), each keyed by
5734
+ * that category's identity (an instruction's, a skill's, a memo's, or a tool's `name`, a workspace
5735
+ * file's `path`, a workspace's `id`) and read as an allow-list:
5289
5736
  * `undefined` lets everything pass, `[]` lets nothing pass, and a non-empty list passes the listed
5290
5737
  * keys alone. `narrow` composes a tighter child by set intersection.
5291
5738
  *
@@ -5303,6 +5750,10 @@ var MemoManager = class {
5303
5750
  * `undefined` with a list yields the list, and `undefined` with `undefined` stays
5304
5751
  * `undefined`. Narrowing can only tighten, never widen — a key excluded by a parent
5305
5752
  * can never be re-admitted by a child. The child keeps this scope's `name` and `select` handler.
5753
+ * - **Exact.** The constructor refuses an own enumerable member of its input outside
5754
+ * `SCOPE_KEYS`, and `narrow` one of its filter outside `SCOPE_FILTER_KEYS`, with a
5755
+ * `ContractError` coded `'structure'`, so a misspelled list never builds an open scope. A
5756
+ * declared member holding `undefined` reads as omitted.
5306
5757
  *
5307
5758
  * @example
5308
5759
  * ```ts
@@ -5322,8 +5773,10 @@ var Scope = class Scope {
5322
5773
  memos;
5323
5774
  tools;
5324
5775
  files;
5776
+ workspaces;
5325
5777
  select;
5326
5778
  constructor(input) {
5779
+ (0, _orkestrel_contract.refuseOptionKeys)(input, SCOPE_KEYS, "Scope", "ScopeInput");
5327
5780
  this.name = input.name;
5328
5781
  if (input.select !== void 0) this.select = input.select;
5329
5782
  if (input.instructions !== void 0) this.instructions = [...input.instructions];
@@ -5331,21 +5784,14 @@ var Scope = class Scope {
5331
5784
  if (input.memos !== void 0) this.memos = [...input.memos];
5332
5785
  if (input.tools !== void 0) this.tools = [...input.tools];
5333
5786
  if (input.files !== void 0) this.files = [...input.files];
5787
+ if (input.workspaces !== void 0) this.workspaces = [...input.workspaces];
5334
5788
  }
5335
5789
  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);
5790
+ (0, _orkestrel_contract.refuseOptionKeys)(config, SCOPE_FILTER_KEYS, "Scope", "ScopeFilter");
5341
5791
  return new Scope({
5342
5792
  name: this.name,
5343
5793
  ...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 }
5794
+ ...intersectFilters(this, config)
5349
5795
  });
5350
5796
  }
5351
5797
  };
@@ -5382,6 +5828,7 @@ var ScopeManager = class {
5382
5828
  #scopes = /* @__PURE__ */ new Map();
5383
5829
  #emitter;
5384
5830
  constructor(options) {
5831
+ (0, _orkestrel_contract.refuseOptionKeys)(options, SCOPE_MANAGER_KEYS, "ScopeManager", "ScopeManagerOptions");
5385
5832
  this.#emitter = new _orkestrel_emitter.Emitter(options);
5386
5833
  }
5387
5834
  get emitter() {
@@ -5455,6 +5902,7 @@ var SkillManager = class {
5455
5902
  #emitter;
5456
5903
  #format;
5457
5904
  constructor(options) {
5905
+ (0, _orkestrel_contract.refuseOptionKeys)(options, SKILL_MANAGER_KEYS, "SkillManager", "SkillManagerOptions");
5458
5906
  this.#emitter = new _orkestrel_emitter.Emitter(options);
5459
5907
  this.#format = options?.format;
5460
5908
  }
@@ -5471,8 +5919,12 @@ var SkillManager = class {
5471
5919
  return this.#format?.close;
5472
5920
  }
5473
5921
  add(input) {
5474
- if ((0, _orkestrel_contract.isArray)(input)) return input.map((one) => this.#create(one));
5475
- return this.#create(input);
5922
+ if ((0, _orkestrel_contract.isArray)(input)) {
5923
+ const skills = input.map((one) => this.#mint(one));
5924
+ skills.forEach((skill) => this.#store(skill));
5925
+ return skills;
5926
+ }
5927
+ return this.#store(this.#mint(input));
5476
5928
  }
5477
5929
  skill(name) {
5478
5930
  return this.#skills.get(name);
@@ -5491,14 +5943,28 @@ var SkillManager = class {
5491
5943
  this.#skills.clear();
5492
5944
  this.#emitter.emit("clear");
5493
5945
  }
5494
- #create(input) {
5495
- const skill = {
5946
+ #mint(input) {
5947
+ const given = (0, _orkestrel_contract.isRecord)(input) ? input.tools : void 0;
5948
+ const tools = (0, _orkestrel_contract.isArray)(given) ? Array.from(given) : given;
5949
+ 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", {
5950
+ code: "placement",
5951
+ context: {
5952
+ path: ["input", "tools"],
5953
+ limit: "readonly string[]",
5954
+ received: (0, _orkestrel_contract.preview)(given)
5955
+ }
5956
+ });
5957
+ requireRecordInput(input, skillInputContract, "SkillManager", "SkillInput");
5958
+ return {
5496
5959
  id: crypto.randomUUID(),
5497
5960
  name: input.name,
5498
5961
  description: input.description,
5499
5962
  content: input.content,
5500
- files: (input.files ?? []).map((file) => (0, _orkestrel_workspace.createFile)(file))
5963
+ files: (input.files ?? []).map((file) => (0, _orkestrel_workspace.createFile)(file)),
5964
+ tools: tools === void 0 ? [] : tools
5501
5965
  };
5966
+ }
5967
+ #store(skill) {
5502
5968
  this.#skills.set(skill.name, skill);
5503
5969
  this.#emitter.emit("add", skill);
5504
5970
  return skill;
@@ -5695,8 +6161,9 @@ var DatabaseMemoStore = class {
5695
6161
  * {@link ConversationManagerInterface} message source whose active conversation is `messages`, the
5696
6162
  * {@link ToolManagerInterface} registry, and an active {@link ScopeInterface} changed through
5697
6163
  * {@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`.
6164
+ * the skills index, and the shown skills' bodies into one system message, the memos index and the
6165
+ * rendered workspace's text into one `user` context message after it, then the conversation, and
6166
+ * never reads `tools`.
5700
6167
  *
5701
6168
  * @remarks
5702
6169
  * - **Composition.** `system` is the optional system prompt; `instructions` / `skills` / `memos` /
@@ -5715,40 +6182,43 @@ var DatabaseMemoStore = class {
5715
6182
  * reference, no duplication), and `build()` with no selection folds that conversation's `view()`
5716
6183
  * (its per-section summaries + live tail) as the authoritative message inclusion — the scope does
5717
6184
  * 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
6185
+ * instructions / skills / memos / tools / workspace files / workspaces). An agent run binds a conversation at
5719
6186
  * its call, the active one unless the run names another, and builds through a selection over that
5720
6187
  * conversation, so `conversations.switch(id)` sets the next run's default at any moment and never
5721
6188
  * moves a live run; `select(conversation, request, signal)` checks the conversation it is given.
5722
6189
  * - **`build()` — the scoped assembly + the format cascade.** It folds, in order,
5723
6190
  * the system prompt, the scope-filtered instructions (a block: the section's `open` text,
5724
6191
  * 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
6192
+ * whose entries join with a single newline), then the shown skills' bodies, into one leading
6193
+ * `system` message (prepended only when at least one part exists), then the scope-filtered memos
6194
+ * index (the same block), the listed workspace ids, and the rendered workspace's text files into
6195
+ * one `user` context message (present only when one of those sections renders), then
5729
6196
  * appends the active conversation's `view()` (the conversation owns message inclusion through
5730
6197
  * compaction — the scope does not filter the conversation). The instruction manager resolves
5731
6198
  * each `open` / item / `close` most-specific-first: `open` = manager-options-override >
5732
6199
  * built-in; per item = item-override > manager-options-override > built-in; `close` =
5733
6200
  * manager-options-override (no built-in ⇒ no closing line when unset) (see
5734
6201
  * {@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
6202
+ * header + items, no closing line. The rendered workspace's scoped-in image files' `base64` payload is attached to
5736
6203
  * the last user message (a vision provider reads images off a user turn); when no user message
5737
6204
  * 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
6205
+ * always reflects the current managers / messages / scope / workspaces; it never mutates a
5739
6206
  * manager or the stored messages. `build(selection)` adds at most one `system` message of its own
5740
6207
  * and reads the selection's messages as conversation parts without a role check; the select path
5741
6208
  * refuses a selected `system` role as a `'ROLE'` fault over `view()`; a host's direct
5742
6209
  * `build(selection)` is host-trusted, and a conversation a host implements itself whose `view()`
5743
6210
  * 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`,
6211
+ * - **The rendered workspace, by carrier — the sole document/image context.**
6212
+ * The rendered workspace has its {@link import('@orkestrel/workspace').FileInterface} values scope-filtered by `scope.files`,
5746
6213
  * then split: text files fold into a dedicated `## Workspace` section (fenced reference blocks)
5747
6214
  * in the context message — a `user` message with a minted id, never stored — and image files'
5748
6215
  * `base64` payload attaches to the last user message of the conversation, never to the context
5749
6216
  * 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` /
6217
+ * instructions, the skills index, and last the bodies of the skills `options.disclosure` shows.
6218
+ * One workspace renders: the one `options.disclosure` selects, else the active one, and only
6219
+ * while the scope's `workspaces` list admits it; with neither, nothing renders for workspaces.
6220
+ * `options.switchable` adds the admitted workspace ids to the context message, after the memos
6221
+ * index. `build()` never moves the active workspace, and it owns this render (a `Workspace` /
5752
6222
  * `WorkspaceManager` stays file-focused).
5753
6223
  * - **Tools are structural, not in the prompt.** The registry is advertised to the provider
5754
6224
  * through `tools.definitions()` (scope-filtered by the loop), never serialized into the
@@ -5775,6 +6245,7 @@ var AgentContext = class {
5775
6245
  #select;
5776
6246
  #scope;
5777
6247
  constructor(options) {
6248
+ (0, _orkestrel_contract.refuseOptionKeys)(options, CONTEXT_OPTION_KEYS, "AgentContext", "AgentContextOptions");
5778
6249
  this.#system = options?.system;
5779
6250
  this.#instructions = options?.instructions ?? new InstructionManager();
5780
6251
  this.#skills = options?.skills ?? new SkillManager();
@@ -5816,31 +6287,44 @@ var AgentContext = class {
5816
6287
  apply(scope) {
5817
6288
  this.#scope = scope;
5818
6289
  }
5819
- select(conversation, request, signal) {
5820
- const handler = this.#scope?.select ?? this.#select;
6290
+ select(conversation, request, signal, scope) {
6291
+ const handler = scope?.select ?? this.#scope?.select ?? this.#select;
5821
6292
  if (handler === void 0) return void 0;
5822
6293
  return this.#check(handler, conversation, request, signal);
5823
6294
  }
5824
- build(selection) {
5825
- const scope = this.#scope;
6295
+ build(options) {
6296
+ (0, _orkestrel_contract.refuseOptionKeys)(options, BUILD_OPTION_KEYS, "AgentContext", "ContextBuildOptions");
6297
+ const scope = intersectFilters(this.#scope, options?.scope);
5826
6298
  const parts = [];
5827
6299
  if (this.#system !== void 0) parts.push(this.#system);
5828
- const instructions = filterAllowList(scope?.instructions, this.#instructions.instructions(), (one) => one.name);
6300
+ const instructions = filterAllowList(scope.instructions, this.#instructions.instructions(), (one) => one.name);
5829
6301
  const instructed = renderSection(this.#instructions.open, instructions, (one) => this.#instructions.render(one), this.#instructions.close);
5830
6302
  if (instructed !== void 0) parts.push(instructed);
5831
- const skills = filterAllowList(scope?.skills, this.#skills.skills(), (one) => one.name);
6303
+ const skills = filterAllowList(scope.skills, this.#skills.skills(), (one) => one.name);
5832
6304
  const indexed = renderSection(this.#skills.open, skills.length === 0 ? [] : [skills.map((one) => this.#skills.render(one)).join("\n")], (block) => block, this.#skills.close);
5833
6305
  if (indexed !== void 0) parts.push(indexed);
5834
- const files = filterAllowList(scope?.files, this.#workspaces.active?.files() ?? [], (one) => one.path);
6306
+ const admitted = new Map(skills.map((one) => [one.name, one]));
6307
+ const bodies = renderSection(LOADED_SECTION_HEADER, [...new Set(options?.disclosure?.skills ?? [])].flatMap((name) => {
6308
+ const skill = admitted.get(name);
6309
+ return skill === void 0 || skill.content.trim() === "" ? [] : [skill];
6310
+ }), renderSkillBody, void 0);
6311
+ if (bodies !== void 0) parts.push(bodies);
6312
+ const workspace = resolveWorkspace(this.#workspaces, options?.disclosure?.workspace, scope.workspaces);
6313
+ const files = filterAllowList(scope.files, workspace?.files() ?? [], (one) => one.path);
5835
6314
  const documented = renderSection(WORKSPACE_SECTION_HEADER, files.flatMap((file) => (0, _orkestrel_workspace.isTextContent)(file.content) ? [{
5836
6315
  path: file.path,
5837
6316
  language: file.content.language,
5838
6317
  text: file.content.text
5839
6318
  }] : []), (file) => renderFencedFile(file.path, file.language, file.text), void 0);
5840
- const memos = filterAllowList(scope?.memos, this.#memos.memos(), (one) => one.name);
6319
+ const memos = filterAllowList(scope.memos, this.#memos.memos(), (one) => one.name);
5841
6320
  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);
6321
+ const tail = attachUserImages(options?.selection?.messages ?? this.#ensureConversation().view(), collectImageData(files));
6322
+ const switchable = options?.switchable === true ? filterAllowList(scope.workspaces, this.#workspaces.workspaces(), (one) => one.id) : [];
6323
+ const slots = [
6324
+ remembered,
6325
+ renderSection(WORKSPACE_INDEX_HEADER, switchable.length === 0 ? [] : [switchable.map((one) => renderIndexLine(one.id)).join("\n")], (block) => block, void 0),
6326
+ documented
6327
+ ].filter((slot) => slot !== void 0);
5844
6328
  const context = slots.length === 0 ? [] : [{
5845
6329
  id: crypto.randomUUID(),
5846
6330
  role: "user",
@@ -6161,6 +6645,17 @@ function createAgentContext(options) {
6161
6645
  */
6162
6646
  var DEFAULT_AGENT_LIMIT = 10;
6163
6647
  /**
6648
+ * Opens a job tree finding that refuses a whole job, which sets it apart from a finding that names an exceeded member.
6649
+ *
6650
+ * @remarks
6651
+ * The `scanJobTree` helper writes it before the path of a job the contract refuses or a member read
6652
+ * makes unreadable, and the `AgentFanout` class reads it to open its refusal with the rule that
6653
+ * finding breaks. An
6654
+ * exceeded member's finding opens with `job`, so text later in a finding cannot pass for this
6655
+ * prefix.
6656
+ */
6657
+ var JOB_REFUSAL_PREFIX = "refused ";
6658
+ /**
6164
6659
  * Holds the model-facing texts the loop writes after a tool name in a denial result — the
6165
6660
  * `denied` note for a deny with no reason, an evaluate throw, an invalid decision, or a tool
6166
6661
  * changed after its decision; the `refused` note for an ask the approver refused; and the
@@ -6172,8 +6667,74 @@ var AUTHORITY_NOTES = Object.freeze({
6172
6667
  unanswered: "it needs approval and none was given. Do not call it again; tell the user it needs approval."
6173
6668
  });
6174
6669
  /**
6670
+ * Holds the texts the built-ins return on success — `loaded`, `unloaded`, `remembered`, and
6671
+ * `switched` — frozen.
6672
+ *
6673
+ * @remarks
6674
+ * `loaded`, `unloaded`, and `switched` are the texts the conversation's stored tool messages are
6675
+ * matched against to derive what a run shows, so each is one fixed constant. A stored message
6676
+ * counts only under a call to a built-in the run enables.
6677
+ */
6678
+ var BUILTIN_NOTES = Object.freeze({
6679
+ loaded: "The skill is loaded. Its instructions and tools are yours from your next step.",
6680
+ unloaded: "The skill is unloaded. Its instructions and tools leave from your next step.",
6681
+ remembered: "The memo is saved. Continue with what you were doing.",
6682
+ switched: "The workspace is selected. Its files are yours from your next step."
6683
+ });
6684
+ /**
6685
+ * Caps each built-in argument in code points, as the built-in argument shapes count them — a `name`
6686
+ * (a skill name, a memo name, or a workspace id), a memo `description`, and a memo `content` — and
6687
+ * the `candidates` one refusal lists, frozen.
6688
+ */
6689
+ var BUILTIN_LIMITS = Object.freeze({
6690
+ name: 128,
6691
+ description: 200,
6692
+ content: 4e3,
6693
+ candidates: 20
6694
+ });
6695
+ /**
6696
+ * Declares each built-in tool's model-facing description and annotations, keyed by its
6697
+ * {@link AgentBuiltin} name, frozen.
6698
+ *
6699
+ * @remarks
6700
+ * `memo` is `pure` and `untrusted`, because it returns memo text a model can have written;
6701
+ * `remember` is `consequential`, so an `allow` verdict becomes an `ask`. A run's own tools and the
6702
+ * definitions `collectBuiltinDefinitions` returns both read their text and annotations here.
6703
+ */
6704
+ var BUILTIN_TOOLS = Object.freeze({
6705
+ 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." }),
6706
+ 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." }),
6707
+ memo: Object.freeze({
6708
+ 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.",
6709
+ annotations: Object.freeze({
6710
+ pure: true,
6711
+ untrusted: true
6712
+ })
6713
+ }),
6714
+ remember: Object.freeze({
6715
+ 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.",
6716
+ annotations: Object.freeze({ consequential: true })
6717
+ }),
6718
+ 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." })
6719
+ });
6720
+ /**
6721
+ * Matches one control, format, line-separator, or paragraph-separator code point — the class
6722
+ * `renderArguments` encodes and a memo's one-line name and description may not hold, frozen.
6723
+ *
6724
+ * @remarks
6725
+ * Not global, so a `test` call keeps no state between calls; a global replace builds its own
6726
+ * global copy from `source`.
6727
+ */
6728
+ var ARGUMENT_CONTROL_PATTERN = Object.freeze(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u);
6729
+ /**
6730
+ * Matches a text that is blank: empty after white space and default-ignorable code points, such
6731
+ * as U+3164, are removed, frozen.
6732
+ */
6733
+ var ARGUMENT_BLANK_PATTERN = Object.freeze(/^[\p{White_Space}\p{Default_Ignorable_Code_Point}]*$/u);
6734
+ /**
6175
6735
  * 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.
6736
+ * outside the turn's tool ceiling, the context scope's tool allow-list intersected with the run
6737
+ * scope's, or whose name no definition advertises this turn.
6177
6738
  */
6178
6739
  var SCOPE_NOTE = "it is not one of the listed tools. Use a listed tool or answer with what you have.";
6179
6740
  /**
@@ -6192,6 +6753,92 @@ var MESSAGE_TOKEN_OVERHEAD = 4;
6192
6753
  * exact count.
6193
6754
  */
6194
6755
  var IMAGE_TOKEN_ESTIMATE = 512;
6756
+ /**
6757
+ * Lists the members `AgentOptions` declares, frozen — the keys the `Agent` constructor admits.
6758
+ *
6759
+ * @remarks
6760
+ * The constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
6761
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
6762
+ */
6763
+ var AGENT_OPTION_KEYS = Object.freeze([
6764
+ "on",
6765
+ "error",
6766
+ "system",
6767
+ "tools",
6768
+ "instructions",
6769
+ "skills",
6770
+ "memos",
6771
+ "workspaces",
6772
+ "scope",
6773
+ "limit",
6774
+ "timeout",
6775
+ "budget",
6776
+ "allowance",
6777
+ "scheduler",
6778
+ "signal",
6779
+ "authority",
6780
+ "conversations",
6781
+ "window",
6782
+ "strict",
6783
+ "select",
6784
+ "builtins",
6785
+ "workspace"
6786
+ ]);
6787
+ /**
6788
+ * Lists the members `AgentRunOptions` declares, frozen — the keys the `stream` and `generate`
6789
+ * methods admit.
6790
+ *
6791
+ * @remarks
6792
+ * The `stream` method passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
6793
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
6794
+ */
6795
+ var AGENT_RUN_KEYS = Object.freeze([
6796
+ "think",
6797
+ "schema",
6798
+ "limit",
6799
+ "timeout",
6800
+ "budget",
6801
+ "allowance",
6802
+ "signal",
6803
+ "conversation",
6804
+ "prompt",
6805
+ "scope",
6806
+ "workspace"
6807
+ ]);
6808
+ /** Lists the members `AuthorityOptions` declares, frozen — the keys the `Authority` constructor admits. */
6809
+ var AUTHORITY_OPTION_KEYS = Object.freeze([
6810
+ "rules",
6811
+ "fallback",
6812
+ "ask"
6813
+ ]);
6814
+ /** Lists the members `AgentRegistryOptions` declares, frozen — the keys the `AgentRegistry` constructor admits. */
6815
+ var REGISTRY_OPTION_KEYS = Object.freeze([
6816
+ "providers",
6817
+ "tools",
6818
+ "authorities",
6819
+ "schedulers",
6820
+ "store",
6821
+ "skills",
6822
+ "memos",
6823
+ "workspaces"
6824
+ ]);
6825
+ /** Lists the members `AgentQueueOptions` declares, frozen — the keys `createAgentQueue` admits. */
6826
+ var QUEUE_OPTION_KEYS = Object.freeze([
6827
+ "registry",
6828
+ "partial",
6829
+ "concurrency",
6830
+ "retries",
6831
+ "timeout",
6832
+ "store"
6833
+ ]);
6834
+ /** Lists the members `AgentRunnerOptions` declares, frozen — the keys `createAgentRunner` admits. */
6835
+ var RUNNER_OPTION_KEYS = Object.freeze([
6836
+ "registry",
6837
+ "partial",
6838
+ "concurrency",
6839
+ "retries",
6840
+ "timeout"
6841
+ ]);
6195
6842
  // src/core/agents/errors.ts
6196
6843
  /**
6197
6844
  * Reports an {@link AgentInterface} run that ended {@link AgentResult.partial} under a
@@ -6275,6 +6922,9 @@ function isAgentJobError(value) {
6275
6922
  * `'AUTHORITY'` reports an `Authority` built with no valid `fallback`, a rule with no valid `verdict`
6276
6923
  * (its `context` names the `rule` index), and an agent built with `tools` and no `authority`. Each
6277
6924
  * throws at construction, with the options a host passes to correct it in the message.
6925
+ * `'BUILTIN'` reports a `builtins` entry that is not a built-in or repeats one, `unload` enabled
6926
+ * without `load`, and a tool manager holding a tool under an enabled built-in's name, each thrown
6927
+ * at construction.
6278
6928
  * `'CONCURRENCY'` reports a run started while another run on the same agent is live, when the
6279
6929
  * agent carries a `window` (a shared context budget) or when the run's effective `budget` or
6280
6930
  * `allowance` is an instance a live run holds — the second run's `start()` would replace that
@@ -6283,18 +6933,18 @@ function isAgentJobError(value) {
6283
6933
  * `agent` id, and the `conversation` id for a shared conversation. For concurrent runs, use
6284
6934
  * separate agents, or one agent with no `window`, distinct budget and allowance instances per
6285
6935
  * 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
6936
+ * `'LIMIT'` reports a construction or per-run `limit` that is not a positive safe integer.
6937
+ * `'REGISTRY'` reports a rehydration name absent from its registry pool, on `provider` / `tool` /
6938
+ * `authority` / `scheduler` / `build`, whose `context` names the `category` and the `name`, and a
6939
+ * job `build` refuses as a record, whose `context` holds the contract's `faults` or the
6940
+ * `refusals` lines `collectJobRefusals` returns. `'WINDOW'` reports a `strict` run whose
6291
6941
  * complete provider request is still over the `window` budget, equal to its `max` included, after
6292
6942
  * the one permitted fold or with no fold possible; it settles the run `error` before the provider
6293
6943
  * is called, and its `context` holds the `agent` id, the measured `consumed`, and the window `max`.
6294
6944
  * Narrow a caught value with {@link isAgentError} and branch on `error.code`.
6295
6945
  */
6296
6946
  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. */
6947
+ /** 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
6948
  code;
6299
6949
  /** Holds the values that locate the failure, such as the `agent` id of a refused run, or `undefined` when none were supplied. */
6300
6950
  context;
@@ -6333,7 +6983,7 @@ function isAgentError(value) {
6333
6983
  return (0, _orkestrel_contract.holds)(() => {
6334
6984
  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
6985
  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;
6986
+ if (!(code === "APPROVER" || code === "AUTHORITY" || code === "BUILTIN" || code === "CONCURRENCY" || code === "LIMIT" || code === "REGISTRY" || code === "SCHEMA" || code === "WINDOW")) return false;
6337
6987
  const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
6338
6988
  return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
6339
6989
  });
@@ -6344,20 +6994,45 @@ function isAgentError(value) {
6344
6994
  * refuses instead of running an agent without it.
6345
6995
  *
6346
6996
  * @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.
6997
+ * `children` holds JSON records, because each child crosses this shape at its own `build`. Each
6998
+ * bound states what the build reads: `limit` an integer from 1 through `Number.MAX_SAFE_INTEGER`,
6999
+ * `timeout` an integer from 0 through `MAX_TIMEOUT_MS`, and `budget` a number of at least 0. A seed
7000
+ * message plays a `ConversationRole`, so a `system` message refuses here. A `scope` holds the
7001
+ * `skills`, `memos`, and `workspaces` lists, each required, and no other member; `builtins` admits
7002
+ * only the names of `AgentBuiltin`, and `collectJobRefusals` reads what a list of them cannot state.
6350
7003
  */
6351
7004
  var agentJobShape = (0, _orkestrel_contract.objectShape)({
6352
7005
  provider: (0, _orkestrel_contract.stringShape)(),
6353
- messages: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(messageInputShape)),
7006
+ messages: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
7007
+ ...messageInputShape.properties,
7008
+ role: (0, _orkestrel_contract.literalShape)(CONVERSATION_ROLES)
7009
+ }))),
6354
7010
  system: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
6355
7011
  tools: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)())),
6356
7012
  authority: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
6357
7013
  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)()),
7014
+ limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
7015
+ min: 1,
7016
+ max: Number.MAX_SAFE_INTEGER
7017
+ })),
7018
+ timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
7019
+ min: 0,
7020
+ max: _orkestrel_timeout.MAX_TIMEOUT_MS
7021
+ })),
7022
+ budget: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)({ min: 0 })),
7023
+ scope: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
7024
+ skills: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()),
7025
+ memos: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)()),
7026
+ workspaces: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)())
7027
+ })),
7028
+ workspace: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
7029
+ builtins: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.literalShape)([
7030
+ "load",
7031
+ "unload",
7032
+ "memo",
7033
+ "remember",
7034
+ "switch"
7035
+ ]))),
6361
7036
  children: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)())))
6362
7037
  });
6363
7038
  /**
@@ -6427,6 +7102,74 @@ var agentAnswerShape = (0, _orkestrel_contract.objectShape)({
6427
7102
  id: (0, _orkestrel_contract.stringShape)(),
6428
7103
  approved: (0, _orkestrel_contract.booleanShape)()
6429
7104
  });
7105
+ /**
7106
+ * Describes the arguments of the `load` and `unload` built-ins — one `skill` name, non-empty and
7107
+ * at most `BUILTIN_LIMITS.name` code points.
7108
+ *
7109
+ * @remarks
7110
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7111
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7112
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7113
+ */
7114
+ var loadToolShape = (0, _orkestrel_contract.objectShape)({ skill: (0, _orkestrel_contract.stringShape)({
7115
+ min: 1,
7116
+ max: BUILTIN_LIMITS.name,
7117
+ description: "A skill name from the skills index"
7118
+ }) });
7119
+ /**
7120
+ * Describes the arguments of the `memo` built-in — one memo `name`, non-empty and at most
7121
+ * `BUILTIN_LIMITS.name` code points.
7122
+ *
7123
+ * @remarks
7124
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7125
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7126
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7127
+ */
7128
+ var memoToolShape = (0, _orkestrel_contract.objectShape)({ name: (0, _orkestrel_contract.stringShape)({
7129
+ min: 1,
7130
+ max: BUILTIN_LIMITS.name,
7131
+ description: "A memo name from the memos index"
7132
+ }) });
7133
+ /**
7134
+ * Describes the arguments of the `remember` built-in — a memo `name`, the one-line `description`
7135
+ * the memos index shows, and the `content`, each non-empty and within its `BUILTIN_LIMITS` cap.
7136
+ *
7137
+ * @remarks
7138
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7139
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7140
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7141
+ */
7142
+ var rememberToolShape = (0, _orkestrel_contract.objectShape)({
7143
+ name: (0, _orkestrel_contract.stringShape)({
7144
+ min: 1,
7145
+ max: BUILTIN_LIMITS.name,
7146
+ description: "A memo name the host lists for you to write"
7147
+ }),
7148
+ description: (0, _orkestrel_contract.stringShape)({
7149
+ min: 1,
7150
+ max: BUILTIN_LIMITS.description,
7151
+ description: "One line the memos index shows"
7152
+ }),
7153
+ content: (0, _orkestrel_contract.stringShape)({
7154
+ min: 1,
7155
+ max: BUILTIN_LIMITS.content,
7156
+ description: "The fact, why it holds, and how to apply it"
7157
+ })
7158
+ });
7159
+ /**
7160
+ * Describes the arguments of the `switch` built-in — one `workspace` id, non-empty and at most
7161
+ * `BUILTIN_LIMITS.name` code points.
7162
+ *
7163
+ * @remarks
7164
+ * The tool's door refuses a missing, empty, over-long, or extra member before the handler runs,
7165
+ * and repairs a value that converts to a string without loss, such as a number. A value of white
7166
+ * space alone passes the door; the handler refuses it as it refuses any name it cannot use.
7167
+ */
7168
+ var switchToolShape = (0, _orkestrel_contract.objectShape)({ workspace: (0, _orkestrel_contract.stringShape)({
7169
+ min: 1,
7170
+ max: BUILTIN_LIMITS.name,
7171
+ description: "A workspace id from the workspaces index"
7172
+ }) });
6430
7173
  // src/core/agents/contracts.ts
6431
7174
  /** Validates and projects agent frames at a JSON wire boundary. */
6432
7175
  var agentFrameContract = (0, _orkestrel_contract.createContract)(agentFrameShape);
@@ -6436,131 +7179,64 @@ var agentPromptContract = (0, _orkestrel_contract.createContract)(agentPromptSha
6436
7179
  var agentJobContract = (0, _orkestrel_contract.createContract)(agentJobShape);
6437
7180
  /** Validates and projects a page's answers to `ask` frames at a JSON wire boundary. */
6438
7181
  var agentAnswerContract = (0, _orkestrel_contract.createContract)(agentAnswerShape);
6439
- // src/core/agents/ChannelCell.ts
6440
- /**
6441
- * Holds one value a `Channel` buffers and the cell pushed after it.
6442
- *
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
7182
+ /** Checks the arguments of the `load` and `unload` built-ins; its schema is the parameters they advertise. */
7183
+ var loadToolContract = (0, _orkestrel_contract.createContract)(loadToolShape);
7184
+ /** Checks the arguments of the `memo` built-in; its schema is the parameters it advertises. */
7185
+ var memoToolContract = (0, _orkestrel_contract.createContract)(memoToolShape);
7186
+ /** Checks the arguments of the `remember` built-in; its schema is the parameters it advertises. */
7187
+ var rememberToolContract = (0, _orkestrel_contract.createContract)(rememberToolShape);
7188
+ /** Checks the arguments of the `switch` built-in; its schema is the parameters it advertises. */
7189
+ var switchToolContract = (0, _orkestrel_contract.createContract)(switchToolShape);
7190
+ /**
7191
+ * Pairs each built-in's argument shape, which its tool's door compiles, with its compiled contract,
7192
+ * keyed by its {@link AgentBuiltin} name, frozen.
7193
+ *
7194
+ * @remarks
7195
+ * The one map from a built-in to its arguments: `createBuiltinTool` builds the door from `shape`,
7196
+ * and `collectBuiltinDefinitions` and `collectDisclosure` read `contract`, so the advertised
7197
+ * parameters, the door, and the derivation admit by one rule. The record declares no shape: each
7198
+ * `shape` is a reference to the one `shapers.ts` declares, held beside the contract compiled from
7199
+ * it, because the tool door takes a shape and the derivation takes a compiled contract. The record
7200
+ * is data, as the rest of this file is.
7201
+ */
7202
+ var builtinToolContracts = Object.freeze({
7203
+ load: Object.freeze({
7204
+ shape: loadToolShape,
7205
+ contract: loadToolContract
7206
+ }),
7207
+ unload: Object.freeze({
7208
+ shape: loadToolShape,
7209
+ contract: loadToolContract
7210
+ }),
7211
+ memo: Object.freeze({
7212
+ shape: memoToolShape,
7213
+ contract: memoToolContract
7214
+ }),
7215
+ remember: Object.freeze({
7216
+ shape: rememberToolShape,
7217
+ contract: rememberToolContract
7218
+ }),
7219
+ switch: Object.freeze({
7220
+ shape: switchToolShape,
7221
+ contract: switchToolContract
7222
+ })
7223
+ });
7224
+ // src/core/agents/validators.ts
6485
7225
  /**
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.
7226
+ * Checks whether a value names a built-in: `load`, `unload`, `memo`, `remember`, or `switch`.
6491
7227
  *
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.
7228
+ * @param value - The unknown name candidate
7229
+ * @returns True if the value names a built-in; false otherwise
6505
7230
  *
6506
7231
  * @example
6507
7232
  * ```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
- * }
7233
+ * isAgentBuiltin('load') // true
7234
+ * isAgentBuiltin('open') // false
6516
7235
  * ```
6517
7236
  */
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
7237
+ function isAgentBuiltin(value) {
7238
+ return value === "load" || value === "unload" || value === "memo" || value === "remember" || value === "switch";
7239
+ }
6564
7240
  /**
6565
7241
  * Checks whether a value is an authority verdict: `allow`, `ask`, or `deny`.
6566
7242
  *
@@ -6601,6 +7277,35 @@ function isAuthorityDecision(value) {
6601
7277
  reason: _orkestrel_contract.isString
6602
7278
  }, ["reason"])(value);
6603
7279
  }
7280
+ /**
7281
+ * Checks whether a value is an agent caller: a plain record whose own keys are exactly `workspace`
7282
+ * and `files`, whose `workspace` is `undefined` or an object, and whose `files` is `undefined` or an
7283
+ * array of strings.
7284
+ *
7285
+ * @remarks
7286
+ * Total: each member is read once, and an omitted, inherited, or undeclared member, a throwing
7287
+ * accessor, and a revoked `Proxy` return false rather than throw. It checks the shape a caller
7288
+ * carries, never that `workspace` is a live workspace: a value that crossed a wire passes when it
7289
+ * holds that shape, so accept its `workspace` only as the very instance your own registry holds
7290
+ * under that id.
7291
+ *
7292
+ * @param value - The unknown caller candidate, such as `ToolContext.caller`
7293
+ * @returns True if the value satisfies the caller contract; false otherwise
7294
+ *
7295
+ * @example
7296
+ * ```ts
7297
+ * isAgentCaller({ workspace: undefined, files: undefined }) // true
7298
+ * isAgentCaller({ workspace: 'returns' }) // false
7299
+ * ```
7300
+ */
7301
+ function isAgentCaller(value) {
7302
+ return (0, _orkestrel_contract.holds)(() => {
7303
+ if (!(0, _orkestrel_contract.isRecord)(value) || !(0, _orkestrel_contract.matchesExactKeys)(value, ["workspace", "files"])) return false;
7304
+ const workspace = value.workspace;
7305
+ const files = value.files;
7306
+ return (workspace === void 0 || (0, _orkestrel_contract.isObject)(workspace)) && (files === void 0 || (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(files));
7307
+ });
7308
+ }
6604
7309
  // src/core/agents/helpers.ts
6605
7310
  /**
6606
7311
  * Projects an unknown value onto a fresh, exact `JSONValue` representation of an
@@ -7081,6 +7786,118 @@ function handleAgentQueueJob(registry, partial, input, context) {
7081
7786
  return settleAgentJob(registry.build(input, context.signal), partial);
7082
7787
  }
7083
7788
  /**
7789
+ * Collects the refusals a job the contract admitted would meet at its build: tools or built-ins with
7790
+ * no authority, a `builtins` list `requireBuiltins` refuses, and a built-in its own `tools` name.
7791
+ *
7792
+ * @remarks
7793
+ * Each line names the member first, then the refusal and its remedy. The `builtins` line of a list
7794
+ * `requireBuiltins` refuses carries that error's message; a job's tool manager holds exactly its
7795
+ * `tools`, so a built-in its `tools` list also names is the name a manager would already hold.
7796
+ * `AgentRegistry.build` refuses a job with any line, and `scanJobTree` refuses each such job of a
7797
+ * tree before anything spawns.
7798
+ *
7799
+ * @param job - The `tools`, `builtins`, and `authority` of a job `agentJobContract` admitted
7800
+ * @returns One line a refusal, in member order; empty when the job builds
7801
+ *
7802
+ * @example
7803
+ * ```ts
7804
+ * collectJobRefusals({ provider: 'main', tools: ['search'] })
7805
+ * // ["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"]
7806
+ * ```
7807
+ */
7808
+ function collectJobRefusals(job) {
7809
+ const tools = job.tools ?? [];
7810
+ const builtins = job.builtins ?? [];
7811
+ const checked = (0, _orkestrel_contract.attempt)(() => requireBuiltins(job.builtins, { tool: () => void 0 }));
7812
+ const shared = builtins.filter((name) => tools.includes(name));
7813
+ return [
7814
+ (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,
7815
+ checked.success ? void 0 : `builtins: ${(0, _orkestrel_contract.errorToMessage)(checked.error)}`,
7816
+ 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`
7817
+ ].filter((line) => line !== void 0);
7818
+ }
7819
+ /**
7820
+ * Scans every job under a root for a member that exceeds its own parent's; an empty result clears the tree.
7821
+ *
7822
+ * @remarks
7823
+ * The walk audits each job, the root included, through `agentJobContract` before it reads a member,
7824
+ * because a job's `children` cross that contract as JSON records, and then reads each job the
7825
+ * contract admits through `collectJobRefusals`. A job the contract refuses yields one finding with its
7826
+ * faults, or with an unreadable note when a member read throws; a job its build would refuse yields
7827
+ * one finding a refusal, its path, a dot, and the line; and the walk reads nothing under either. A
7828
+ * child is within its parent when each of
7829
+ * its `tools` is one its parent names (a parent with no `tools` names none), each of its `builtins`
7830
+ * is one its parent names (a parent with no `builtins` names none), each name in each list of its
7831
+ * `scope` is in its parent's same list (a job with no `scope` holds the three lists empty), its
7832
+ * `authority` and `scheduler` are absent or its parent's, its `provider` is its parent's, its `limit`
7833
+ * is at most its parent's (an absent `limit` reads `DEFAULT_AGENT_LIMIT`), and, where its parent
7834
+ * holds a `timeout` or a `budget`, it holds one no larger. `system`, `messages`, and `workspace` are
7835
+ * free: a `workspace` renders nothing its own `scope` omits. A refused job's finding
7836
+ * opens with `JOB_REFUSAL_PREFIX` and then the job's path from `job`; an exceeded member's opens
7837
+ * with that path, a dot, and the member. The walk is total: an unreadable job yields a finding,
7838
+ * never a throw. The `AgentFanout` class runs it on every attempt of a unit, before the unit spawns
7839
+ * or runs.
7840
+ *
7841
+ * @param root - The root job of the tree
7842
+ * @returns One finding per refused job and per exceeded member, in walk order
7843
+ *
7844
+ * @example
7845
+ * ```ts
7846
+ * scanJobTree({ provider: 'main', children: [{ provider: 'other' }] })
7847
+ * // ['job.children[0].provider: "other" differs from its parent\'s, which is "main"']
7848
+ * ```
7849
+ */
7850
+ function scanJobTree(root) {
7851
+ const findings = [];
7852
+ const pending = [[
7853
+ root,
7854
+ "job",
7855
+ void 0
7856
+ ]];
7857
+ for (const [job, path, parent] of pending) {
7858
+ if (!agentJobContract.is(job)) {
7859
+ const faults = (0, _orkestrel_contract.attempt)(() => agentJobContract.audit(job));
7860
+ 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"}`);
7861
+ continue;
7862
+ }
7863
+ const refusals = collectJobRefusals(job);
7864
+ if (refusals.length > 0) {
7865
+ findings.push(...refusals.map((line) => `${JOB_REFUSAL_PREFIX}${path}.${line}`));
7866
+ continue;
7867
+ }
7868
+ if (parent !== void 0) {
7869
+ const held = parent.tools ?? [];
7870
+ const outside = (job.tools ?? []).filter((name) => !held.includes(name));
7871
+ const enabled = parent.builtins ?? [];
7872
+ const extra = (job.builtins ?? []).filter((name) => !enabled.includes(name));
7873
+ const unlisted = [
7874
+ "skills",
7875
+ "memos",
7876
+ "workspaces"
7877
+ ].map((list) => [list, (job.scope?.[list] ?? []).filter((name) => !(parent.scope?.[list] ?? []).includes(name))]);
7878
+ const limit = job.limit ?? 10;
7879
+ const bound = parent.limit ?? 10;
7880
+ findings.push(...[
7881
+ outside.length === 0 ? void 0 : `${path}.tools: names ${outside.map((name) => (0, _orkestrel_contract.preview)(name)).join(", ")}, which its parent does not hold`,
7882
+ extra.length === 0 ? void 0 : `${path}.builtins: names ${extra.map((name) => (0, _orkestrel_contract.preview)(name)).join(", ")}, which its parent does not hold`,
7883
+ ...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`),
7884
+ 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)}`,
7885
+ 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)}`,
7886
+ 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)}`,
7887
+ limit <= bound ? void 0 : `${path}.limit: ${job.limit === void 0 ? `none reads ${limit}, which exceeds` : `${limit} exceeds`} its parent's ${bound}`,
7888
+ 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}`,
7889
+ 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}`
7890
+ ].filter((finding) => finding !== void 0));
7891
+ }
7892
+ for (const [index, child] of (job.children ?? []).entries()) pending.push([
7893
+ child,
7894
+ `${path}.children[${index}]`,
7895
+ job
7896
+ ]);
7897
+ }
7898
+ return findings;
7899
+ }
7900
+ /**
7084
7901
  * Assembles the settled {@link AgentResult} from a run's {@link AgentRunResult} — `thinking`,
7085
7902
  * `usage`, and `stop` are carried only when the run surfaced them, and the loop-internal
7086
7903
  * `exhausted` flag is left out.
@@ -7163,7 +7980,7 @@ function denyCall(call, reason) {
7163
7980
  function renderArguments(value) {
7164
7981
  const rendered = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.canonicalStringify)(value));
7165
7982
  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(""));
7983
+ 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
7984
  }
7168
7985
  /**
7169
7986
  * Applies the `consequential` floor to one {@link AuthorityDecision} — an `allow` for a tool
@@ -7382,6 +8199,37 @@ function requireConversation(value) {
7382
8199
  });
7383
8200
  }
7384
8201
  /**
8202
+ * Returns a scope a run can bind, or throws a `ContractError` coded `'placement'`; `undefined`
8203
+ * passes through.
8204
+ *
8205
+ * @remarks
8206
+ * The check is {@link isScope}, a total guard that reads each member one time and admits any
8207
+ * implementation of `ScopeInterface`. `undefined` passes, because an absent option leaves the
8208
+ * context scope alone as the run's ceiling. The error's `context` carries the option path, the
8209
+ * limit `'ScopeInterface'`, and a preview of the refused value.
8210
+ *
8211
+ * @param value - The run's `scope` option, or `undefined`
8212
+ * @returns The value unchanged
8213
+ * @throws {ContractError} Thrown when `value` is defined and does not implement `ScopeInterface` (code `'placement'`)
8214
+ *
8215
+ * @example
8216
+ * ```ts
8217
+ * requireScope(createScope({ name: 'reader', tools: ['search'] })) // the scope
8218
+ * requireScope({ id: 's1', name: 'reader' }) // throws ContractError ('placement')
8219
+ * ```
8220
+ */
8221
+ function requireScope(value) {
8222
+ if (value === void 0 || isScope(value)) return value;
8223
+ throw new _orkestrel_contract.ContractError("Agent: scope must implement ScopeInterface", {
8224
+ code: "placement",
8225
+ context: {
8226
+ path: ["options", "scope"],
8227
+ limit: "ScopeInterface",
8228
+ received: (0, _orkestrel_contract.preview)(value)
8229
+ }
8230
+ });
8231
+ }
8232
+ /**
7385
8233
  * Renders a tool's output as the string content of its tool message, or returns `undefined` when JSON
7386
8234
  * cannot carry it.
7387
8235
  *
@@ -7483,6 +8331,735 @@ function extractQueueOptions(options) {
7483
8331
  ...timeout === void 0 ? {} : { timeout }
7484
8332
  };
7485
8333
  }
8334
+ /**
8335
+ * Collects the definitions of the tools an agent serves itself, as a run advertises them.
8336
+ *
8337
+ * @remarks
8338
+ * Each definition carries the name, description, and annotations `BUILTIN_TOOLS` declares and the
8339
+ * parameters of the built-in's compiled contract in `builtinToolContracts`, in the order `builtins` lists them, so
8340
+ * it equals the definition of the tool `createBuiltinTool` creates. A relay server lists these
8341
+ * among its declared definitions to forward a page's built-ins. A name that is no built-in
8342
+ * contributes nothing.
8343
+ *
8344
+ * @param options - The agent options whose `builtins` to project
8345
+ * @returns The definitions, empty when `builtins` is absent
8346
+ *
8347
+ * @example
8348
+ * ```ts
8349
+ * collectBuiltinDefinitions({ builtins: ['load', 'unload'] }).map((definition) => definition.name) // ['load', 'unload']
8350
+ * ```
8351
+ */
8352
+ function collectBuiltinDefinitions(options) {
8353
+ return (options.builtins ?? []).filter(isAgentBuiltin).map((name) => {
8354
+ const declared = BUILTIN_TOOLS[name];
8355
+ const parameters = (0, _orkestrel_contract.schemaToParameters)(builtinToolContracts[name].contract.schema);
8356
+ return {
8357
+ name,
8358
+ description: declared.description,
8359
+ ...parameters === void 0 ? {} : { parameters },
8360
+ ...declared.annotations === void 0 ? {} : { annotations: declared.annotations }
8361
+ };
8362
+ });
8363
+ }
8364
+ /**
8365
+ * Returns an agent's `builtins` option as an owned, frozen list, or throws a coded error before the
8366
+ * agent is built.
8367
+ *
8368
+ * @remarks
8369
+ * `undefined` returns an empty list. The option must be an array of distinct {@link AgentBuiltin}
8370
+ * names; `unload` needs `load`, because only a loaded skill can be unloaded; and the host's tool
8371
+ * manager may hold no tool under an enabled name, because the run serves that name itself.
8372
+ *
8373
+ * @param value - The `builtins` option
8374
+ * @param tools - The host's tool manager
8375
+ * @returns The enabled names, in the order given
8376
+ * @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'`)
8377
+ *
8378
+ * @example
8379
+ * ```ts
8380
+ * requireBuiltins(['load', 'unload'], createToolManager()) // ['load', 'unload']
8381
+ * requireBuiltins(['unload'], createToolManager()) // throws AgentError ('BUILTIN')
8382
+ * ```
8383
+ */
8384
+ function requireBuiltins(value, tools) {
8385
+ if (value === void 0) return Object.freeze([]);
8386
+ if (!(0, _orkestrel_contract.isArray)(value)) throw new AgentError("BUILTIN", `builtins must be an array; received ${(0, _orkestrel_contract.preview)(value)}`);
8387
+ const names = [...value].filter(isAgentBuiltin);
8388
+ 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) } });
8389
+ if (new Set(names).size !== names.length) throw new AgentError("BUILTIN", "builtins lists a name more than once", { context: { builtins: names } });
8390
+ if (names.includes("unload") && !names.includes("load")) throw new AgentError("BUILTIN", "builtins lists 'unload' without 'load'; add 'load' too", { context: { builtins: names } });
8391
+ const taken = names.filter((name) => tools.tool(name) !== void 0);
8392
+ 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: {
8393
+ builtins: names,
8394
+ taken
8395
+ } });
8396
+ return Object.freeze(names);
8397
+ }
8398
+ /**
8399
+ * Returns a `workspace` option unchanged, or throws a `ContractError` coded `'placement'`;
8400
+ * `undefined` passes through.
8401
+ *
8402
+ * @remarks
8403
+ * The check is `isString`. An id the workspace registry does not hold, or the ceiling does not
8404
+ * admit, passes here and renders nothing. The error's `context` carries the option path, the limit
8405
+ * `'string'`, and a preview of the refused value.
8406
+ *
8407
+ * @param value - The `workspace` option of an agent, a run, or a ledger's agent, or `undefined`
8408
+ * @returns The value unchanged
8409
+ * @throws {ContractError} Thrown when `value` is defined and is not a string (code `'placement'`)
8410
+ *
8411
+ * @example
8412
+ * ```ts
8413
+ * requireWorkspace('orders') // 'orders'
8414
+ * requireWorkspace(7) // throws ContractError ('placement')
8415
+ * ```
8416
+ */
8417
+ function requireWorkspace(value) {
8418
+ if (value === void 0 || (0, _orkestrel_contract.isString)(value)) return value;
8419
+ throw new _orkestrel_contract.ContractError("Agent: workspace must be a string", {
8420
+ code: "placement",
8421
+ context: {
8422
+ path: ["options", "workspace"],
8423
+ limit: "string",
8424
+ received: (0, _orkestrel_contract.preview)(value)
8425
+ }
8426
+ });
8427
+ }
8428
+ /**
8429
+ * Returns the definitions a turn advertises from the allow-list, the registered skills, and the
8430
+ * shown skills.
8431
+ *
8432
+ * @remarks
8433
+ * The allow-list admits first, as `filterAllowList` does. When no skill is passed, or an allow-list
8434
+ * admits no `load` definition, the admitted list returns as it is. Otherwise a tool any registered
8435
+ * skill names is held back unless a shown skill names it too; `load` and `unload` are never held
8436
+ * back. A caller passes the registered skills only while `load` is enabled, so with no allow-list a
8437
+ * ceiling whose empty `skills` list withholds `load` from the candidates still holds the skills'
8438
+ * tools back. Holding a tool back is presentation, never authority: a call to it is denied as
8439
+ * outside the turn's scope.
8440
+ *
8441
+ * @param definitions - The candidate definitions, in advertisement order
8442
+ * @param names - The turn's `tools` allow-list (`undefined` ⇒ all, `[]` ⇒ none)
8443
+ * @param skills - The registered skills whose `tools` gate, or none while `load` is not enabled
8444
+ * @param shown - The names of the skills the turn shows
8445
+ * @returns The advertised definitions, in candidate order
8446
+ *
8447
+ * @example
8448
+ * ```ts
8449
+ * const skills = [{ id: '1', name: 'refunds', description: 'Refunds', content: '', tools: ['refund'], files: [] }]
8450
+ * filterAdvertisedTools([{ name: 'load' }, { name: 'refund' }], undefined, skills, []) // [{ name: 'load' }]
8451
+ * filterAdvertisedTools([{ name: 'load' }, { name: 'refund' }], undefined, skills, ['refunds']).length // 2
8452
+ * ```
8453
+ */
8454
+ function filterAdvertisedTools(definitions, names, skills, shown) {
8455
+ const admitted = filterAllowList(names, definitions, (definition) => definition.name);
8456
+ if (skills.length === 0) return admitted;
8457
+ if (names !== void 0 && !admitted.some((definition) => definition.name === "load")) return admitted;
8458
+ const held = new Set(skills.flatMap((skill) => skill.tools));
8459
+ const visible = new Set(shown);
8460
+ const open = new Set(skills.filter((skill) => visible.has(skill.name)).flatMap((skill) => skill.tools));
8461
+ return admitted.filter((definition) => definition.name === "load" || definition.name === "unload" || !held.has(definition.name) || open.has(definition.name));
8462
+ }
8463
+ /**
8464
+ * Returns the definitions a run offers before the allow-list, without `remember` unless the
8465
+ * ceiling's `memos` list names a memo, without `switch` unless its `workspaces` list names a
8466
+ * workspace, and without `load` and `unload` while its `skills` list is empty.
8467
+ *
8468
+ * @remarks
8469
+ * `remember` writes and `switch` selects only a name its list holds, so an absent list and an empty
8470
+ * list each leave the built-in nothing to act on, and it is not advertised. An absent `skills` list
8471
+ * admits every registered skill, so `load` and `unload` are withheld only when the list is empty.
8472
+ * Every other definition keeps its place.
8473
+ *
8474
+ * @param definitions - The candidate definitions, in advertisement order
8475
+ * @param ceiling - The turn's ceiling
8476
+ * @returns The definitions, in candidate order
8477
+ *
8478
+ * @example
8479
+ * ```ts
8480
+ * filterBuiltinCandidates([{ name: 'memo' }, { name: 'remember' }], { memos: [] }) // [{ name: 'memo' }]
8481
+ * filterBuiltinCandidates([{ name: 'memo' }, { name: 'remember' }], { memos: ['tone'] }).length // 2
8482
+ * ```
8483
+ */
8484
+ function filterBuiltinCandidates(definitions, ceiling) {
8485
+ const writable = (ceiling.memos ?? []).length > 0;
8486
+ const selectable = (ceiling.workspaces ?? []).length > 0;
8487
+ const loadable = ceiling.skills === void 0 || ceiling.skills.length > 0;
8488
+ return definitions.filter((definition) => (definition.name !== "remember" || writable) && (definition.name !== "switch" || selectable) && (definition.name !== "load" && definition.name !== "unload" || loadable));
8489
+ }
8490
+ /**
8491
+ * Returns the refusal that names a subject and at most `BUILTIN_LIMITS.candidates` of the names a
8492
+ * caller can use.
8493
+ *
8494
+ * @remarks
8495
+ * The subject reads after "No", so it names what the caller may use, such as `'skill you can load'`.
8496
+ * The text echoes no argument, so an absent name and a name outside the ceiling read byte for byte
8497
+ * the same. Each name is JSON-encoded and capped by `preview`, in the order given. Past the cap one
8498
+ * fixed sentence sends the reader to the index in its prompt.
8499
+ *
8500
+ * @param subject - What the caller may name, such as `'skill you can load'`
8501
+ * @param names - The names the caller can use, in registry order
8502
+ * @returns The refusal text
8503
+ *
8504
+ * @example
8505
+ * ```ts
8506
+ * describeCandidates('skill you can load', ['refunds', 'billing'])
8507
+ * // 'No skill you can load has that name. Use one of: "refunds", "billing".'
8508
+ * ```
8509
+ */
8510
+ function describeCandidates(subject, names) {
8511
+ if (names.length === 0) return `No ${subject} has that name, and none is available now.`;
8512
+ 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." : ""}`;
8513
+ }
8514
+ /**
8515
+ * Checks whether a text is one nonblank line: it holds a code point other than white space and
8516
+ * default-ignorable code points (`ARGUMENT_BLANK_PATTERN`), and no control, format, line-separator, or
8517
+ * paragraph-separator code point (`ARGUMENT_CONTROL_PATTERN`).
8518
+ *
8519
+ * @remarks
8520
+ * The class is the one `renderArguments` encodes. A memo's name and description render in the memos
8521
+ * index one line each, so `remember` refuses a value outside this check.
8522
+ *
8523
+ * @param text - The text to check
8524
+ * @returns True if the text is one nonblank line; false otherwise
8525
+ *
8526
+ * @example
8527
+ * ```ts
8528
+ * matchesPlainLine('Reply tone') // true
8529
+ * matchesPlainLine('Reply\ntone') // false
8530
+ * matchesPlainLine(' ') // false
8531
+ * ```
8532
+ */
8533
+ function matchesPlainLine(text) {
8534
+ return !ARGUMENT_BLANK_PATTERN.test(text) && !ARGUMENT_CONTROL_PATTERN.test(text);
8535
+ }
8536
+ /**
8537
+ * Checks whether two ceilings carry the same lists of what a head renders: `instructions`,
8538
+ * `skills`, `memos`, `files`, and `workspaces`.
8539
+ *
8540
+ * @remarks
8541
+ * Each list compares by its members in order, and an absent list equals only an absent list,
8542
+ * because `undefined` admits everything and `[]` admits nothing. The `tools` list is not compared:
8543
+ * it reaches the tools block, never the head.
8544
+ *
8545
+ * @param left - One ceiling's lists
8546
+ * @param right - The other ceiling's lists
8547
+ * @returns True if every compared list matches; false otherwise
8548
+ *
8549
+ * @example
8550
+ * ```ts
8551
+ * matchesHeadLists({ skills: ['refunds'] }, { skills: ['refunds'], tools: [] }) // true
8552
+ * matchesHeadLists({ skills: [] }, {}) // false
8553
+ * ```
8554
+ */
8555
+ function matchesHeadLists(left, right) {
8556
+ return [
8557
+ [left.instructions, right.instructions],
8558
+ [left.skills, right.skills],
8559
+ [left.memos, right.memos],
8560
+ [left.files, right.files],
8561
+ [left.workspaces, right.workspaces]
8562
+ ].every(([one, other]) => one === void 0 || other === void 0 ? one === other : one.length === other.length && one.every((key, index) => key === other[index]));
8563
+ }
8564
+ /**
8565
+ * Resolves what a turn shows: the conversation's record, and the given workspace, within the
8566
+ * ceiling and the registries.
8567
+ *
8568
+ * @remarks
8569
+ * The caller reads the ceiling, so a turn reads each scope list one time. The skills are those
8570
+ * {@link collectDisclosure} reads from the conversation, in load order, keeping each that the skill
8571
+ * registry holds and the ceiling's `skills` list admits; an unregistered or excluded name shows
8572
+ * nothing. The workspace is the conversation's last switch, else the given one, kept only when the
8573
+ * workspace registry holds it and the ceiling's `workspaces` list admits it. The record counts only
8574
+ * the built-ins in `builtins`, as {@link collectDisclosure} states.
8575
+ *
8576
+ * @param context - The agent's context, whose scope, skills, and workspaces it reads
8577
+ * @param conversation - The conversation whose record to read
8578
+ * @param workspace - The `workspace` option of the run or the agent, or `undefined`
8579
+ * @param ceiling - The ceiling the turn read: the context scope intersected with the run scope
8580
+ * @param builtins - The built-ins the run enables
8581
+ * @returns The shown skills, in order, and the shown workspace when one is
8582
+ *
8583
+ * @example
8584
+ * ```ts
8585
+ * resolveDisclosure(agent.context, agent.context.messages, 'orders', {}, []) // { skills: [], workspace: 'orders' } when orders is registered
8586
+ * ```
8587
+ */
8588
+ function resolveDisclosure(context, conversation, workspace, ceiling, builtins) {
8589
+ const recorded = collectDisclosure(conversation, builtins);
8590
+ const admitted = new Set(filterAllowList(ceiling.skills, context.skills.skills(), (skill) => skill.name).map((skill) => skill.name));
8591
+ const skills = recorded.skills.filter((name) => admitted.has(name));
8592
+ const selected = recorded.workspace ?? workspace;
8593
+ return selected !== void 0 && context.workspaces.workspace(selected) !== void 0 && filterAllowList(ceiling.workspaces, [selected], (id) => id).length > 0 ? {
8594
+ skills,
8595
+ workspace: selected
8596
+ } : { skills };
8597
+ }
8598
+ /**
8599
+ * Collects the loaded skills and the selected workspace a conversation's stored built-in calls
8600
+ * record, counting only the built-ins a run enables.
8601
+ *
8602
+ * @remarks
8603
+ * Pure. It reads each section's retained originals, oldest first, then the live tail, and pairs
8604
+ * each tool message with the call that owns it through {@link collectToolGroups} and
8605
+ * {@link findToolCall}. It then folds the pairs in message order: a `load` whose message content
8606
+ * equals `BUILTIN_NOTES.loaded` adds its `skill`, an `unload` with `BUILTIN_NOTES.unloaded` removes
8607
+ * it, and a `switch` with `BUILTIN_NOTES.switched` selects its `workspace`. Each call's arguments
8608
+ * are admitted through the built-in's contract in `builtinToolContracts`, lossless repairs
8609
+ * included, as its tool's door admits them, so a `{ skill: 1 }` the door repaired to `'1'` counts
8610
+ * as `'1'`; arguments the contract refuses change nothing. A skill loaded again while shown keeps
8611
+ * its place, and one loaded again after its unload goes last. Any other message and a denied or
8612
+ * refused call change nothing. The result holds `workspace` only when a switch succeeded.
8613
+ *
8614
+ * A record counts only while its built-in is in `builtins`, so a host tool that holds a built-in's
8615
+ * name on a run that does not enable it shows nothing; with none of `load`, `unload`, and `switch`
8616
+ * enabled, nothing is read. A stored record cannot tell its writer, so one a host tool wrote under a
8617
+ * built-in's name while that built-in was off counts after a run enables it, bounded by the registry
8618
+ * and the ceiling as every record is.
8619
+ *
8620
+ * @param conversation - The conversation whose stored messages record the calls
8621
+ * @param builtins - The built-ins the run enables
8622
+ * @returns The skills in load order and the last selected workspace
8623
+ *
8624
+ * @example
8625
+ * ```ts
8626
+ * import { collectDisclosure, createConversation } from '@orkestrel/agent'
8627
+ *
8628
+ * collectDisclosure(createConversation(), ['load', 'unload']) // { skills: [] }
8629
+ * ```
8630
+ */
8631
+ function collectDisclosure(conversation, builtins) {
8632
+ const counted = new Set(builtins.filter((name) => name === "load" || name === "unload" || name === "switch"));
8633
+ if (counted.size === 0) return { skills: [] };
8634
+ const stored = [...conversation.sections.flatMap((section) => section.messages), ...conversation.messages()];
8635
+ const owners = new Map(collectToolGroups(stored).flatMap((group) => group.flatMap((message) => {
8636
+ const call = findToolCall(group, message);
8637
+ return call === void 0 ? [] : [[message, call]];
8638
+ })));
8639
+ return stored.reduce((shown, message) => {
8640
+ const call = owners.get(message);
8641
+ const name = call?.name;
8642
+ if (call === void 0 || !isAgentBuiltin(name) || !counted.has(name)) return shown;
8643
+ const note = name === "load" ? BUILTIN_NOTES.loaded : name === "unload" ? BUILTIN_NOTES.unloaded : BUILTIN_NOTES.switched;
8644
+ if (message.content !== note) return shown;
8645
+ const admitted = (0, _orkestrel_contract.admitValue)(builtinToolContracts[name].contract, { ...call.arguments });
8646
+ const skill = admitted.success ? admitted.value.value["skill"] : void 0;
8647
+ const workspace = admitted.success ? admitted.value.value["workspace"] : void 0;
8648
+ if (name === "load") return !(0, _orkestrel_contract.isString)(skill) || shown.skills.includes(skill) ? shown : {
8649
+ ...shown,
8650
+ skills: [...shown.skills, skill]
8651
+ };
8652
+ if (name === "unload") return !(0, _orkestrel_contract.isString)(skill) ? shown : {
8653
+ ...shown,
8654
+ skills: shown.skills.filter((one) => one !== skill)
8655
+ };
8656
+ return !(0, _orkestrel_contract.isString)(workspace) ? shown : {
8657
+ skills: shown.skills,
8658
+ workspace
8659
+ };
8660
+ }, { skills: [] });
8661
+ }
8662
+ // src/core/agents/AgentRun.ts
8663
+ /**
8664
+ * Holds one agent run's own tools and the view its turns and builds share: the ceiling a turn
8665
+ * read, what it shows, what it advertises, and what the working head was built with.
8666
+ *
8667
+ * @remarks
8668
+ * `Agent` creates one per run, so no public signature names this class. The run's built-in tools
8669
+ * live in a tool manager the run owns and never changes after construction, so a tool a host
8670
+ * registers under a built-in's name is never read: `tool` and `execute` answer a built-in's name
8671
+ * from the run's manager and every other name from the host's. Each handler reads the ceiling when
8672
+ * it runs, the context scope intersected with the run scope at that moment, never a turn's copy.
8673
+ * The `caller` each call receives reads the ceiling the same way, as the call starts.
8674
+ */
8675
+ var AgentRun = class {
8676
+ #context;
8677
+ #conversation;
8678
+ #scope;
8679
+ #workspace;
8680
+ #enabled;
8681
+ #builtins;
8682
+ #tools;
8683
+ #ceiling = {};
8684
+ #shown = { skills: [] };
8685
+ #ids = [];
8686
+ #files = [];
8687
+ #advertised = [];
8688
+ #snapshot;
8689
+ #selection;
8690
+ #length = 0;
8691
+ #head;
8692
+ /**
8693
+ * Creates the run's own tools for the enabled built-ins.
8694
+ *
8695
+ * @param context - The agent's context
8696
+ * @param conversation - The conversation the run is bound to
8697
+ * @param scope - The run scope, or `undefined`
8698
+ * @param workspace - The workspace the run renders in place of the active one, already checked, or `undefined`
8699
+ * @param builtins - The enabled built-ins, already checked
8700
+ */
8701
+ constructor(context, conversation, scope, workspace, builtins) {
8702
+ this.#context = context;
8703
+ this.#conversation = conversation;
8704
+ this.#scope = scope;
8705
+ this.#workspace = workspace;
8706
+ this.#enabled = builtins;
8707
+ this.#builtins = new Set(builtins);
8708
+ this.#tools = (0, _orkestrel_tool.createToolManager)();
8709
+ this.#tools.add(builtins.map((name) => createBuiltinTool(name, (args) => this.#handle(name, args))));
8710
+ }
8711
+ /**
8712
+ * Returns the conversation the run is bound to.
8713
+ *
8714
+ * @returns The conversation
8715
+ */
8716
+ get conversation() {
8717
+ return this.#conversation;
8718
+ }
8719
+ /**
8720
+ * Returns the run scope.
8721
+ *
8722
+ * @returns The scope, or undefined when the run has none
8723
+ */
8724
+ get scope() {
8725
+ return this.#scope;
8726
+ }
8727
+ /**
8728
+ * Returns an owned scope holding the run scope's lists as the last `observe` read them, so a build
8729
+ * renders by the turn's one read and never reads the run scope again.
8730
+ *
8731
+ * @returns The owned scope, or undefined when the run has none
8732
+ */
8733
+ get snapshot() {
8734
+ return this.#snapshot;
8735
+ }
8736
+ /**
8737
+ * Returns the ceiling the last `observe` read: the context scope intersected with the run scope.
8738
+ *
8739
+ * @returns The ceiling's lists
8740
+ */
8741
+ get ceiling() {
8742
+ return this.#ceiling;
8743
+ }
8744
+ /**
8745
+ * Returns the definitions the last `observe` advertised.
8746
+ *
8747
+ * @returns The advertised definitions
8748
+ */
8749
+ get advertised() {
8750
+ return this.#advertised;
8751
+ }
8752
+ /**
8753
+ * Returns what a build renders from the last `observe`: the shown skills and workspace, or
8754
+ * undefined when it shows neither.
8755
+ *
8756
+ * @returns The shown value, or undefined
8757
+ */
8758
+ get disclosure() {
8759
+ return this.#shown.skills.length === 0 && this.#shown.workspace === void 0 ? void 0 : this.#shown;
8760
+ }
8761
+ /**
8762
+ * Returns whether the last `observe` advertised `switch`, which lists the workspaces in the head.
8763
+ *
8764
+ * @returns True if `switch` is advertised; false otherwise
8765
+ */
8766
+ get switchable() {
8767
+ return this.#advertised.some((definition) => definition.name === "switch");
8768
+ }
8769
+ /**
8770
+ * Returns what a tool call receives as `ToolContext.caller`: the workspace instance the working
8771
+ * head rendered, kept only while the ceiling at this moment admits its id, and that ceiling's
8772
+ * `files` list.
8773
+ *
8774
+ * @remarks
8775
+ * The instance is the one the head's build resolved, never the registry's `active` pointer at the
8776
+ * call, so a host's `switch` made while the provider answers reaches the next turn's head and not
8777
+ * this call. Each read of this getter reads the `workspaces` and `files` lists of the context
8778
+ * scope and the run scope one time each, and no other list, so a call reads them as it starts, a
8779
+ * narrowing made after the turn's decision reaches that call, and a turn still reads its `tools`
8780
+ * list once.
8781
+ *
8782
+ * @returns A fresh caller
8783
+ */
8784
+ get caller() {
8785
+ const outer = this.#context.scope;
8786
+ const inner = this.#scope;
8787
+ const rendered = this.#head?.rendered;
8788
+ const [workspace] = filterAllowList(intersectKeys(outer?.workspaces, inner?.workspaces), rendered === void 0 ? [] : [rendered], (one) => one.id);
8789
+ return {
8790
+ workspace,
8791
+ files: intersectKeys(outer?.files, inner?.files)
8792
+ };
8793
+ }
8794
+ /**
8795
+ * Returns the selection the working head rendered.
8796
+ *
8797
+ * @returns The selection, or undefined before the first build
8798
+ */
8799
+ get selection() {
8800
+ return this.#selection;
8801
+ }
8802
+ /**
8803
+ * Returns the length of the array the working head was built as.
8804
+ *
8805
+ * @returns The length, 0 before the first build
8806
+ */
8807
+ get length() {
8808
+ return this.#length;
8809
+ }
8810
+ /**
8811
+ * Reads the ceiling, resolves what the run shows within it, and computes what it advertises.
8812
+ *
8813
+ * @remarks
8814
+ * The ceiling's lists are each read one time. The conversation's record is read only while
8815
+ * `load`, `unload`, or `switch` is enabled, and only the records of an enabled built-in count.
8816
+ * Registered skills hold their tools back only while `load` is enabled. The view compared with
8817
+ * the working head's is the ceiling's `instructions`, `skills`, `memos`, `files`, and
8818
+ * `workspaces` lists, the shown skills by id, the shown workspace, the files that workspace
8819
+ * renders under the ceiling's `files` list, compared by identity, and whether `switch` is
8820
+ * advertised. An edit to a rendered file, by a tool or by the host, therefore makes the head
8821
+ * stale, and an edit to a file no build renders does not.
8822
+ *
8823
+ * @returns True if the working head was built with another view and must be rebuilt; false otherwise
8824
+ */
8825
+ observe() {
8826
+ const lists = this.#scope === void 0 ? void 0 : intersectFilters(void 0, this.#scope);
8827
+ const ceiling = intersectFilters(this.#context.scope, lists);
8828
+ this.#snapshot = lists === void 0 ? void 0 : createScope({
8829
+ name: "run",
8830
+ ...lists
8831
+ });
8832
+ const shown = resolveDisclosure(this.#context, this.#conversation, this.#workspace, ceiling, this.#enabled);
8833
+ this.#ceiling = ceiling;
8834
+ this.#shown = shown;
8835
+ this.#ids = shown.skills.flatMap((name) => this.#context.skills.skill(name)?.id ?? []);
8836
+ this.#files = filterAllowList(ceiling.files, resolveWorkspace(this.#context.workspaces, shown.workspace, ceiling.workspaces)?.files() ?? [], (file) => file.path);
8837
+ this.#advertised = filterAdvertisedTools(this.#candidates(ceiling), ceiling.tools, this.#builtins.has("load") ? this.#context.skills.skills() : [], shown.skills);
8838
+ const head = this.#head;
8839
+ return head === void 0 || !matchesHeadLists(head.ceiling, ceiling) || head.ids.length !== this.#ids.length || head.ids.some((id, index) => id !== this.#ids[index]) || head.workspace !== shown.workspace || head.switchable !== this.switchable || head.files.length !== this.#files.length || head.files.some((file, index) => file !== this.#files[index]);
8840
+ }
8841
+ /**
8842
+ * Records what the working head was built with: the selection, the built array's length, the
8843
+ * view the last `observe` resolved, and the workspace instance the build rendered.
8844
+ *
8845
+ * @remarks
8846
+ * Call it right after the build: it resolves the rendered workspace from the inputs the build
8847
+ * read, the shown workspace and the context scope's `workspaces` list intersected with the
8848
+ * snapshot's.
8849
+ *
8850
+ * @param selection - The selection the build rendered
8851
+ * @param length - The length of the built array
8852
+ */
8853
+ record(selection, length) {
8854
+ this.#selection = selection;
8855
+ this.#length = length;
8856
+ this.#head = {
8857
+ ceiling: this.#ceiling,
8858
+ ids: this.#ids,
8859
+ workspace: this.#shown.workspace,
8860
+ switchable: this.switchable,
8861
+ files: this.#files,
8862
+ rendered: resolveWorkspace(this.#context.workspaces, this.#shown.workspace, intersectKeys(this.#context.scope?.workspaces, this.#snapshot?.workspaces))
8863
+ };
8864
+ }
8865
+ /**
8866
+ * Finds the tool a call names: the run's own for a built-in's name, the host's for any other.
8867
+ *
8868
+ * @param name - The tool name
8869
+ * @returns The tool, or undefined when none is registered under the name
8870
+ */
8871
+ tool(name) {
8872
+ return this.#builtins.has(name) ? this.#tools.tool(name) : this.#context.tools.tool(name);
8873
+ }
8874
+ /**
8875
+ * Executes one call through the run's manager for a built-in's name, and the host's for any other.
8876
+ *
8877
+ * @param call - The call to execute
8878
+ * @param context - The execution context
8879
+ * @returns The correlated result
8880
+ */
8881
+ execute(call, context) {
8882
+ return this.#builtins.has(call.name) ? this.#tools.execute(call, context) : this.#context.tools.execute(call, context);
8883
+ }
8884
+ #candidates(ceiling) {
8885
+ return [...this.#context.tools.definitions().filter((one) => !this.#builtins.has(one.name)), ...filterBuiltinCandidates(this.#tools.definitions(), ceiling)];
8886
+ }
8887
+ #live() {
8888
+ return intersectFilters(this.#context.scope, this.#scope);
8889
+ }
8890
+ #handle(name, args) {
8891
+ switch (name) {
8892
+ case "load": return this.#load(args.skill);
8893
+ case "unload": return this.#unload(args.skill);
8894
+ case "memo": return this.#memo(args.name);
8895
+ case "remember": return this.#remember(args.name, args.description, args.content);
8896
+ case "switch": return this.#switch(args.workspace);
8897
+ }
8898
+ }
8899
+ #load(skill) {
8900
+ const admitted = filterAllowList(this.#live().skills, this.#context.skills.skills(), (one) => one.name).map((one) => one.name);
8901
+ if (!(0, _orkestrel_contract.isString)(skill) || !admitted.includes(skill)) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("skill you can load", admitted));
8902
+ return BUILTIN_NOTES.loaded;
8903
+ }
8904
+ #unload(skill) {
8905
+ const loaded = collectDisclosure(this.#conversation, this.#enabled).skills;
8906
+ if (!(0, _orkestrel_contract.isString)(skill) || !loaded.includes(skill)) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("skill you loaded", loaded));
8907
+ return BUILTIN_NOTES.unloaded;
8908
+ }
8909
+ #memo(name) {
8910
+ const admitted = filterAllowList(this.#live().memos, this.#context.memos.memos(), (one) => one.name);
8911
+ const memo = admitted.find((one) => one.name === name);
8912
+ if (memo === void 0) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("memo you can read", admitted.map((one) => one.name)));
8913
+ return memo.content;
8914
+ }
8915
+ #remember(name, description, content) {
8916
+ const allowed = this.#live().memos;
8917
+ 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.");
8918
+ if (!(0, _orkestrel_contract.isString)(name) || !allowed.includes(name)) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("memo you may write", allowed));
8919
+ if (!(0, _orkestrel_contract.isString)(description) || !(0, _orkestrel_contract.isString)(content)) throw new _orkestrel_tool.ToolError("ARGUMENTS", "Send description and content as text.");
8920
+ 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.");
8921
+ const current = this.#context.memos.memo(name);
8922
+ 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.");
8923
+ this.#context.memos.add({
8924
+ name,
8925
+ description,
8926
+ content,
8927
+ origin: { conversation: this.#conversation.id }
8928
+ });
8929
+ return BUILTIN_NOTES.remembered;
8930
+ }
8931
+ #switch(workspace) {
8932
+ const allowed = this.#live().workspaces;
8933
+ 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.");
8934
+ const admitted = filterAllowList(allowed, this.#context.workspaces.workspaces(), (one) => one.id).map((one) => one.id);
8935
+ if (!(0, _orkestrel_contract.isString)(workspace) || !admitted.includes(workspace)) throw new _orkestrel_tool.ToolError("ARGUMENTS", describeCandidates("workspace you can select", admitted));
8936
+ return BUILTIN_NOTES.switched;
8937
+ }
8938
+ };
8939
+ // src/core/agents/ChannelCell.ts
8940
+ /**
8941
+ * Holds one value a `Channel` buffers and the cell pushed after it.
8942
+ *
8943
+ * @remarks
8944
+ * A `Channel` keeps its unread values as a chain of these cells, from its `#head` cell to its
8945
+ * `#tail` cell, so no public signature names this class. The `#next` field is a latch: the
8946
+ * `link` method sets it once, and a later call keeps the first cell.
8947
+ */
8948
+ var ChannelCell = class {
8949
+ #value;
8950
+ #next;
8951
+ /**
8952
+ * Holds a value with no next cell.
8953
+ *
8954
+ * @param value - The value the cell holds
8955
+ */
8956
+ constructor(value) {
8957
+ this.#value = value;
8958
+ }
8959
+ /**
8960
+ * Returns the value the cell holds.
8961
+ *
8962
+ * @returns The value
8963
+ */
8964
+ get value() {
8965
+ return this.#value;
8966
+ }
8967
+ /**
8968
+ * Returns the cell pushed after this one.
8969
+ *
8970
+ * @returns The next cell, or undefined before one is linked
8971
+ */
8972
+ get next() {
8973
+ return this.#next;
8974
+ }
8975
+ /**
8976
+ * Links the cell pushed after this one, once.
8977
+ *
8978
+ * @param cell - The cell pushed after this one
8979
+ */
8980
+ link(cell) {
8981
+ this.#next ??= cell;
8982
+ }
8983
+ };
8984
+ // src/core/agents/Channel.ts
8985
+ /**
8986
+ * Buffers chunks in a minimal unbounded async channel — the eager pump writes them in (`push`)
8987
+ * and ends it (`close` / `fail`) regardless of consumption, while a consumer reads them back live
8988
+ * through the `drain` async-iterator. Decoupling write from read is what lets a producer make
8989
+ * progress without a consumer pulling, and it is why an agent's `result` settles whether or not
8990
+ * its `events` are drained.
8991
+ *
8992
+ * @remarks
8993
+ * A waiting `drain` call parks its void resolver in the `#waiting` field. The `push` method links a
8994
+ * cell after the `#tail` cell, and the `close` and `fail` methods flag the end; each then fires and
8995
+ * clears every parked resolver, so each parked reader wakes, reads the `#head` cell, and either
8996
+ * takes the next chunk, returns (on `close`), throws (on `fail`), or parks again when another
8997
+ * reader took the chunk first. Taking a chunk reads its value and moves `#head` to the next cell
8998
+ * synchronously, so each value reaches exactly one reader, a push and a take each cost constant
8999
+ * time, and every taken cell but the newest is released at once; the `#tail` field holds the newest
9000
+ * cell until the next push links after it, so a channel retains at most one taken value. A reader
9001
+ * suspended at its `yield` expression holds only the value it took, never a cell, so the bound on
9002
+ * retained values covers both the channel's fields and a suspended reader.
9003
+ * A `return()` issued while a read is parked waits for the next `push`, `close`, or `fail`.
9004
+ * The parked read takes a pushed value before that `return()` settles.
9005
+ *
9006
+ * @example
9007
+ * ```ts
9008
+ * import { Channel } from '@orkestrel/agent'
9009
+ *
9010
+ * const channel = new Channel<number>()
9011
+ * channel.push(1)
9012
+ * channel.close()
9013
+ * for await (const value of channel.drain()) {
9014
+ * value // 1
9015
+ * }
9016
+ * ```
9017
+ */
9018
+ var Channel = class {
9019
+ #head;
9020
+ #tail;
9021
+ #waiting = [];
9022
+ #closed = false;
9023
+ #failure;
9024
+ push(value) {
9025
+ const cell = new ChannelCell(value);
9026
+ this.#tail?.link(cell);
9027
+ this.#tail = cell;
9028
+ this.#head ??= cell;
9029
+ this.#signal();
9030
+ }
9031
+ close() {
9032
+ this.#closed = true;
9033
+ this.#signal();
9034
+ }
9035
+ fail(error) {
9036
+ if (this.#failure === void 0) this.#failure = { error };
9037
+ this.#closed = true;
9038
+ this.#signal();
9039
+ }
9040
+ async *drain() {
9041
+ for (;;) {
9042
+ while (this.#head !== void 0) {
9043
+ const value = this.#head.value;
9044
+ this.#head = this.#head.next;
9045
+ yield value;
9046
+ }
9047
+ if (this.#failure !== void 0) throw this.#failure.error;
9048
+ if (this.#closed) return;
9049
+ await this.#park();
9050
+ }
9051
+ }
9052
+ #signal() {
9053
+ const waiting = this.#waiting;
9054
+ this.#waiting = [];
9055
+ for (const wake of waiting) wake();
9056
+ }
9057
+ #park() {
9058
+ return new Promise((resolve) => {
9059
+ this.#waiting = [...this.#waiting, resolve];
9060
+ });
9061
+ }
9062
+ };
7486
9063
  // src/core/agents/Agent.ts
7487
9064
  /**
7488
9065
  * Composes a {@link ProviderInterface}, an {@link AgentContext}, and a
@@ -7547,9 +9124,12 @@ var Agent = class {
7547
9124
  #window;
7548
9125
  #strict;
7549
9126
  #emitter;
9127
+ #builtins;
9128
+ #workspace;
7550
9129
  #settled = "idle";
7551
9130
  #runs = /* @__PURE__ */ new Map();
7552
9131
  constructor(provider, options) {
9132
+ (0, _orkestrel_contract.refuseOptionKeys)(options, AGENT_OPTION_KEYS, "Agent", "AgentOptions");
7553
9133
  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
9134
  this.#limit = requireLimit(options?.limit ?? 10);
7555
9135
  this.#id = crypto.randomUUID();
@@ -7566,6 +9146,9 @@ var Agent = class {
7566
9146
  ...options?.conversations === void 0 ? {} : { conversations: options.conversations },
7567
9147
  ...options?.select === void 0 ? {} : { select: options.select }
7568
9148
  });
9149
+ this.#builtins = requireBuiltins(options?.builtins, this.#context.tools);
9150
+ 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");
9151
+ this.#workspace = requireWorkspace(options?.workspace);
7569
9152
  this.#timeoutMs = options?.timeout;
7570
9153
  this.#budget = options?.budget;
7571
9154
  this.#allowance = options?.allowance;
@@ -7592,6 +9175,7 @@ var Agent = class {
7592
9175
  return this.stream(options).result;
7593
9176
  }
7594
9177
  stream(options) {
9178
+ (0, _orkestrel_contract.refuseOptionKeys)(options, AGENT_RUN_KEYS, "Agent", "AgentRunOptions");
7595
9179
  const prompt = options?.prompt;
7596
9180
  if (prompt !== void 0 && !agentPromptContract.is(prompt)) {
7597
9181
  const faults = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.describeFaults)(agentPromptContract.audit(prompt), agentPromptContract.schema));
@@ -7604,11 +9188,13 @@ var Agent = class {
7604
9188
  }
7605
9189
  });
7606
9190
  }
9191
+ const workspace = requireWorkspace(options?.workspace) ?? this.#workspace;
7607
9192
  const limit = options?.limit === void 0 ? this.#limit : requireLimit(options.limit);
7608
9193
  const budget = requireBudget(options?.budget ?? this.#budget, "budget");
7609
9194
  const allowance = requireBudget(options?.allowance ?? this.#allowance, "allowance");
7610
9195
  const held = [budget, allowance].filter((one, index, all) => one !== void 0 && all.indexOf(one) === index);
7611
9196
  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 } });
9197
+ const scope = requireScope(options?.scope);
7612
9198
  const conversation = requireConversation(options?.conversation) ?? this.#context.messages;
7613
9199
  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
9200
  agent: this.#id,
@@ -7632,7 +9218,8 @@ var Agent = class {
7632
9218
  this.#emitter.emit("start", this.id);
7633
9219
  const channel = new Channel();
7634
9220
  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));
9221
+ const run = new AgentRun(this.#context, conversation, scope, workspace, this.#builtins);
9222
+ 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
9223
  settled.promise.catch(() => {});
7637
9224
  return {
7638
9225
  events: this.#drainEvents(channel, abort),
@@ -7662,10 +9249,10 @@ var Agent = class {
7662
9249
  settled.reject(error);
7663
9250
  this.#emitter.emit("error", error);
7664
9251
  }
7665
- async #drive(abort, conversation, channel, think, schema, limit, budget, allowance) {
7666
- const run = this.#execute(abort, conversation, think, schema, limit, budget, allowance);
9252
+ async #drive(abort, run, channel, think, schema, limit, budget, allowance) {
9253
+ const steps = this.#execute(abort, run, think, schema, limit, budget, allowance);
7667
9254
  for (;;) {
7668
- const next = await run.next();
9255
+ const next = await steps.next();
7669
9256
  if (next.done === true) return next.value;
7670
9257
  channel.push(next.value);
7671
9258
  }
@@ -7677,15 +9264,16 @@ var Agent = class {
7677
9264
  abort.abort();
7678
9265
  }
7679
9266
  }
7680
- async *#execute(abort, conversation, think, schema, limit, budget, allowance) {
9267
+ async *#execute(abort, run, think, schema, limit, budget, allowance) {
9268
+ const conversation = run.conversation;
7681
9269
  const last = conversation.view().at(-1);
7682
9270
  const request = last?.role === "user" ? last : void 0;
7683
- const selection = this.#select(conversation, request, abort, budget);
9271
+ run.observe();
9272
+ const selection = this.#select(run, request, abort, budget);
7684
9273
  const entry = selection === void 0 ? {
7685
- messages: this.#build(conversation),
9274
+ messages: this.#build(run),
7686
9275
  usage: void 0
7687
9276
  } : await selection;
7688
- const tools = this.#context.tools;
7689
9277
  const options = think === void 0 && schema === void 0 ? void 0 : {
7690
9278
  ...think === void 0 ? {} : { think },
7691
9279
  ...schema === void 0 ? {} : { schema }
@@ -7694,14 +9282,14 @@ var Agent = class {
7694
9282
  const turns = [];
7695
9283
  while (turns.length < limit && (turns.at(-1)?.outcome ?? "tools") === "tools") {
7696
9284
  const index = turns.length;
7697
- const base = turns.at(-1)?.messages ?? entry.messages;
9285
+ const prior = turns.at(-1)?.messages ?? entry.messages;
7698
9286
  const futile = turns.at(-1)?.futile ?? false;
7699
9287
  const stopped = {
7700
9288
  content: "",
7701
9289
  thinking: void 0,
7702
9290
  usage: void 0,
7703
9291
  spent: void 0,
7704
- messages: base,
9292
+ messages: prior,
7705
9293
  outcome: "cancel",
7706
9294
  partial: true,
7707
9295
  futile,
@@ -7720,9 +9308,10 @@ var Agent = class {
7720
9308
  turns.push(stopped);
7721
9309
  continue;
7722
9310
  }
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)]));
9311
+ const base = run.observe() ? this.#refresh(run, prior) : prior;
9312
+ const names = run.ceiling.tools;
9313
+ const advertised = run.advertised;
9314
+ const held = new Map(advertised.map((definition) => [definition.name, run.tool(definition.name)]));
7726
9315
  const definitions = advertised.length > 0 ? advertised : void 0;
7727
9316
  const composed = this.#compose(base, definitions, options);
7728
9317
  const admitted = this.#window === void 0 ? {
@@ -7730,7 +9319,7 @@ var Agent = class {
7730
9319
  messages: base,
7731
9320
  spent: void 0,
7732
9321
  futile
7733
- } : await this.#admit(this.#window, conversation, composed, base, request, index > 0, futile, abort, budget);
9322
+ } : await this.#admit(this.#window, run, composed, base, request, index > 0, futile, abort, budget);
7734
9323
  if (admitted.dispatch === void 0) {
7735
9324
  turns.push({
7736
9325
  ...stopped,
@@ -7740,12 +9329,25 @@ var Agent = class {
7740
9329
  });
7741
9330
  continue;
7742
9331
  }
9332
+ const dispatch = admitted.dispatch;
9333
+ Object.freeze(dispatch.messages);
9334
+ if (dispatch.tools !== void 0) Object.freeze(dispatch.tools);
9335
+ this.#emitter.emit("request", index, Object.freeze(dispatch), conversation);
9336
+ if (abort.signal.aborted) {
9337
+ turns.push({
9338
+ ...stopped,
9339
+ messages: admitted.messages,
9340
+ spent: admitted.spent,
9341
+ futile: admitted.futile
9342
+ });
9343
+ continue;
9344
+ }
7743
9345
  const streamed = [];
7744
9346
  const meter = allowance === void 0 ? void 0 : (0, _orkestrel_budget.createTokenMeter)({
7745
9347
  budget: allowance,
7746
9348
  estimator: estimateTokens
7747
9349
  });
7748
- const provided = yield* this.#provide(admitted.dispatch, abort.signal, streamed, meter);
9350
+ const provided = yield* this.#provide(dispatch, abort.signal, streamed, meter);
7749
9351
  const settled = this.#reconcile(provided, abort, streamed, admitted, meter, budget, allowance);
7750
9352
  if (!provided.success) {
7751
9353
  turns.push({
@@ -7768,8 +9370,8 @@ var Agent = class {
7768
9370
  if (result.tools !== void 0 && result.tools.length > 0) {
7769
9371
  const admittedCalls = filterAllowList(names, result.tools, (call) => call.name);
7770
9372
  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);
9373
+ if (authorization.asked.length > 0 && !abort.signal.aborted) yield* this.#approve(run, authorization, abort.signal);
9374
+ if (!abort.signal.aborted) this.#confirm(run, authorization, advertised);
7773
9375
  if (abort.signal.aborted) {
7774
9376
  turns.push({
7775
9377
  ...settled,
@@ -7784,7 +9386,7 @@ var Agent = class {
7784
9386
  calls: result.tools,
7785
9387
  ...recorded
7786
9388
  });
7787
- const results = await this.#dispatch(tools, result.tools, abort.signal, authorization, advertised);
9389
+ const results = await this.#dispatch(run, result.tools, abort.signal, authorization, advertised);
7788
9390
  const replies = [];
7789
9391
  for (const [position, call] of result.tools.entries()) {
7790
9392
  const dispatched = results[position];
@@ -7880,11 +9482,11 @@ var Agent = class {
7880
9482
  #compose(messages, tools, options) {
7881
9483
  return {
7882
9484
  messages: [...stripThinking(messages, this.#replay)],
7883
- ...tools === void 0 ? {} : { tools },
9485
+ ...tools === void 0 ? {} : { tools: [...tools] },
7884
9486
  ...options === void 0 ? {} : { options }
7885
9487
  };
7886
9488
  }
7887
- async #admit(window, conversation, composed, messages, request, latch, futile, abort, budget) {
9489
+ async #admit(window, run, composed, messages, request, latch, futile, abort, budget) {
7888
9490
  window.clear();
7889
9491
  window.consume(composed);
7890
9492
  if (!window.exhausted) return {
@@ -7899,15 +9501,16 @@ var Agent = class {
7899
9501
  spent: void 0,
7900
9502
  futile
7901
9503
  };
9504
+ const conversation = run.conversation;
7902
9505
  if (!conversation.summarizable || futile) return {
7903
9506
  dispatch: this.#enforce(window, composed),
7904
9507
  messages,
7905
9508
  spent: void 0,
7906
9509
  futile
7907
9510
  };
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));
9511
+ 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
9512
  }
7910
- async #refold(section, window, conversation, composed, messages, request, latch, abort, budget) {
9513
+ async #refold(section, window, run, composed, messages, request, latch, abort, budget) {
7911
9514
  if (abort.signal.aborted) return {
7912
9515
  dispatch: void 0,
7913
9516
  messages,
@@ -7920,9 +9523,9 @@ var Agent = class {
7920
9523
  spent: void 0,
7921
9524
  futile: latch
7922
9525
  };
7923
- const selection = this.#select(conversation, request, abort, budget);
9526
+ const selection = this.#select(run, request, abort, budget);
7924
9527
  const rebuilt = selection === void 0 ? {
7925
- messages: this.#build(conversation),
9528
+ messages: this.#build(run),
7926
9529
  usage: void 0
7927
9530
  } : await selection;
7928
9531
  if (abort.signal.aborted) return {
@@ -7971,47 +9574,61 @@ var Agent = class {
7971
9574
  } });
7972
9575
  return request;
7973
9576
  }
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));
9577
+ #select(run, request, abort, budget) {
9578
+ const pending = request === void 0 ? void 0 : this.#context.select(run.conversation, request, abort.signal, run.scope);
9579
+ return pending === void 0 ? void 0 : pending.then((selection) => this.#adopt(run, selection, abort, budget), (error) => this.#reject(run, error, abort));
7977
9580
  }
7978
- #adopt(conversation, selection, abort, budget) {
9581
+ #adopt(run, selection, abort, budget) {
7979
9582
  const usage = selection.usage === void 0 ? void 0 : sanitizeUsage(selection.usage);
7980
9583
  if (usage !== void 0) chargeUsage(budget, usage, 0);
7981
9584
  if (abort.signal.aborted) return {
7982
- messages: this.#build(conversation),
9585
+ messages: this.#build(run),
7983
9586
  usage
7984
9587
  };
7985
9588
  if (selection.fault !== void 0) {
7986
9589
  this.#emitter.emit("fault", selection.fault);
7987
9590
  if (abort.signal.aborted) return {
7988
- messages: this.#build(conversation),
9591
+ messages: this.#build(run),
7989
9592
  usage
7990
9593
  };
7991
9594
  if (this.#strict) throw selection.fault;
7992
9595
  }
7993
- const messages = this.#build(conversation, selection);
9596
+ const messages = this.#build(run, selection);
7994
9597
  this.#emitter.emit("select", selection);
7995
9598
  return {
7996
9599
  messages,
7997
9600
  usage
7998
9601
  };
7999
9602
  }
8000
- #reject(conversation, error, abort) {
9603
+ #reject(run, error, abort) {
8001
9604
  if (!abort.signal.aborted) {
8002
9605
  this.#emitter.emit("fault", error);
8003
9606
  if (this.#strict && !abort.signal.aborted) throw error;
8004
9607
  }
8005
9608
  return {
8006
- messages: this.#build(conversation),
9609
+ messages: this.#build(run),
8007
9610
  usage: void 0
8008
9611
  };
8009
9612
  }
8010
- #build(conversation, selection) {
8011
- return this.#context.build(selection ?? {
8012
- messages: conversation.view(),
9613
+ #build(run, selection) {
9614
+ const rendered = selection ?? {
9615
+ messages: run.conversation.view(),
8013
9616
  judgments: []
9617
+ };
9618
+ const scope = run.snapshot;
9619
+ const disclosure = run.disclosure;
9620
+ const messages = this.#context.build({
9621
+ selection: rendered,
9622
+ ...scope === void 0 ? {} : { scope },
9623
+ ...disclosure === void 0 ? {} : { disclosure },
9624
+ ...run.switchable ? { switchable: true } : {}
8014
9625
  });
9626
+ run.record(rendered, messages.length);
9627
+ return messages;
9628
+ }
9629
+ #refresh(run, messages) {
9630
+ const added = messages.slice(run.length);
9631
+ return [...this.#build(run, run.selection), ...added];
8015
9632
  }
8016
9633
  #authorize(calls, admitted, advertised, held) {
8017
9634
  const allowed = /* @__PURE__ */ new Map();
@@ -8084,7 +9701,7 @@ var Agent = class {
8084
9701
  }
8085
9702
  return (0, _orkestrel_contract.success)(evaluated.value);
8086
9703
  }
8087
- async *#approve(tools, authorization, signal) {
9704
+ async *#approve(run, authorization, signal) {
8088
9705
  const authority = this.#authority;
8089
9706
  for (const { index, call, decision, tool } of authorization.asked) {
8090
9707
  if (signal.aborted) return;
@@ -8113,7 +9730,7 @@ var Agent = class {
8113
9730
  };
8114
9731
  const outcome = await raceSignal(answer, signal).catch(() => void 0);
8115
9732
  if (outcome === void 0 || signal.aborted) return;
8116
- if (outcome.approved && tools.tool(call.name) === tool) {
9733
+ if (outcome.approved && run.tool(call.name) === tool) {
8117
9734
  authorization.allowed.set(index, {
8118
9735
  call,
8119
9736
  tool
@@ -8125,29 +9742,32 @@ var Agent = class {
8125
9742
  }
8126
9743
  }
8127
9744
  }
8128
- #changed(tools, call, tool, advertised) {
9745
+ #changed(run, call, tool, advertised) {
8129
9746
  const same = (0, _orkestrel_contract.attempt)(() => {
8130
- const current = tools.tool(call.name);
9747
+ const current = run.tool(call.name);
8131
9748
  const definition = advertised.find((entry) => entry.name === call.name);
8132
9749
  return current === tool && current?.annotations?.consequential === true === (definition?.annotations?.consequential === true);
8133
9750
  });
8134
9751
  return !same.success || !same.value;
8135
9752
  }
8136
- #confirm(tools, authorization, advertised) {
9753
+ #confirm(run, authorization, advertised) {
8137
9754
  for (const [index, { call, tool }] of authorization.allowed) {
8138
- if (!this.#changed(tools, call, tool, advertised)) continue;
9755
+ if (!this.#changed(run, call, tool, advertised)) continue;
8139
9756
  authorization.allowed.delete(index);
8140
9757
  authorization.denials.set(index, denyCall(call, AUTHORITY_NOTES.denied));
8141
9758
  this.#emitter.emit("deny", call, `${call.name} changed after its decision`, void 0);
8142
- this.#confirm(tools, authorization, advertised);
9759
+ this.#confirm(run, authorization, advertised);
8143
9760
  return;
8144
9761
  }
8145
9762
  }
8146
- async #dispatch(tools, calls, signal, authorization, advertised) {
9763
+ async #dispatch(run, calls, signal, authorization, advertised) {
8147
9764
  const { allowed, denials } = authorization;
8148
9765
  const entries = [...allowed].sort(([left], [right]) => left - right);
8149
9766
  const started = entries.map(([, { call, tool }]) => {
8150
- if (!this.#changed(tools, call, tool, advertised)) return tools.execute(call, { signal });
9767
+ if (!this.#changed(run, call, tool, advertised)) return run.execute(call, {
9768
+ signal,
9769
+ caller: run.caller
9770
+ });
8151
9771
  this.#emitter.emit("deny", call, `${call.name} changed after its decision`, void 0);
8152
9772
  return denyCall(call, AUTHORITY_NOTES.denied);
8153
9773
  });
@@ -8216,6 +9836,17 @@ var Agent = class {
8216
9836
  * awaited inline, so a bounded runner frees the parent's slot for them. `createAgentRunner`
8217
9837
  * creates one instance per runner and binds its `execute` method as the runner's handler.
8218
9838
  *
9839
+ * A child job runs within its parent. Before a unit spawns or runs, on every attempt, the
9840
+ * `execute` method walks the whole tree under it through the `scanJobTree` helper and refuses the
9841
+ * unit when any job exceeds its own parent on `tools`, `builtins`, `scope`, `authority`,
9842
+ * `provider`, `scheduler`, `limit`, `timeout`, or `budget`, or when a job is malformed or its build
9843
+ * would refuse it (the lines `collectJobRefusals` returns), so nothing in a refused tree spawns, builds,
9844
+ * or runs. A child's `workspace` is free, because it renders nothing its own `scope`
9845
+ * omits. Each child is held within its parent alone, so the children of one parent can together
9846
+ * spend more than it. A job that needs another provider, another authority, a skill, memo,
9847
+ * workspace, or built-in its parent lacks, or more room than its parent is a root job the host
9848
+ * enqueues.
9849
+ *
8219
9850
  * @example
8220
9851
  * ```ts
8221
9852
  * import type { AgentJobInput, ProviderInterface } from '@orkestrel/agent'
@@ -8245,14 +9876,32 @@ var AgentFanout = class {
8245
9876
  this.#partial = partial;
8246
9877
  }
8247
9878
  /**
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.
9879
+ * Executes one unit: refuses its tree when a job in it is malformed or exceeds its parent, spawns
9880
+ * its declared children unless an earlier attempt of the unit already did, then runs the
9881
+ * rehydrated agent and settles it under the partial policy.
9882
+ *
9883
+ * @remarks
9884
+ * The check runs on every attempt, before the unit spawns or runs, and throws synchronously. The
9885
+ * error's message opens with the rule each kind of finding breaks, a refused job's rule when a
9886
+ * finding opens with `JOB_REFUSAL_PREFIX` and the within-parent rule when a finding names an
9887
+ * exceeded member, then lists every finding, one a line. Its `context.findings` holds the same
9888
+ * findings in walk order.
8250
9889
  *
8251
9890
  * @param controller - The runner controller for this unit's attempt
8252
9891
  * @returns The settled agent result of the unit
9892
+ * @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
9893
  * @throws {AgentJobError} Thrown when the run ended partial and the partial policy is `false`
8254
9894
  */
8255
9895
  execute(controller) {
9896
+ const findings = scanJobTree(controller.input);
9897
+ if (findings.length > 0) {
9898
+ const refused = findings.filter((finding) => finding.startsWith(JOB_REFUSAL_PREFIX));
9899
+ throw new AgentError("REGISTRY", [
9900
+ ...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"],
9901
+ ...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"],
9902
+ ...findings
9903
+ ].join("\n"), { context: { findings } });
9904
+ }
8256
9905
  const children = controller.input.children;
8257
9906
  if (children !== void 0 && !this.#spawned.has(controller.id)) {
8258
9907
  this.#spawned = /* @__PURE__ */ new Set([...this.#spawned, controller.id]);
@@ -8306,6 +9955,7 @@ var Authority = class {
8306
9955
  #handler;
8307
9956
  constructor(options) {
8308
9957
  this.#fallback = requireFallback(options);
9958
+ (0, _orkestrel_contract.refuseOptionKeys)(options, AUTHORITY_OPTION_KEYS, "Authority", "AuthorityOptions");
8309
9959
  this.#rules = requireRules(options.rules);
8310
9960
  this.#handler = options.ask;
8311
9961
  }
@@ -8428,16 +10078,22 @@ var Approver = class {
8428
10078
  * the message `unknown <category>: <name>` when it is absent — an unknown name in a
8429
10079
  * rehydrated job is a programmer / config error that must fail loudly at build time, never
8430
10080
  * 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` /
10081
+ * - **`build` rehydrates.** Refuse with `code: 'REGISTRY'` a job `agentJobContract` refuses,
10082
+ * its faults under `context.faults`, and a job `collectJobRefusals` refuses, its lines under
10083
+ * `context.refusals`: tools or built-ins with no `authority`, a `builtins` list an agent
10084
+ * refuses, and a built-in the job's `tools` also name. Then resolve the job's `provider`;
10085
+ * assemble a fresh {@link ToolManager} from the `tools` names; rebuild the token `budget` from
10086
+ * its ceiling (`createTokenBudget({ max })`); resolve the optional `authority` /
8435
10087
  * `scheduler` names; construct the {@link Agent} with `system` / `limit` / `timeout` /
8436
10088
  * the threaded `signal`; seed its context with the job's `messages`; return it. The
8437
10089
  * `signal` is the queue attempt's / runner unit's cancel, so a bounded abort propagates
8438
10090
  * 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.
10091
+ * decides success vs. retry). A job is a record, so a malformed `limit`, `timeout`, `budget`,
10092
+ * seed role, or `builtins` list refuses as one, with `code: 'REGISTRY'`.
10093
+ * - **A job holds what it names.** A job with a `scope` gets the registry's `skills`, `memos`, and
10094
+ * `workspaces` managers, which every scoped job shares, and a scope named `job` built from its
10095
+ * three lists; a job with no `scope` gets none of them. Its `workspace` and `builtins` pass to
10096
+ * the agent.
8441
10097
  * - **Event-free.** A pure resolver — no Emitter, no events.
8442
10098
  *
8443
10099
  * @example
@@ -8454,12 +10110,19 @@ var AgentRegistry = class {
8454
10110
  #authorities;
8455
10111
  #schedulers;
8456
10112
  #store;
10113
+ #skills;
10114
+ #memos;
10115
+ #workspaces;
8457
10116
  constructor(options) {
10117
+ (0, _orkestrel_contract.refuseOptionKeys)(options, REGISTRY_OPTION_KEYS, "AgentRegistry", "AgentRegistryOptions");
8458
10118
  this.#providers = new Map(Object.entries(options.providers));
8459
10119
  this.#tools = new Map(Object.entries(options.tools ?? {}));
8460
10120
  this.#authorities = new Map(Object.entries(options.authorities ?? {}));
8461
10121
  this.#schedulers = new Map(Object.entries(options.schedulers ?? {}));
8462
10122
  this.#store = options.store;
10123
+ this.#skills = options.skills;
10124
+ this.#memos = options.memos;
10125
+ this.#workspaces = options.workspaces;
8463
10126
  }
8464
10127
  provider(name) {
8465
10128
  return requireEntry(this.#providers, "provider", name);
@@ -8476,13 +10139,16 @@ var AgentRegistry = class {
8476
10139
  build(input, signal) {
8477
10140
  const faults = agentJobContract.audit(input);
8478
10141
  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 } });
10142
+ const refusals = collectJobRefusals(input);
10143
+ 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
10144
  const agent = new Agent(this.provider(input.provider), this.#buildOptions(input, signal));
8480
10145
  for (const message of input.messages ?? []) agent.context.messages.add(message);
8481
10146
  return agent;
8482
10147
  }
8483
10148
  #buildOptions(input, signal) {
8484
10149
  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 } });
10150
+ const builtins = input.builtins ?? [];
10151
+ const scope = input.scope;
8486
10152
  const budget = input.budget === void 0 ? void 0 : (0, _orkestrel_budget.createTokenBudget)({ max: input.budget });
8487
10153
  return {
8488
10154
  ...names.length === 0 ? {} : { tools: this.#buildToolManager(names) },
@@ -8493,6 +10159,15 @@ var AgentRegistry = class {
8493
10159
  ...input.authority === void 0 ? {} : { authority: this.authority(input.authority) },
8494
10160
  ...input.scheduler === void 0 ? {} : { scheduler: this.scheduler(input.scheduler) },
8495
10161
  ...this.#store === void 0 ? {} : { conversations: new ConversationManager({ store: this.#store }) },
10162
+ ...scope === void 0 || this.#skills === void 0 ? {} : { skills: this.#skills },
10163
+ ...scope === void 0 || this.#memos === void 0 ? {} : { memos: this.#memos },
10164
+ ...scope === void 0 || this.#workspaces === void 0 ? {} : { workspaces: this.#workspaces },
10165
+ ...scope === void 0 ? {} : { scope: createScope({
10166
+ name: "job",
10167
+ ...scope
10168
+ }) },
10169
+ ...input.workspace === void 0 ? {} : { workspace: input.workspace },
10170
+ ...builtins.length === 0 ? {} : { builtins },
8496
10171
  ...signal === void 0 ? {} : { signal }
8497
10172
  };
8498
10173
  }
@@ -8727,6 +10402,7 @@ function createAgentRegistry(options) {
8727
10402
  * ```
8728
10403
  */
8729
10404
  function createAgentQueue(options) {
10405
+ (0, _orkestrel_contract.refuseOptionKeys)(options, QUEUE_OPTION_KEYS, "createAgentQueue", "AgentQueueOptions");
8730
10406
  const { registry, partial = false, store } = options;
8731
10407
  return (0, _orkestrel_queue.createQueue)({
8732
10408
  ...extractQueueOptions(options),
@@ -8773,6 +10449,7 @@ function createAgentQueue(options) {
8773
10449
  * ```
8774
10450
  */
8775
10451
  function createAgentRunner(options) {
10452
+ (0, _orkestrel_contract.refuseOptionKeys)(options, RUNNER_OPTION_KEYS, "createAgentRunner", "AgentRunnerOptions");
8776
10453
  const { registry, partial = false } = options;
8777
10454
  const fanout = new AgentFanout(registry, partial);
8778
10455
  return (0, _orkestrel_workflow.createRunner)({
@@ -8780,6 +10457,38 @@ function createAgentRunner(options) {
8780
10457
  handler: fanout.execute.bind(fanout)
8781
10458
  });
8782
10459
  }
10460
+ /**
10461
+ * Creates the tool a run serves for one built-in: its description and annotations from
10462
+ * `BUILTIN_TOOLS`, its argument shape from `builtinToolContracts`, and the given handler.
10463
+ *
10464
+ * @remarks
10465
+ * The tool's door refuses a missing, empty, over-long, or extra argument before the handler runs,
10466
+ * and repairs a value that converts to a string without loss, such as a number, so the handler
10467
+ * reads only arguments the shape admits. Its definition equals the one `collectBuiltinDefinitions`
10468
+ * returns for the same name.
10469
+ *
10470
+ * @param name - The built-in to create
10471
+ * @param execute - The handler the door hands admitted arguments to
10472
+ * @returns The tool
10473
+ *
10474
+ * @example
10475
+ * ```ts
10476
+ * import { createBuiltinTool } from '@orkestrel/agent'
10477
+ *
10478
+ * const memo = createBuiltinTool('memo', (args) => `read ${String(args.name)}`)
10479
+ * memo.annotations // { pure: true, untrusted: true }
10480
+ * ```
10481
+ */
10482
+ function createBuiltinTool(name, execute) {
10483
+ const declared = BUILTIN_TOOLS[name];
10484
+ return (0, _orkestrel_tool.createTool)({
10485
+ name,
10486
+ description: declared.description,
10487
+ ...declared.annotations === void 0 ? {} : { annotations: declared.annotations },
10488
+ contract: builtinToolContracts[name].shape,
10489
+ execute
10490
+ });
10491
+ }
8783
10492
  // src/core/ledgers/constants.ts
8784
10493
  /**
8785
10494
  * Lists the categories the ledger files a message under, in the order the category question
@@ -8854,19 +10563,25 @@ var LEDGER_QUESTIONS = Object.freeze({
8854
10563
  /**
8855
10564
  * Supplies the text of each ledger note, worded for a model that answers in its final
8856
10565
  * message.
10566
+ *
10567
+ * @remarks
10568
+ * `overflow` names `unload` because a skill the thread shows is the room a model can give back: a
10569
+ * successful `unload` reopens every call the room denied in that request.
8857
10570
  */
8858
10571
  var LEDGER_NOTES = Object.freeze({
8859
10572
  cue: "Give your complete answer to the request now, from what you already have.",
8860
10573
  results: "Your lookups and recalls in this request returned:",
8861
10574
  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"
10575
+ closure: "recall is closed for the rest of this request; give your final answer from what you have",
10576
+ 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
10577
  });
8864
10578
  /**
8865
- * Supplies the default prompt and tail shares.
10579
+ * Supplies the default prompt, tail, and skills shares.
8866
10580
  */
8867
10581
  var DEFAULT_LEDGER_SHARE = Object.freeze({
8868
10582
  prompt: .7,
8869
- tail: .35
10583
+ tail: .35,
10584
+ skills: .5
8870
10585
  });
8871
10586
  /**
8872
10587
  * Caps the tool-iteration turns of a ledger's agent at 8 turns.
@@ -8903,6 +10618,85 @@ var LEDGER_SCALE_DRIFT = .06;
8903
10618
  var LEDGER_WORD = /[\p{L}\p{N}]+/gu;
8904
10619
  /** Matches the logprob decoding failure `invalid or duplicate top logprob token`, which the ledger holds for the ledger's life. */
8905
10620
  var DETERMINISTIC_JUDGE_ERROR = /invalid or duplicate top logprob token/;
10621
+ /**
10622
+ * Lists the members `LedgerOptions` declares, frozen — the keys the `Ledger` constructor admits.
10623
+ *
10624
+ * @remarks
10625
+ * The constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an own
10626
+ * enumerable member outside it refuses with a `ContractError` coded `'structure'`.
10627
+ */
10628
+ var LEDGER_OPTION_KEYS = Object.freeze([
10629
+ "judge",
10630
+ "system",
10631
+ "topics",
10632
+ "questions",
10633
+ "thresholds",
10634
+ "capacity",
10635
+ "predict",
10636
+ "think",
10637
+ "gauge",
10638
+ "lookups",
10639
+ "writes",
10640
+ "share",
10641
+ "recall",
10642
+ "correction",
10643
+ "agent",
10644
+ "snapshot"
10645
+ ]);
10646
+ /**
10647
+ * Lists the members the `LedgerAgentOptions` pick declares, frozen — the keys a ledger's `agent`
10648
+ * option admits.
10649
+ *
10650
+ * @remarks
10651
+ * The `Ledger` constructor passes the list to `refuseOptionKeys` of `@orkestrel/contract`, so an
10652
+ * own enumerable member outside it, an `AgentOptions` member the pick leaves out included, refuses
10653
+ * with a `ContractError` coded `'structure'`.
10654
+ */
10655
+ var LEDGER_AGENT_KEYS = Object.freeze([
10656
+ "limit",
10657
+ "timeout",
10658
+ "on",
10659
+ "scheduler",
10660
+ "instructions",
10661
+ "skills",
10662
+ "memos",
10663
+ "workspaces",
10664
+ "strict",
10665
+ "authority",
10666
+ "builtins",
10667
+ "workspace"
10668
+ ]);
10669
+ /** Lists the members `ClassifierOptions` declares, frozen — the keys the `Classifier` constructor admits. */
10670
+ var CLASSIFIER_OPTION_KEYS = Object.freeze([
10671
+ "strict",
10672
+ "conversation",
10673
+ "judge",
10674
+ "questions",
10675
+ "topics",
10676
+ "thresholds",
10677
+ "assign",
10678
+ "entities",
10679
+ "correction",
10680
+ "scheduler"
10681
+ ]);
10682
+ /** Lists the members `GaugeOptions` declares, frozen — the keys the `Gauge` constructor admits. */
10683
+ var GAUGE_OPTION_KEYS = Object.freeze([
10684
+ "scale",
10685
+ "overhead",
10686
+ "capacity",
10687
+ "predict",
10688
+ "replay"
10689
+ ]);
10690
+ /**
10691
+ * Sets the 3 UTF-8 bytes one UTF-16 code unit encodes to at most, the divisor that sizes the
10692
+ * workspace text a calibration's third call carries.
10693
+ *
10694
+ * @remarks
10695
+ * A tokenizer that falls back to bytes charges at most one token a byte, so workspace text cut to the
10696
+ * capacity the call without definitions left, divided by this figure, in characters, reports at most
10697
+ * `capacity` apart from a template's few tokens for an added message.
10698
+ */
10699
+ var LEDGER_SAMPLE_BYTES = 3;
8906
10700
  // src/core/ledgers/errors.ts
8907
10701
  /**
8908
10702
  * Reports an invalid ledger configuration, failed calibration, or unowned request selection,
@@ -8911,7 +10705,9 @@ var DETERMINISTIC_JUDGE_ERROR = /invalid or duplicate top logprob token/;
8911
10705
  * @remarks
8912
10706
  * A threshold, share, capacity, limit, topic, lookup, write, question, or gauge outside its bounds is a
8913
10707
  * 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
10708
+ * no prompt usage, or a calibration call, with the tool definitions, without them, or with the workspace text, that reports a prompt
10709
+ * over `capacity`, the head included, throws this with `'GAUGE'`; the second holds `capacity` and
10710
+ * `prompt` in its `context`. A snapshot option that `isLedgerSnapshot` refuses
8915
10711
  * throws this with `'SNAPSHOT'`, its `context` holding the `faults`. Narrow a caught value with {@link isLedgerError} and
8916
10712
  * branch on `error.code`. A selection without an active `respond` call, for a conversation other
8917
10713
  * than the ledger's, or for a request other than that call's request or a ledger note, reports
@@ -8953,7 +10749,7 @@ function isLedgerError(value) {
8953
10749
  return (0, _orkestrel_contract.holds)(() => {
8954
10750
  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
10751
  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;
10752
+ 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
10753
  const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
8958
10754
  return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
8959
10755
  });
@@ -8989,9 +10785,10 @@ function digestLedgerWindow(exchanges, messages) {
8989
10785
  * Digests tool definitions as the JSON a provider request carries them, in order.
8990
10786
  *
8991
10787
  * @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.
10788
+ * A ledger stores this digest with its gauge and compares it at a hydrate and at each `respond`
10789
+ * call, so it reuses the gauge only for the same definitions: an added, removed, reordered, or
10790
+ * edited definition changes the digest. The digest is a {@link computeLedgerChecksum} checksum and
10791
+ * makes no security claim.
8995
10792
  *
8996
10793
  * @param definitions - The tool definitions, as a tool manager's `definitions` method returns them
8997
10794
  * @returns The 16-digit hexadecimal digest, or undefined when JSON cannot render the definitions
@@ -9005,6 +10802,82 @@ function digestLedgerTools(definitions) {
9005
10802
  return text === void 0 ? void 0 : computeLedgerChecksum(text);
9006
10803
  }
9007
10804
  /**
10805
+ * Computes the tokens one estimate unit of a definition costs beyond the calibrated set.
10806
+ *
10807
+ * @remarks
10808
+ * The larger of `scale` and the calibrated set's overhead per estimate unit, or `scale` for an
10809
+ * empty set or one that estimates 0. A set's estimate is `estimateTokens` over its JSON text.
10810
+ *
10811
+ * @param calibrated - The definitions the gauge's overhead priced
10812
+ * @param gauge - The gauge's scale and overhead
10813
+ * @returns The tokens one estimate unit of a definition costs
10814
+ * @example
10815
+ * ```ts
10816
+ * computeToolDensity([], { scale: 1.1, overhead: 400 }) // 1.1
10817
+ * ```
10818
+ */
10819
+ function computeToolDensity(calibrated, gauge) {
10820
+ const estimate = calibrated.length === 0 ? 0 : estimateTokens(JSON.stringify(calibrated));
10821
+ return estimate > 0 ? Math.max(gauge.scale, gauge.overhead / estimate) : gauge.scale;
10822
+ }
10823
+ /**
10824
+ * Computes what a call's advertised set spares of the calibrated overhead and adds beyond it.
10825
+ *
10826
+ * @remarks
10827
+ * Definitions compare by their JSON text, so a definition replaced under its name by another text
10828
+ * is both left out and added. An empty advertised set spares the whole overhead. Otherwise the
10829
+ * calibrated definitions the call leaves out spare the lower of `scale` and the calibrated set's
10830
+ * overhead per estimate unit, times their estimate, so a credit never exceeds their proportional
10831
+ * share of the overhead; the definitions it advertises beyond the calibrated set add
10832
+ * {@link computeToolDensity}, the higher of the two, times theirs. A set's estimate is
10833
+ * `estimateTokens` over its JSON text, 0 for an empty set. The call's price is
10834
+ * `overhead - spared + added`.
10835
+ *
10836
+ * @param calibrated - The definitions the gauge's overhead priced
10837
+ * @param advertised - The definitions the call advertises
10838
+ * @param gauge - The gauge's scale and overhead
10839
+ * @returns The tokens the call spares and adds
10840
+ * @example
10841
+ * ```ts
10842
+ * computeToolPrice([], [], { scale: 1.1, overhead: 400 }) // { spared: 400, added: 0 }
10843
+ * ```
10844
+ */
10845
+ function computeToolPrice(calibrated, advertised, gauge) {
10846
+ if (advertised.length === 0) return {
10847
+ spared: gauge.overhead,
10848
+ added: 0
10849
+ };
10850
+ const sent = new Set(advertised.map((definition) => JSON.stringify(definition)));
10851
+ const priced = new Set(calibrated.map((definition) => JSON.stringify(definition)));
10852
+ const hidden = calibrated.filter((definition) => !sent.has(JSON.stringify(definition)));
10853
+ const extra = advertised.filter((definition) => !priced.has(JSON.stringify(definition)));
10854
+ return {
10855
+ spared: hidden.length === 0 ? 0 : Math.min(gauge.scale, gauge.overhead / estimateTokens(JSON.stringify(calibrated))) * estimateTokens(JSON.stringify(hidden)),
10856
+ added: extra.length === 0 ? 0 : computeToolDensity(calibrated, gauge) * estimateTokens(JSON.stringify(extra))
10857
+ };
10858
+ }
10859
+ /**
10860
+ * Computes the tokens loading a skill adds: its body, and the definitions it names beyond an advertised set.
10861
+ *
10862
+ * @remarks
10863
+ * The body is the text `renderSkillBody` renders, priced at `scale`, and a blank `content` renders
10864
+ * no body. The caller passes the definitions the skill names that the turn does not advertise,
10865
+ * priced as {@link computeToolPrice} prices an addition.
10866
+ *
10867
+ * @param skill - The skill to load
10868
+ * @param added - The definitions the skill names that the advertised set leaves out
10869
+ * @param calibrated - The definitions the gauge's overhead priced
10870
+ * @param gauge - The gauge's scale and overhead
10871
+ * @returns The tokens the load adds
10872
+ * @example
10873
+ * ```ts
10874
+ * computeSkillPrice({ name: 'refunds', content: 'Check the order first.' }, [], [], { scale: 1, overhead: 0 }) // 9
10875
+ * ```
10876
+ */
10877
+ function computeSkillPrice(skill, added, calibrated, gauge) {
10878
+ return (skill.content.trim() === "" ? 0 : gauge.scale * estimateTokens(renderSkillBody(skill))) + computeToolPrice(calibrated, [...calibrated, ...added], gauge).added;
10879
+ }
10880
+ /**
9008
10881
  * Computes a checksum of text: two 32-bit FNV-1a lanes over its code points, as 16 hexadecimal digits.
9009
10882
  *
9010
10883
  * @remarks
@@ -9233,23 +11106,6 @@ function splitWords(part) {
9233
11106
  return part.split(/\s+/).map((word) => word.replace(/^[\p{P}\p{S}]+|[\p{P}\p{S}]+$/gu, "").toLowerCase()).filter((word) => word !== "");
9234
11107
  }
9235
11108
  /**
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
11109
  * Ranks a source the records render for removal before the next prompt.
9254
11110
  *
9255
11111
  * @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 +11799,34 @@ function matchesCutLine(line) {
9943
11799
  /**
9944
11800
  * Renders the tail stub of a lookup result from an earlier request.
9945
11801
  *
11802
+ * @remarks
11803
+ * A `hidden` stub names the `recall` call that finds its result, with the first string argument,
11804
+ * trimmed, or `its id` when no argument is a string or that one is blank, only while `offered` is `true`. The ledger plans the
11805
+ * tail with every stub naming `recall`, then renders the tail's stubs again after it admits a window
11806
+ * rung, with `offered` set to whether that rung offers `recall` and the scope admits it, so the
11807
+ * second render can only shorten a stub and raises no price. `offered` changes no other state.
11808
+ *
9946
11809
  * @param name - The lookup tool name
9947
11810
  * @param args - The arguments the call carried; arguments JSON cannot carry leave the call as its bare tool name, per {@link renderLookupCall}
9948
11811
  * @param state - What became of the result: `failed`, `empty`, `shown` in the records, or `hidden` from them
9949
11812
  * @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`
11813
+ * @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
11814
  * @returns The stub text, which carries the call and its state
9951
11815
  *
9952
11816
  * @example
9953
11817
  * ```ts
9954
11818
  * renderStub('lookup_order', { id: 'BW-5512' }, 'hidden')
9955
11819
  * // 'lookup_order {"id":"BW-5512"}: result not shown; call recall with BW-5512'
11820
+ * renderStub('lookup_order', { id: 'BW-5512' }, 'hidden', false, false)
11821
+ * // 'lookup_order {"id":"BW-5512"}: result not shown'
9956
11822
  * ```
9957
11823
  */
9958
- function renderStub(name, args, state, write = false) {
11824
+ function renderStub(name, args, state, write = false, offered = true) {
9959
11825
  const head = renderLookupCall(name, args);
9960
11826
  if (state === "failed") return `${head}: failed`;
9961
11827
  if (state === "empty") return write ? `${head}: changed nothing` : `${head}: no record`;
9962
11828
  if (state === "shown") return `${head}: result shown under Records below`;
11829
+ if (!offered) return `${head}: result not shown`;
9963
11830
  return `${head}: result not shown; call recall with ${(Object.values(args).find(_orkestrel_contract.isString) ?? "").trim() || "its id"}`;
9964
11831
  }
9965
11832
  /**
@@ -10329,6 +12196,51 @@ function collectLedgerEvidence(messages, sources, amendments) {
10329
12196
  ]), { ...edge }]))
10330
12197
  };
10331
12198
  }
12199
+ /**
12200
+ * Cuts the workspace text a calibration sample adds over its head to a number of characters, or
12201
+ * returns `undefined` when the sample adds anything else.
12202
+ *
12203
+ * @remarks
12204
+ * The head and the shown sample are what a context's `build` returns for an empty turn without and
12205
+ * with the workspace: the shown sample holds the head's messages with the workspace section appended
12206
+ * to the context message, or one context message added where the head holds none. The cut keeps the
12207
+ * first `limit` characters of that added text and every other message as the sample holds it. A
12208
+ * sample whose messages differ from the head's in any other way, or that adds no text, returns
12209
+ * `undefined`.
12210
+ *
12211
+ * @param head - The head built with no workspace
12212
+ * @param shown - The head built with the workspace a request renders
12213
+ * @param limit - The characters of added text to keep
12214
+ * @returns The cut sample, or `undefined` when the sample adds no trailing text to one message
12215
+ *
12216
+ * @example
12217
+ * ```ts
12218
+ * import { cutWorkspaceSample } from '@orkestrel/agent'
12219
+ *
12220
+ * const head = [{ id: 's', role: 'system', content: 'Serve the desk.' }] as const
12221
+ * const shown = [...head, { id: 'c', role: 'user', content: '## Workspace\n\nFile: a.md' }] as const
12222
+ * cutWorkspaceSample(head, shown, 12)?.[1]?.content // '## Workspace'
12223
+ * ```
12224
+ */
12225
+ function cutWorkspaceSample(head, shown, limit) {
12226
+ const at = shown.findIndex((message, index) => message.role !== head[index]?.role || message.content !== head[index]?.content);
12227
+ const added = shown[at];
12228
+ const extended = shown.length === head.length ? head[at] : void 0;
12229
+ const prefix = extended?.content ?? "";
12230
+ const rest = shown.slice(at + 1);
12231
+ const held = head.slice(extended === void 0 ? at : at + 1);
12232
+ if (added === void 0 || shown.length - head.length > 1 || shown.length < head.length) return void 0;
12233
+ if (!added.content.startsWith(prefix) || added.content.length === prefix.length) return void 0;
12234
+ if (rest.length !== held.length || rest.some((message, index) => message.role !== held[index]?.role || message.content !== held[index]?.content)) return void 0;
12235
+ return [
12236
+ ...shown.slice(0, at),
12237
+ {
12238
+ ...added,
12239
+ content: prefix + added.content.slice(prefix.length, prefix.length + limit)
12240
+ },
12241
+ ...rest
12242
+ ];
12243
+ }
10332
12244
  // src/core/ledgers/Classifier.ts
10333
12245
  /**
10334
12246
  * Files messages through the conversation's judgment manager and reads their categories and corrections.
@@ -10349,6 +12261,7 @@ var Classifier = class {
10349
12261
  * @param options - The conversation, judge, questions, cutoffs, and handlers
10350
12262
  */
10351
12263
  constructor(options) {
12264
+ (0, _orkestrel_contract.refuseOptionKeys)(options, CLASSIFIER_OPTION_KEYS, "Classifier", "ClassifierOptions");
10352
12265
  this.#options = options;
10353
12266
  }
10354
12267
  /**
@@ -10725,6 +12638,7 @@ var Gauge = class {
10725
12638
  * @throws {LedgerError} Thrown when `predict` is not a nonnegative safe integer less than `capacity` (code `'CAPACITY'`)
10726
12639
  */
10727
12640
  constructor(options) {
12641
+ (0, _orkestrel_contract.refuseOptionKeys)(options, GAUGE_OPTION_KEYS, "Gauge", "GaugeOptions");
10728
12642
  if (!(0, _orkestrel_contract.isFiniteNumber)(options.scale) || options.scale <= 0) throw new LedgerError("GAUGE", "gauge scale must be finite and greater than 0");
10729
12643
  if (!(0, _orkestrel_contract.isFiniteNumber)(options.overhead) || options.overhead < 0) throw new LedgerError("GAUGE", "gauge overhead must be finite and at least 0");
10730
12644
  if (!isPositiveSafeInteger(options.capacity)) throw new LedgerError("CAPACITY", "gauge capacity must be a positive safe integer");
@@ -10755,7 +12669,7 @@ var Gauge = class {
10755
12669
  }
10756
12670
  remainder(calls) {
10757
12671
  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);
12672
+ 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
12673
  return Math.max(0, this.#capacity - used);
10760
12674
  }
10761
12675
  reserve(calls, longest) {
@@ -10775,7 +12689,7 @@ var Gauge = class {
10775
12689
  }
10776
12690
  observe(calls, reply) {
10777
12691
  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)) {
12692
+ 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
12693
  const priced = first.prompt - Math.max(0, this.#overhead - (first.spared ?? 0));
10780
12694
  if (priced > 0) this.#scale = priced / first.estimate;
10781
12695
  }
@@ -10983,6 +12897,7 @@ var LedgerRequest = class {
10983
12897
  #admitted = /* @__PURE__ */ new Set();
10984
12898
  #denials = /* @__PURE__ */ new Set();
10985
12899
  #listed = [];
12900
+ #commits = [];
10986
12901
  #recalls = 0;
10987
12902
  #closed = false;
10988
12903
  /**
@@ -11169,6 +13084,23 @@ var LedgerRequest = class {
11169
13084
  lines(result) {
11170
13085
  return this.#listed.findLast((listing) => listing.result === result)?.lines;
11171
13086
  }
13087
+ commit(price, skill) {
13088
+ this.#commits = [...this.#commits, {
13089
+ price,
13090
+ turn: this.calls.length,
13091
+ ...skill === void 0 ? {} : { skill }
13092
+ }];
13093
+ }
13094
+ get committed() {
13095
+ return this.#inflight().reduce((sum, entry) => sum + entry.price, 0);
13096
+ }
13097
+ get loads() {
13098
+ return this.#inflight().flatMap((entry) => entry.skill === void 0 ? [] : [entry.skill]);
13099
+ }
13100
+ #inflight() {
13101
+ const turn = this.calls.length;
13102
+ return this.#commits.filter((entry) => entry.turn === turn);
13103
+ }
11172
13104
  };
11173
13105
  // src/core/ledgers/LedgerScan.ts
11174
13106
  /**
@@ -11446,16 +13378,20 @@ var Ledger = class {
11446
13378
  #share;
11447
13379
  #conversation;
11448
13380
  #agent;
13381
+ #record;
13382
+ #builtins;
13383
+ #given;
11449
13384
  #classifier;
11450
13385
  #requests = /* @__PURE__ */ new Set();
11451
13386
  #annotations = /* @__PURE__ */ new Set();
11452
13387
  #results = /* @__PURE__ */ new Map();
11453
13388
  #pending = /* @__PURE__ */ new Map();
11454
- #live = /* @__PURE__ */ new Set();
13389
+ #live = /* @__PURE__ */ new Map();
11455
13390
  #scans = /* @__PURE__ */ new Set();
11456
13391
  #calibrations = /* @__PURE__ */ new Set();
11457
13392
  #gauge;
11458
- #digest;
13393
+ #measured;
13394
+ #refusal;
11459
13395
  #window = [];
11460
13396
  #evidence = collectLedgerEvidence([], /* @__PURE__ */ new Set(), []);
11461
13397
  /**
@@ -11470,6 +13406,8 @@ var Ledger = class {
11470
13406
  * @throws {ConversationError} Thrown when `isLedgerSnapshot` admits `snapshot` but its conversation holds a `system` message (code `'ROLE'`) or repeats an id (code `'SNAPSHOT'`)
11471
13407
  */
11472
13408
  constructor(provider, options) {
13409
+ (0, _orkestrel_contract.refuseOptionKeys)(options, LEDGER_OPTION_KEYS, "Ledger", "LedgerOptions");
13410
+ (0, _orkestrel_contract.refuseOptionKeys)(options.agent, LEDGER_AGENT_KEYS, "Ledger agent", "LedgerAgentOptions");
11473
13411
  for (const key of [
11474
13412
  "category",
11475
13413
  "topic",
@@ -11547,6 +13485,9 @@ var Ledger = class {
11547
13485
  }) }),
11548
13486
  execute: (args) => this.#recall(args)
11549
13487
  }));
13488
+ this.#builtins = requireBuiltins(options.agent?.builtins, tools);
13489
+ 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");
13490
+ this.#given = requireWorkspace(options.agent?.workspace);
11550
13491
  const advertised = new Authority({
11551
13492
  rules: [{
11552
13493
  match: (context) => names.includes(context.call.name),
@@ -11565,17 +13506,14 @@ var Ledger = class {
11565
13506
  system: options.system,
11566
13507
  tools,
11567
13508
  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),
13509
+ evaluate: (context) => this.#evaluate(context, host, advertised, names),
11573
13510
  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
13511
  },
11575
13512
  limit: options.agent?.limit ?? 8,
11576
13513
  select: (conversation, request, signal) => this.#select(conversation, request, signal)
11577
13514
  });
11578
- const digest = digestLedgerTools(this.#agent.context.tools.definitions());
13515
+ const calibrated = this.#calibrated();
13516
+ const digest = digestLedgerTools(calibrated);
11579
13517
  const gauge = options.gauge ?? (snapshot?.gauge?.digest === digest ? snapshot?.gauge : void 0);
11580
13518
  if (gauge !== void 0) {
11581
13519
  this.#gauge = new Gauge({
@@ -11585,10 +13523,10 @@ var Ledger = class {
11585
13523
  predict: this.#predict,
11586
13524
  replay: this.#replay
11587
13525
  });
11588
- this.#digest = digest;
13526
+ this.#measured = calibrated;
11589
13527
  }
11590
13528
  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);
13529
+ 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
13530
  });
11593
13531
  this.#agent.emitter.on("tool", (call, result) => {
11594
13532
  this.#flush();
@@ -11601,21 +13539,34 @@ var Ledger = class {
11601
13539
  const request = this.#current();
11602
13540
  if (request?.calls.at(-1)?.tools === 0) request.abort();
11603
13541
  this.#release(call, result);
13542
+ this.#reopen(call, result);
11604
13543
  });
11605
13544
  this.#agent.emitter.on("select", (selection) => {
11606
13545
  if (isLedgerError(selection.fault) && selection.fault.code === "REQUEST") return;
11607
13546
  this.#current()?.adopt(selection, this.#conversation.messages().length);
11608
13547
  });
11609
13548
  this.#agent.emitter.on("fault", (error) => this.#current()?.fault(error));
11610
- this.#agent.emitter.on("turn", () => this.#observeTurn());
13549
+ this.#agent.emitter.on("request", (index, sent) => {
13550
+ if (this.#gate(index, sent)) this.#observeRequest(sent);
13551
+ });
11611
13552
  this.#agent.emitter.on("usage", (usage) => this.#current()?.spend(usage));
13553
+ const agent = this.#agent;
13554
+ this.#record = Object.freeze({
13555
+ emitter: agent.emitter,
13556
+ id: agent.id,
13557
+ get status() {
13558
+ return agent.status;
13559
+ },
13560
+ context: agent.context,
13561
+ abort: (reason) => agent.abort(reason)
13562
+ });
11612
13563
  }
11613
13564
  /**
11614
- * Returns the owned request engine.
11615
- * @returns The engine serving this conversation
13565
+ * Returns the record of the owned request engine, which starts no run.
13566
+ * @returns The frozen record of the agent serving this conversation
11616
13567
  */
11617
13568
  get agent() {
11618
- return this.#agent;
13569
+ return this.#record;
11619
13570
  }
11620
13571
  /**
11621
13572
  * Returns the owned message history.
@@ -11636,9 +13587,10 @@ var Ledger = class {
11636
13587
  }
11637
13588
  /**
11638
13589
  * 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
13590
+ * @param signal - The signal bounding every calibration call; an abort rejects with its reason
11640
13591
  * @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'`)
13592
+ * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
13593
+ * @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
13594
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
11643
13595
  */
11644
13596
  async calibrate(signal) {
@@ -11661,7 +13613,7 @@ var Ledger = class {
11661
13613
  const conversation = this.#conversation.snapshot();
11662
13614
  const ids = conversation.messages.map((message) => message.id);
11663
13615
  const gauge = this.gauge;
11664
- const priced = this.#digest;
13616
+ const priced = this.#digest();
11665
13617
  const window = {
11666
13618
  exchanges: this.#window.map((entry) => ({
11667
13619
  request: entry.request.id,
@@ -11695,13 +13647,15 @@ var Ledger = class {
11695
13647
  /**
11696
13648
  * Appends a request and serves it, recovering an unfinished or rejected first pass with one answer pass.
11697
13649
  * 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.
13650
+ * 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
13651
  * 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
13652
  * @param content - The request text
11701
13653
  * @param signal - The caller's cancellation signal; an abort during calibration rejects with its reason
11702
13654
  * @returns The final pass, the usage of every pass, and the filing attempts of this request alone
11703
13655
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
11704
13656
  * @throws {LedgerError} Thrown when a calibration call reports no prompt usage, or a prompt usage of 0 or less (code `'GAUGE'`)
13657
+ * @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'`)
13658
+ * @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
13659
  * @throws {ContractError} Thrown when `signal` is defined and is not a native `AbortSignal`
11706
13660
  */
11707
13661
  async respond(content, signal) {
@@ -11709,12 +13663,15 @@ var Ledger = class {
11709
13663
  const caller = (0, _orkestrel_abort.createAbort)({ ...signal === void 0 ? {} : { signal } }).signal;
11710
13664
  const request = new LedgerRequest(content, this.#options.recall?.limit);
11711
13665
  const scope = this.#agent.context.scope;
11712
- this.#live.add(request);
13666
+ this.#live.set(request, scope);
11713
13667
  try {
11714
- if (this.#gauge === void 0) await this.#measureGauge(caller);
13668
+ if (this.#gauge === void 0 || digestLedgerTools(this.#calibrated()) !== this.#digest()) {
13669
+ this.#gauge = void 0;
13670
+ await this.#measureGauge(caller);
13671
+ }
11715
13672
  const entered = request.enter(this.#conversation);
11716
13673
  this.#requests.add(entered.id);
11717
- const first = this.#dropStrayCalls(request, await this.#generatePass(request, caller, this.#options.think).catch((error) => {
13674
+ const first = this.#dropStrayCalls(request, await this.#generatePass(request, caller, this.#options.think, scope).catch((error) => {
11718
13675
  if (request.faulted(error)) throw error;
11719
13676
  return {
11720
13677
  content: "",
@@ -11733,13 +13690,12 @@ var Ledger = class {
11733
13690
  role: "user",
11734
13691
  content: LEDGER_NOTES.cue
11735
13692
  }).id);
11736
- const previous = this.#agent.context.scope;
13693
+ const filter = intersectFilters(scope, { tools: [] });
11737
13694
  const answer = createScope({
11738
- ...previous,
13695
+ ...filter,
11739
13696
  name: "answer",
11740
- tools: [],
11741
13697
  select: async (conversation, message, runSignal) => {
11742
- const selection = await this.#select(conversation, message, runSignal);
13698
+ const selection = await this.#select(conversation, message, runSignal, filter);
11743
13699
  if (selection.fault !== void 0) return selection;
11744
13700
  return {
11745
13701
  ...selection,
@@ -11747,12 +13703,9 @@ var Ledger = class {
11747
13703
  };
11748
13704
  }
11749
13705
  });
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
- }
13706
+ passes.push(this.#dropStrayCalls(request, await this.#generatePass(request, caller, false, answer)));
13707
+ const refusal = this.#refusal;
13708
+ if (refusal !== void 0 && !caller.aborted) throw refusal;
11756
13709
  }
11757
13710
  const last = passes.at(-1) ?? first;
11758
13711
  request.think(this.#conversation.messages());
@@ -11778,26 +13731,31 @@ var Ledger = class {
11778
13731
  }
11779
13732
  async #measureGauge(signal) {
11780
13733
  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();
13734
+ const messages = this.#agent.context.build({
13735
+ selection: {
13736
+ messages: [],
13737
+ judgments: []
13738
+ },
13739
+ scope: createScope({
13740
+ name: "calibration",
13741
+ workspaces: []
13742
+ })
13743
+ });
13744
+ const definitions = this.#calibrated();
11790
13745
  const priced = await this.#provider.generate(messages, signal, definitions, { think: false });
13746
+ this.#refuseOverCapacity("with", priced.usage?.prompt);
11791
13747
  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");
13748
+ this.#refuseOverCapacity("without", bare.usage?.prompt);
13749
+ 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");
13750
+ const scale = await this.#measureScale(messages, bare.usage.prompt, signal);
11793
13751
  this.#gauge = new Gauge({
11794
13752
  capacity: this.#options.capacity,
11795
13753
  predict: this.#predict,
11796
13754
  replay: this.#replay,
11797
- scale: bare.usage.prompt / estimateMessages(messages),
13755
+ scale,
11798
13756
  overhead: Math.max(0, priced.usage.prompt - bare.usage.prompt)
11799
13757
  });
11800
- this.#digest = digestLedgerTools(definitions);
13758
+ this.#measured = definitions;
11801
13759
  return {
11802
13760
  scale: this.#gauge.scale,
11803
13761
  overhead: this.#gauge.overhead
@@ -11807,13 +13765,52 @@ var Ledger = class {
11807
13765
  throw error;
11808
13766
  }
11809
13767
  }
11810
- async #generatePass(request, signal, think) {
13768
+ async #measureScale(head, bare, signal) {
13769
+ const rate = bare / estimateMessages(head);
13770
+ const workspace = this.#show(intersectFilters(this.#agent.context.scope, void 0)).workspace;
13771
+ const files = (resolveWorkspace(this.#agent.context.workspaces, workspace, void 0)?.files() ?? []).flatMap((file) => (0, _orkestrel_workspace.isTextContent)(file.content) ? [file.path] : []);
13772
+ const shown = this.#agent.context.build({
13773
+ selection: {
13774
+ messages: [],
13775
+ judgments: []
13776
+ },
13777
+ scope: createScope({
13778
+ name: "calibration",
13779
+ files
13780
+ }),
13781
+ disclosure: {
13782
+ skills: [],
13783
+ ...workspace === void 0 ? {} : { workspace }
13784
+ }
13785
+ });
13786
+ const whole = estimateMessages(shown) - estimateMessages(head);
13787
+ const cut = cutWorkspaceSample(head, shown, Math.floor((this.#options.capacity - bare) / 3));
13788
+ const part = cut === void 0 ? 0 : estimateMessages(cut) - estimateMessages(head);
13789
+ if (cut === void 0 || whole <= 0 || part <= 0) return rate;
13790
+ const third = await this.#provider.generate(cut, signal, void 0, { think: false });
13791
+ this.#refuseOverCapacity("workspace", third.usage?.prompt);
13792
+ if (!(0, _orkestrel_contract.isFiniteNumber)(third.usage?.prompt) || third.usage.prompt <= 0) throw new LedgerError("GAUGE", "calibration requires prompt usage from each call");
13793
+ const tokens = bare + (third.usage.prompt - bare) * whole / part;
13794
+ return Math.max(rate, tokens / estimateMessages(shown));
13795
+ }
13796
+ #refuseOverCapacity(call, prompt) {
13797
+ const capacity = this.#options.capacity;
13798
+ const subject = call === "workspace" ? "with the workspace text" : `${call} the tool definitions`;
13799
+ 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";
13800
+ 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: {
13801
+ capacity,
13802
+ prompt
13803
+ } });
13804
+ }
13805
+ async #generatePass(request, signal, think, scope) {
13806
+ this.#refusal = void 0;
11811
13807
  request.open(this.#conversation.messages().length);
11812
13808
  const stop = request.signal;
11813
13809
  return await this.#agent.generate({
11814
- signal: stop === void 0 ? signal : AbortSignal.any([signal, stop]),
13810
+ signal: stop === void 0 ? signal : (0, _orkestrel_abort.linkSignal)(signal, stop),
11815
13811
  conversation: this.#conversation,
11816
- ...think === void 0 ? {} : { think }
13812
+ ...think === void 0 ? {} : { think },
13813
+ ...scope === void 0 ? {} : { scope }
11817
13814
  });
11818
13815
  }
11819
13816
  #dropStrayCalls(request, pass) {
@@ -11839,12 +13836,103 @@ var Ledger = class {
11839
13836
  };
11840
13837
  }
11841
13838
  #current() {
11842
- const [request] = this.#live;
13839
+ const [request] = this.#live.keys();
11843
13840
  return request?.message === void 0 ? void 0 : request;
11844
13841
  }
11845
13842
  #busy() {
11846
13843
  return this.#live.size > 0 || this.#calibrations.size > 0;
11847
13844
  }
13845
+ #evaluate(context, host, advertised, names) {
13846
+ const request = this.#current();
13847
+ const { name, arguments: args } = context.call;
13848
+ if (request?.denied(name, args) === true) return {
13849
+ zone: "ledger",
13850
+ verdict: "deny",
13851
+ reason: LEDGER_NOTES.repeat
13852
+ };
13853
+ if (this.#enables(name)) {
13854
+ const admitted = (0, _orkestrel_contract.attempt)(() => request?.admit(name, args));
13855
+ if (!admitted.success) {
13856
+ if ((0, _orkestrel_contract.isError)(admitted.error) && admitted.error.message === LEDGER_NOTES.repeat) return {
13857
+ zone: "ledger",
13858
+ verdict: "deny",
13859
+ reason: LEDGER_NOTES.repeat
13860
+ };
13861
+ throw admitted.error;
13862
+ }
13863
+ if (request !== void 0) {
13864
+ const commitment = this.#room(request, name, args);
13865
+ if (commitment === void 0) return {
13866
+ zone: "ledger",
13867
+ verdict: "deny",
13868
+ reason: LEDGER_NOTES.overflow
13869
+ };
13870
+ request.commit(commitment.price, commitment.skill);
13871
+ }
13872
+ return host === void 0 ? advertised.evaluate(context) : host.evaluate(context);
13873
+ }
13874
+ const decision = host === void 0 || name === "recall" || !names.includes(name) ? advertised.evaluate(context) : host.evaluate(context);
13875
+ if (request !== void 0 && decision.verdict !== "deny" && name !== "recall" && names.includes(name)) request.commit(this.#priceResult(request.calls));
13876
+ return decision;
13877
+ }
13878
+ #room(request, name, args) {
13879
+ if (name !== "load" && name !== "remember" && name !== "switch") return { price: 0 };
13880
+ const gauge = this.#gauge;
13881
+ if (gauge === void 0) return void 0;
13882
+ const room = gauge.remainder(request.calls) - this.#predict;
13883
+ const admitted = (0, _orkestrel_contract.admitValue)(builtinToolContracts[name].contract, { ...args });
13884
+ const values = admitted.success ? admitted.value.value : {};
13885
+ if (name === "remember") {
13886
+ const line = (0, _orkestrel_contract.isString)(values["name"]) && (0, _orkestrel_contract.isString)(values["description"]) ? renderIndexEntry({
13887
+ name: values["name"],
13888
+ description: values["description"]
13889
+ }) : "";
13890
+ const price = gauge.scale * estimateTokens(line);
13891
+ return (request.committed + price) * 1.06 > room ? void 0 : { price };
13892
+ }
13893
+ const ceiling = this.#ceiling(request);
13894
+ const shown = this.#show(ceiling);
13895
+ if (name === "switch") {
13896
+ const workspace = values["workspace"];
13897
+ const price = (0, _orkestrel_contract.isString)(workspace) ? gauge.scale * Math.max(0, this.#estimateHead(request, ceiling, {
13898
+ skills: shown.skills,
13899
+ workspace
13900
+ }) - this.#estimateHead(request, ceiling, shown)) : 0;
13901
+ return (request.committed + price) * 1.06 > room ? void 0 : { price };
13902
+ }
13903
+ const thread = [.../* @__PURE__ */ new Set([...shown.skills, ...request.loads])];
13904
+ const skill = filterAllowList(ceiling.skills, this.#agent.context.skills.skills(), (one) => one.name).find((one) => one.name === values["skill"]);
13905
+ if (skill === void 0 || thread.includes(skill.name)) return { price: 0 };
13906
+ const price = this.#priceSkill(skill, ceiling, this.#advertise(ceiling, thread));
13907
+ const budget = (this.#options.capacity - this.#predict) * this.#share.prompt;
13908
+ return (request.committed + price + this.#priceResult(request.calls)) * 1.06 > room || this.#priceShown({ skills: thread }, ceiling) + price > this.#share.skills * budget ? void 0 : {
13909
+ price,
13910
+ skill: skill.name
13911
+ };
13912
+ }
13913
+ #estimateHead(request, ceiling, shown) {
13914
+ return estimateMessages(stripThinking(this.#agent.context.build({
13915
+ ...request.selection === void 0 ? {} : { selection: request.selection },
13916
+ disclosure: shown,
13917
+ switchable: this.#advertise(ceiling, shown.skills).some((tool) => tool.name === "switch")
13918
+ }), this.#replay));
13919
+ }
13920
+ #reopen(call, result) {
13921
+ const request = this.#current();
13922
+ const entered = request?.message;
13923
+ if (request === void 0 || entered === void 0 || !result.success || !this.#enables(call.name)) return;
13924
+ const messages = this.#conversation.messages();
13925
+ const pairs = collectToolGroups(messages.slice(messages.findIndex((message) => message.id === entered.id) + 1)).flatMap((group) => group.slice(1).flatMap((message) => {
13926
+ const owner = findToolCall(group, message);
13927
+ return owner === void 0 ? [] : [{
13928
+ call: owner,
13929
+ content: message.content
13930
+ }];
13931
+ }));
13932
+ const key = (0, _orkestrel_contract.canonicalStringify)(call.arguments);
13933
+ 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);
13934
+ for (const { call: earlier } of reopened) request.release(earlier.name, earlier.arguments);
13935
+ }
11848
13936
  #flush() {
11849
13937
  if (this.#pending.size === 0) return;
11850
13938
  const messages = this.#conversation.messages();
@@ -11895,7 +13983,7 @@ var Ledger = class {
11895
13983
  for (const message of messages) {
11896
13984
  if (!snapshot.failures.includes(message.id)) continue;
11897
13985
  const group = groups.find((entries) => entries.some((one) => one.id === message.id));
11898
- const call = group === void 0 ? void 0 : findLedgerCall(group, message);
13986
+ const call = group === void 0 ? void 0 : findToolCall(group, message);
11899
13987
  this.#results.set(message.id, {
11900
13988
  success: false,
11901
13989
  id: call?.id ?? message.call ?? message.id,
@@ -11925,7 +14013,7 @@ var Ledger = class {
11925
14013
  #readLookups(messages) {
11926
14014
  this.#flush();
11927
14015
  return collectToolGroups(messages).flatMap((group) => group.slice(1).flatMap((message) => {
11928
- const call = findLedgerCall(group, message);
14016
+ const call = findToolCall(group, message);
11929
14017
  const lookup = this.#options.lookups?.find((one) => one.tool.name === call?.name);
11930
14018
  const write = this.#options.writes?.find((one) => one.tool.name === call?.name);
11931
14019
  if (call === void 0 || lookup === void 0 && write === void 0 || this.#results.get(message.id)?.success === false) return [];
@@ -11992,7 +14080,7 @@ var Ledger = class {
11992
14080
  const start = messages.findIndex((message) => message.id === request.id);
11993
14081
  return messages.slice(start + 1).filter((message) => message.role !== "assistant" || hasCalls(message) || message.content.trim() !== "");
11994
14082
  }
11995
- async #select(conversation, message, signal) {
14083
+ async #select(conversation, message, signal, filter) {
11996
14084
  const request = this.#current();
11997
14085
  const selected = request?.message;
11998
14086
  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 +14092,7 @@ var Ledger = class {
12004
14092
  const scanned = (0, _orkestrel_contract.attempt)(() => this.#scan(this.#conversation.messages()));
12005
14093
  if (scanned.success) this.#hold(scanned.value);
12006
14094
  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));
14095
+ 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
14096
  } finally {
12009
14097
  const [scan] = this.#scans;
12010
14098
  if (scan !== void 0) this.#scans.delete(scan);
@@ -12026,11 +14114,11 @@ var Ledger = class {
12026
14114
  this.#hold(scan);
12027
14115
  return scan;
12028
14116
  }
12029
- #file(classified, request, selected, message) {
14117
+ #file(classified, request, selected, message, filter) {
12030
14118
  const { attempts, failure, ...filed } = classified;
12031
14119
  const planned = (0, _orkestrel_contract.attempt)(() => {
12032
14120
  if (filed.fault !== void 0) throw filed.fault;
12033
- const { draft, ...plan } = this.#plan(selected, this.#rescan());
14121
+ const { draft, ...plan } = this.#plan(selected, this.#rescan(), filter);
12034
14122
  return {
12035
14123
  draft,
12036
14124
  selection: {
@@ -12063,7 +14151,7 @@ var Ledger = class {
12063
14151
  const category = this.#classifier.category(source);
12064
14152
  return !(role === "user" && !this.#classifier.decisive(source)) && (category === "rule" || category === "correction");
12065
14153
  }
12066
- #plan(request, scan) {
14154
+ #plan(request, scan, filter) {
12067
14155
  const { input, projection } = this.#project(scan);
12068
14156
  const registry = scan.registry;
12069
14157
  const links = linkOwners(input.findings, registry.owners);
@@ -12075,10 +14163,20 @@ var Ledger = class {
12075
14163
  }),
12076
14164
  topics: [...near]
12077
14165
  });
12078
- const total = Math.max(0, (this.#options.capacity - this.#predict) * this.#share.prompt - (this.#gauge?.overhead ?? 0)) / (1 + LEDGER_SCALE_DRIFT);
14166
+ const lists = intersectFilters(void 0, filter);
14167
+ const bound = lists.tools;
14168
+ const disclosure = this.#show(lists);
14169
+ const advertised = this.#advertise(lists, disclosure.skills);
14170
+ const calibrated = this.#measuredSet();
14171
+ const price = computeToolPrice(calibrated, advertised, this.#priced());
14172
+ 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
14173
  const built = this.#agent.context.build({
12080
- messages: [request],
12081
- judgments: []
14174
+ selection: {
14175
+ messages: [request],
14176
+ judgments: []
14177
+ },
14178
+ disclosure,
14179
+ switchable: advertised.some((tool) => tool.name === "switch")
12082
14180
  });
12083
14181
  const tail = this.#selectTail(request, total, input, scan);
12084
14182
  const tailIds = new Set(tail.map((message) => message.id));
@@ -12187,23 +14285,24 @@ var Ledger = class {
12187
14285
  ...rung.window.flatMap((entry) => [entry.request, entry.reply]),
12188
14286
  buildRequestMessage(built.at(-1) ?? request, rung.rendered.text)
12189
14287
  ], this.#replay)]);
12190
- const names = this.#agent.context.scope?.tools;
12191
- const definitions = this.#agent.context.tools.definitions();
12192
14288
  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
14289
  const longest = this.#findLongest();
12194
14290
  const closed = probes.map((messages, at) => {
12195
- const tools = definitions.filter((tool) => (names === void 0 || names.includes(tool.name)) && (tool.name !== "recall" || offered[at]));
14291
+ const tools = advertised.filter((tool) => tool.name !== "recall" || offered[at]);
12196
14292
  const calls = [{
12197
14293
  estimate: estimateMessages(messages),
12198
14294
  tools: tools.length,
12199
- spared: this.#spared(tools)
14295
+ ...computeToolPrice(calibrated, tools, this.#priced())
12200
14296
  }];
12201
14297
  return offered[at] === true && this.#gauge !== void 0 && this.#gauge.remainder(calls) - this.#predict < 2 * this.#gauge.reserve(calls, longest);
12202
14298
  });
12203
14299
  const prices = probes.map((messages) => this.#price(messages));
12204
14300
  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);
14301
+ const recall = offered[rungs.indexOf(admitted)] === true;
14302
+ const ceiling = intersectKeys(this.#agent.context.scope?.tools, bound);
14303
+ const admits = ceiling === void 0 || ceiling.includes("recall");
14304
+ if (!recall && admits) {
14305
+ const tools = this.#definitions().filter((tool) => tool.name !== "recall").map((tool) => tool.name);
12207
14306
  const narrowed = this.#agent.context.scope?.narrow({ tools }) ?? createScope({
12208
14307
  name: "ledger",
12209
14308
  tools
@@ -12226,10 +14325,11 @@ var Ledger = class {
12226
14325
  window: admitted.window
12227
14326
  };
12228
14327
  const shown = buildLedgerRenderContext(scan.messages, [...kept, ...admitted.rendered.lines], []).complete;
14328
+ const route = recall && admits;
12229
14329
  return {
12230
14330
  draft,
12231
14331
  messages: [
12232
- ...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown) : message),
14332
+ ...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown, route) : message),
12233
14333
  ...admitted.window.flatMap((entry) => [entry.request, entry.reply]),
12234
14334
  copy
12235
14335
  ]
@@ -12317,7 +14417,7 @@ var Ledger = class {
12317
14417
  return [{
12318
14418
  leader,
12319
14419
  kept: (leader.calls ?? []).flatMap((call) => {
12320
- const message = group.slice(1).find((result) => findLedgerCall(group, result) === call);
14420
+ const message = group.slice(1).find((result) => findToolCall(group, result) === call);
12321
14421
  const result = message === void 0 ? void 0 : this.#results.get(message.id);
12322
14422
  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
14423
  call,
@@ -12362,16 +14462,16 @@ var Ledger = class {
12362
14462
  amendments: evidence.amendments
12363
14463
  };
12364
14464
  }
12365
- #renderTailStub(message, scan, shown) {
14465
+ #renderTailStub(message, scan, shown, offered = true) {
12366
14466
  const group = scan.group(message.id);
12367
- const call = group === void 0 ? void 0 : findLedgerCall(group, message);
14467
+ const call = group === void 0 ? void 0 : findToolCall(group, message);
12368
14468
  const write = this.#options.writes?.some((one) => one.tool.name === call?.name) === true;
12369
14469
  const finding = scan.finding(message.id);
12370
14470
  const hidden = renderStub(call?.name ?? "tool", call?.arguments ?? {}, "hidden");
12371
14471
  const visible = renderStub(call?.name ?? "tool", call?.arguments ?? {}, "shown");
12372
14472
  return {
12373
14473
  ...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)
14474
+ 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
14475
  };
12376
14476
  }
12377
14477
  #release(call, result) {
@@ -12402,26 +14502,71 @@ var Ledger = class {
12402
14502
  }]);
12403
14503
  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
14504
  }
12405
- #observeTurn() {
14505
+ #observeRequest(sent) {
12406
14506
  this.#flush();
12407
14507
  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));
14508
+ if (request?.boundary === void 0) return;
14509
+ const tools = sent.tools ?? [];
12414
14510
  request.observe({
12415
- estimate: estimateMessages(stripThinking([...messages, ...additions], this.#replay)),
14511
+ estimate: estimateMessages(sent.messages),
12416
14512
  tools: tools.length,
12417
- spared: this.#spared(tools)
14513
+ ...computeToolPrice(this.#measuredSet(), tools, this.#priced())
12418
14514
  }, this.#conversation.messages().length);
12419
14515
  }
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));
14516
+ #priced() {
14517
+ return this.#gauge ?? {
14518
+ scale: 1,
14519
+ overhead: 0
14520
+ };
14521
+ }
14522
+ #measuredSet() {
14523
+ return this.#measured ?? this.#calibrated();
14524
+ }
14525
+ #digest() {
14526
+ return this.#measured === void 0 ? void 0 : digestLedgerTools(this.#measured);
14527
+ }
14528
+ #enables(name) {
14529
+ return this.#builtins.some((one) => one === name);
14530
+ }
14531
+ #definitions() {
14532
+ return [...this.#agent.context.tools.definitions().filter((tool) => !this.#enables(tool.name)), ...collectBuiltinDefinitions({ builtins: this.#builtins })];
14533
+ }
14534
+ #calibrated() {
14535
+ const definitions = this.#definitions();
14536
+ if (!this.#enables("load")) return definitions;
14537
+ const held = new Set(this.#agent.context.skills.skills().flatMap((skill) => skill.tools));
14538
+ return definitions.filter((tool) => !held.has(tool.name));
14539
+ }
14540
+ #show(ceiling) {
14541
+ return resolveDisclosure(this.#agent.context, this.#conversation, this.#given, ceiling, this.#builtins);
14542
+ }
14543
+ #advertise(ceiling, shown) {
14544
+ return filterAdvertisedTools(filterBuiltinCandidates(this.#definitions(), ceiling), ceiling.tools, this.#enables("load") ? this.#agent.context.skills.skills() : [], shown);
14545
+ }
14546
+ #ceiling(request) {
14547
+ return intersectFilters(this.#agent.context.scope, this.#live.get(request));
14548
+ }
14549
+ #priceSkill(skill, ceiling, advertised) {
14550
+ const sent = new Set(advertised.map((tool) => tool.name));
14551
+ 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());
14552
+ }
14553
+ #priceShown(shown, ceiling) {
14554
+ const skills = shown.skills.flatMap((name) => this.#agent.context.skills.skill(name) ?? []);
14555
+ const named = new Set(skills.flatMap((skill) => skill.tools));
14556
+ const definitions = filterAllowList(ceiling.tools, this.#definitions().filter((tool) => named.has(tool.name)), (tool) => tool.name);
14557
+ const calibrated = this.#measuredSet();
14558
+ const gauge = this.#priced();
14559
+ return skills.reduce((sum, skill) => sum + computeSkillPrice(skill, [], calibrated, gauge), 0) + computeToolPrice(calibrated, [...calibrated, ...definitions], gauge).added;
14560
+ }
14561
+ #priceResult(calls) {
14562
+ 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);
14563
+ const gauge = this.#gauge;
14564
+ return Math.max(this.#priced().scale * (longest === void 0 ? 0 : estimateMessages([longest])), gauge === void 0 ? 0 : gauge.reserve(calls, this.#findLongest()));
14565
+ }
14566
+ #holdback(ceiling, shown, advertised, calls) {
14567
+ if (!advertised.some((tool) => tool.name === "load")) return 0;
14568
+ const loadable = filterAllowList(ceiling.skills, this.#agent.context.skills.skills(), (skill) => skill.name).filter((skill) => !shown.skills.includes(skill.name));
14569
+ return loadable.length === 0 ? 0 : Math.max(...loadable.map((skill) => this.#priceSkill(skill, ceiling, advertised))) + this.#priceResult(calls);
12425
14570
  }
12426
14571
  #findLongest() {
12427
14572
  return this.#conversation.messages().filter((message) => message.role === "assistant").reduce((longest, message) => message.content.length > longest.length ? message.content : longest, "");
@@ -12449,12 +14594,7 @@ var Ledger = class {
12449
14594
  request?.recall();
12450
14595
  const guidance = `an owner name, an id, or one of ${renderTopicNames(this.#options.topics)}`;
12451
14596
  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)));
14597
+ const { rate, extra, slack: drift } = this.#carry(request, calls);
12458
14598
  const siblings = estimateMessages((request?.listings ?? []).filter((listing) => listing.turn === calls.length).map((listing) => ({
12459
14599
  id: "result",
12460
14600
  role: "tool",
@@ -12529,7 +14669,7 @@ var Ledger = class {
12529
14669
  this.#flush();
12530
14670
  const groups = [...new Set(this.#collectAfter(entered).flatMap((message) => {
12531
14671
  const result = this.#results.get(message.id);
12532
- if (message.role !== "tool" || result?.success !== true) return [];
14672
+ if (message.role !== "tool" || result?.success !== true || result.name !== "memo" && this.#enables(result.name)) return [];
12533
14673
  const recalled = result.name === "recall";
12534
14674
  if (recalled && /^nothing earlier on /.test(message.content)) return [];
12535
14675
  const listing = recalled ? request.listings.findLast((one) => one.result === message.content) : void 0;
@@ -12538,7 +14678,7 @@ var Ledger = class {
12538
14678
  const unique = groups.filter((group, at) => !splitResultLines(group).every((line) => groups.slice(0, at).some((earlier) => splitResultLines(earlier).includes(line))));
12539
14679
  const digest = unique.length === 0 ? void 0 : `${LEDGER_NOTES.results}\n${unique.join("\n")}`;
12540
14680
  const omitted = /* @__PURE__ */ new Set();
12541
- const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
14681
+ const rate = this.#capacityRate(request) * (1 + LEDGER_SCALE_DRIFT);
12542
14682
  const allowance = (this.#options.capacity - this.#predict) / rate;
12543
14683
  const after = this.#collectAfter(entered);
12544
14684
  const messages = [
@@ -12555,28 +14695,105 @@ var Ledger = class {
12555
14695
  role: "user",
12556
14696
  content: digest
12557
14697
  }];
14698
+ const shown = this.#show(this.#ceiling(request));
12558
14699
  if (this.#gauge === void 0 || estimateMessages(stripThinking(this.#agent.context.build({
12559
- messages: [...messages, ...note],
12560
- judgments: []
14700
+ selection: {
14701
+ messages: [...messages, ...note],
14702
+ judgments: []
14703
+ },
14704
+ disclosure: shown
12561
14705
  }), this.#replay)) <= allowance) return {
12562
14706
  digest,
12563
14707
  omitted
12564
14708
  };
12565
14709
  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);
14710
+ const base = estimateMessages(stripThinking(this.#agent.context.build({
14711
+ selection: {
14712
+ messages: messages.filter((message) => !partials.has(message.id)),
14713
+ judgments: []
14714
+ },
14715
+ disclosure: shown
14716
+ }), this.#replay));
14717
+ 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: {
14718
+ capacity: this.#options.capacity,
14719
+ predict: this.#predict,
14720
+ price: base * rate
14721
+ } });
14722
+ const fitted = cutListing(unique, allowance - base - estimateTokens(`${LEDGER_NOTES.results}\n`), "more", false);
14723
+ const cut = fitted === "" ? void 0 : `${LEDGER_NOTES.results}\n${fitted}`;
12570
14724
  return {
12571
- digest: fitted === "" ? void 0 : `${LEDGER_NOTES.results}\n${fitted}`,
14725
+ digest: cut !== void 0 && base + estimateMessages([{
14726
+ id: "results",
14727
+ role: "user",
14728
+ content: cut
14729
+ }]) > allowance ? void 0 : cut,
12572
14730
  omitted: partials
12573
14731
  };
12574
14732
  }
14733
+ #carry(request, calls) {
14734
+ const rate = this.#recallRate(request) * (1 + LEDGER_SCALE_DRIFT);
14735
+ const ceiling = request === void 0 ? {} : this.#ceiling(request);
14736
+ const shown = this.#show(ceiling);
14737
+ const carried = request === void 0 ? 0 : estimateMessages(stripThinking([...this.#agent.context.build({
14738
+ ...request.selection === void 0 ? {} : { selection: request.selection },
14739
+ disclosure: shown,
14740
+ switchable: this.#advertise(ceiling, shown.skills).some((tool) => tool.name === "switch")
14741
+ }), ...this.#conversation.messages().slice(request.boundary)], this.#replay));
14742
+ return {
14743
+ rate,
14744
+ ...this.#terms(calls.at(-1), carried, rate)
14745
+ };
14746
+ }
14747
+ #terms(call, carried, rate) {
14748
+ const reply = (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : 0;
14749
+ return {
14750
+ extra: Math.max(0, (carried - (call?.estimate ?? carried)) * rate - reply),
14751
+ slack: (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? 0 : Math.max(0, (call?.estimate ?? 0) * (rate - (this.#gauge?.scale ?? rate)))
14752
+ };
14753
+ }
14754
+ #gate(index, sent) {
14755
+ const request = this.#current();
14756
+ if (request?.boundary === void 0) return true;
14757
+ const left = (0, _orkestrel_contract.attempt)(() => index === 0 ? this.#first(request, sent) : this.#next(request, sent));
14758
+ if (left.success && left.value >= 0) return true;
14759
+ const limit = this.#options.capacity - this.#predict;
14760
+ 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: {
14761
+ capacity: this.#options.capacity,
14762
+ predict: this.#predict,
14763
+ price: left.success ? limit - left.value : Number.POSITIVE_INFINITY
14764
+ } });
14765
+ this.#agent.abort("overflow");
14766
+ return false;
14767
+ }
14768
+ #first(request, sent) {
14769
+ const gauge = this.#gauge;
14770
+ if (gauge === void 0) throw new LedgerError("GAUGE", "the gate needs a gauge");
14771
+ const tools = computeToolPrice(this.#measuredSet(), sent.tools ?? [], gauge);
14772
+ const rate = request.calls.length === 0 ? gauge.scale : this.#capacityRate(request);
14773
+ return this.#options.capacity - this.#predict - (Math.max(0, gauge.overhead - tools.spared) + tools.added + rate * (1 + LEDGER_SCALE_DRIFT) * estimateMessages(sent.messages));
14774
+ }
14775
+ #next(request, sent) {
14776
+ request.think(this.#conversation.messages());
14777
+ const gauge = this.#gauge;
14778
+ if (gauge === void 0) throw new LedgerError("GAUGE", "the gate needs a gauge");
14779
+ const calls = request.calls;
14780
+ const last = calls.at(-1);
14781
+ if (last === void 0) throw new Error("a later request has no recorded call before it");
14782
+ const measured = this.#measuredSet();
14783
+ const rate = this.#capacityRate(request) * (1 + LEDGER_SCALE_DRIFT);
14784
+ const { extra, slack } = this.#terms(last, estimateMessages(sent.messages), rate);
14785
+ const tools = computeToolPrice(measured, sent.tools ?? [], gauge);
14786
+ 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);
14787
+ return gauge.remainder(calls) - this.#predict - extra - slack - grown;
14788
+ }
14789
+ #capacityRate(request) {
14790
+ return Math.max(this.#recallRate(request), this.#gauge?.scale ?? 1);
14791
+ }
12575
14792
  #recallRate(request) {
12576
14793
  const gauge = this.#gauge;
12577
14794
  if (gauge === void 0) return 1;
12578
14795
  const calls = request?.calls ?? [];
12579
- const last = calls.findLast((call) => (0, _orkestrel_contract.isFiniteNumber)(call.prompt));
14796
+ const last = calls.findLast((call) => (0, _orkestrel_contract.isFiniteNumber)(call.prompt) && (call.added ?? 0) === 0);
12580
14797
  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
14798
  return Math.max(gauge.rate(calls), measured);
12582
14799
  }
@@ -12597,7 +14814,12 @@ var Ledger = class {
12597
14814
  function createLedger(provider, options) {
12598
14815
  return new Ledger(provider, options);
12599
14816
  }
14817
+ exports.AGENT_OPTION_KEYS = AGENT_OPTION_KEYS;
14818
+ exports.AGENT_RUN_KEYS = AGENT_RUN_KEYS;
14819
+ exports.ARGUMENT_BLANK_PATTERN = ARGUMENT_BLANK_PATTERN;
14820
+ exports.ARGUMENT_CONTROL_PATTERN = ARGUMENT_CONTROL_PATTERN;
12600
14821
  exports.AUTHORITY_NOTES = AUTHORITY_NOTES;
14822
+ exports.AUTHORITY_OPTION_KEYS = AUTHORITY_OPTION_KEYS;
12601
14823
  exports.Agent = Agent;
12602
14824
  exports.AgentContext = AgentContext;
12603
14825
  exports.AgentError = AgentError;
@@ -12608,6 +14830,16 @@ exports.AgentProvider = AgentProvider;
12608
14830
  exports.AgentRegistry = AgentRegistry;
12609
14831
  exports.Approver = Approver;
12610
14832
  exports.Authority = Authority;
14833
+ exports.BUILD_OPTION_KEYS = BUILD_OPTION_KEYS;
14834
+ exports.BUILTIN_LIMITS = BUILTIN_LIMITS;
14835
+ exports.BUILTIN_NOTES = BUILTIN_NOTES;
14836
+ exports.BUILTIN_TOOLS = BUILTIN_TOOLS;
14837
+ exports.CLASSIFIER_OPTION_KEYS = CLASSIFIER_OPTION_KEYS;
14838
+ exports.COMPACT_OPTION_KEYS = COMPACT_OPTION_KEYS;
14839
+ exports.CONTEXT_OPTION_KEYS = CONTEXT_OPTION_KEYS;
14840
+ exports.CONVERSATION_INPUT_KEYS = CONVERSATION_INPUT_KEYS;
14841
+ exports.CONVERSATION_MANAGER_KEYS = CONVERSATION_MANAGER_KEYS;
14842
+ exports.CONVERSATION_OPTION_KEYS = CONVERSATION_OPTION_KEYS;
12611
14843
  exports.CONVERSATION_RECAP_PREFIX = CONVERSATION_RECAP_PREFIX;
12612
14844
  exports.CONVERSATION_ROLES = CONVERSATION_ROLES;
12613
14845
  exports.Channel = Channel;
@@ -12627,27 +14859,36 @@ exports.DEFAULT_RELAY_LIMIT = DEFAULT_RELAY_LIMIT;
12627
14859
  exports.DETERMINISTIC_JUDGE_ERROR = DETERMINISTIC_JUDGE_ERROR;
12628
14860
  exports.DatabaseConversationStore = DatabaseConversationStore;
12629
14861
  exports.DatabaseMemoStore = DatabaseMemoStore;
14862
+ exports.GAUGE_OPTION_KEYS = GAUGE_OPTION_KEYS;
12630
14863
  exports.Gauge = Gauge;
12631
14864
  exports.IMAGE_TOKEN_ESTIMATE = IMAGE_TOKEN_ESTIMATE;
14865
+ exports.INSTRUCTION_MANAGER_KEYS = INSTRUCTION_MANAGER_KEYS;
12632
14866
  exports.INVALID_RELAY_STATUS = INVALID_RELAY_STATUS;
12633
14867
  exports.Instruction = Instruction;
12634
14868
  exports.InstructionManager = InstructionManager;
14869
+ exports.JOB_REFUSAL_PREFIX = JOB_REFUSAL_PREFIX;
14870
+ exports.JUDGE_INPUT_KEYS = JUDGE_INPUT_KEYS;
12635
14871
  exports.JudgeAbortError = JudgeAbortError;
12636
14872
  exports.JudgeError = JudgeError;
12637
14873
  exports.JudgmentManager = JudgmentManager;
14874
+ exports.LEDGER_AGENT_KEYS = LEDGER_AGENT_KEYS;
12638
14875
  exports.LEDGER_CATEGORIES = LEDGER_CATEGORIES;
12639
14876
  exports.LEDGER_NOTES = LEDGER_NOTES;
14877
+ exports.LEDGER_OPTION_KEYS = LEDGER_OPTION_KEYS;
12640
14878
  exports.LEDGER_OWNER_PREFIX = LEDGER_OWNER_PREFIX;
12641
14879
  exports.LEDGER_QUESTIONS = LEDGER_QUESTIONS;
12642
14880
  exports.LEDGER_RECALL_LABELS = LEDGER_RECALL_LABELS;
12643
14881
  exports.LEDGER_RULES_KEY = LEDGER_RULES_KEY;
14882
+ exports.LEDGER_SAMPLE_BYTES = LEDGER_SAMPLE_BYTES;
12644
14883
  exports.LEDGER_SCALE_DRIFT = LEDGER_SCALE_DRIFT;
12645
14884
  exports.LEDGER_SLICE = LEDGER_SLICE;
12646
14885
  exports.LEDGER_WORD = LEDGER_WORD;
14886
+ exports.LOADED_SECTION_HEADER = LOADED_SECTION_HEADER;
12647
14887
  exports.Ledger = Ledger;
12648
14888
  exports.LedgerError = LedgerError;
12649
14889
  exports.LedgerRequest = LedgerRequest;
12650
14890
  exports.MAX_ERROR_BODY_LENGTH = MAX_ERROR_BODY_LENGTH;
14891
+ exports.MEMO_MANAGER_KEYS = MEMO_MANAGER_KEYS;
12651
14892
  exports.MEMO_SECTION_HEADER = MEMO_SECTION_HEADER;
12652
14893
  exports.MESSAGE_ROLES = MESSAGE_ROLES;
12653
14894
  exports.MESSAGE_TOKEN_OVERHEAD = MESSAGE_TOKEN_OVERHEAD;
@@ -12656,17 +14897,31 @@ exports.MemoryConversationStore = MemoryConversationStore;
12656
14897
  exports.MemoryMemoStore = MemoryMemoStore;
12657
14898
  exports.OVERSIZED_RELAY_STATUS = OVERSIZED_RELAY_STATUS;
12658
14899
  exports.PLACED_CATEGORIES = PLACED_CATEGORIES;
14900
+ exports.PROVIDER_INPUT_KEYS = PROVIDER_INPUT_KEYS;
12659
14901
  exports.ProviderAbortError = ProviderAbortError;
12660
14902
  exports.ProviderError = ProviderError;
14903
+ exports.QUEUE_OPTION_KEYS = QUEUE_OPTION_KEYS;
12661
14904
  exports.QUIET_CATEGORIES = QUIET_CATEGORIES;
14905
+ exports.REFERENCE_OPTION_KEYS = REFERENCE_OPTION_KEYS;
12662
14906
  exports.REFUSED_RELAY_CONTENT_TYPE = REFUSED_RELAY_CONTENT_TYPE;
12663
14907
  exports.REFUSED_RELAY_STATUS = REFUSED_RELAY_STATUS;
14908
+ exports.REGISTRY_OPTION_KEYS = REGISTRY_OPTION_KEYS;
12664
14909
  exports.RELAY_CONTENT_TYPE = RELAY_CONTENT_TYPE;
14910
+ exports.RELAY_OPTION_KEYS = RELAY_OPTION_KEYS;
14911
+ exports.RELAY_PROVIDER_KEYS = RELAY_PROVIDER_KEYS;
12665
14912
  exports.RELAY_PROVIDER_MESSAGE = RELAY_PROVIDER_MESSAGE;
14913
+ exports.RELAY_STREAM_KEYS = RELAY_STREAM_KEYS;
14914
+ exports.RUNNER_OPTION_KEYS = RUNNER_OPTION_KEYS;
12666
14915
  exports.RelayProvider = RelayProvider;
12667
14916
  exports.RelayStream = RelayStream;
14917
+ exports.SCOPE_FILTER_KEYS = SCOPE_FILTER_KEYS;
14918
+ exports.SCOPE_KEYS = SCOPE_KEYS;
14919
+ exports.SCOPE_MANAGER_KEYS = SCOPE_MANAGER_KEYS;
12668
14920
  exports.SCOPE_NOTE = SCOPE_NOTE;
14921
+ exports.SKILL_MANAGER_KEYS = SKILL_MANAGER_KEYS;
12669
14922
  exports.SKILL_SECTION_HEADER = SKILL_SECTION_HEADER;
14923
+ exports.STREAM_OPTION_KEYS = STREAM_OPTION_KEYS;
14924
+ exports.SYSTEM_ONE_OPTION_KEYS = SYSTEM_ONE_OPTION_KEYS;
12670
14925
  exports.SYSTEM_ONE_PATH = SYSTEM_ONE_PATH;
12671
14926
  exports.Scope = Scope;
12672
14927
  exports.ScopeManager = ScopeManager;
@@ -12677,6 +14932,7 @@ exports.THINK_OPEN = THINK_OPEN;
12677
14932
  exports.ThinkSplitter = ThinkSplitter;
12678
14933
  exports.UNAUTHORIZED_RELAY_STATUS = UNAUTHORIZED_RELAY_STATUS;
12679
14934
  exports.UPSTREAM_RELAY_STATUS = UPSTREAM_RELAY_STATUS;
14935
+ exports.WORKSPACE_INDEX_HEADER = WORKSPACE_INDEX_HEADER;
12680
14936
  exports.WORKSPACE_SECTION_HEADER = WORKSPACE_SECTION_HEADER;
12681
14937
  exports.agentAnswerContract = agentAnswerContract;
12682
14938
  exports.agentAnswerShape = agentAnswerShape;
@@ -12709,10 +14965,14 @@ exports.buildRecords = buildRecords;
12709
14965
  exports.buildRelayRequest = buildRelayRequest;
12710
14966
  exports.buildRequestMessage = buildRequestMessage;
12711
14967
  exports.buildSummaryMessage = buildSummaryMessage;
14968
+ exports.builtinToolContracts = builtinToolContracts;
12712
14969
  exports.chargeUsage = chargeUsage;
12713
14970
  exports.collectAmendments = collectAmendments;
14971
+ exports.collectBuiltinDefinitions = collectBuiltinDefinitions;
14972
+ exports.collectDisclosure = collectDisclosure;
12714
14973
  exports.collectExchanges = collectExchanges;
12715
14974
  exports.collectImageData = collectImageData;
14975
+ exports.collectJobRefusals = collectJobRefusals;
12716
14976
  exports.collectLedgerEvidence = collectLedgerEvidence;
12717
14977
  exports.collectLedgerLines = collectLedgerLines;
12718
14978
  exports.collectLedgerReplies = collectLedgerReplies;
@@ -12725,7 +14985,10 @@ exports.collectRetiredFindings = collectRetiredFindings;
12725
14985
  exports.collectToolGroups = collectToolGroups;
12726
14986
  exports.collectWriteSupersessions = collectWriteSupersessions;
12727
14987
  exports.computeLedgerChecksum = computeLedgerChecksum;
14988
+ exports.computeSkillPrice = computeSkillPrice;
12728
14989
  exports.computeThinking = computeThinking;
14990
+ exports.computeToolDensity = computeToolDensity;
14991
+ exports.computeToolPrice = computeToolPrice;
12729
14992
  exports.conversationSnapshotContract = conversationSnapshotContract;
12730
14993
  exports.conversationSnapshotShape = conversationSnapshotShape;
12731
14994
  exports.copyJSON = copyJSON;
@@ -12736,6 +14999,7 @@ exports.createAgentRegistry = createAgentRegistry;
12736
14999
  exports.createAgentRunner = createAgentRunner;
12737
15000
  exports.createApprover = createApprover;
12738
15001
  exports.createAuthority = createAuthority;
15002
+ exports.createBuiltinTool = createBuiltinTool;
12739
15003
  exports.createChannel = createChannel;
12740
15004
  exports.createConversation = createConversation;
12741
15005
  exports.createConversationManager = createConversationManager;
@@ -12755,7 +15019,9 @@ exports.createSkillManager = createSkillManager;
12755
15019
  exports.createSystemOneJudge = createSystemOneJudge;
12756
15020
  exports.createThinkSplitter = createThinkSplitter;
12757
15021
  exports.cutListing = cutListing;
15022
+ exports.cutWorkspaceSample = cutWorkspaceSample;
12758
15023
  exports.denyCall = denyCall;
15024
+ exports.describeCandidates = describeCandidates;
12759
15025
  exports.describeQuestionRefusal = describeQuestionRefusal;
12760
15026
  exports.describeRelayRefusal = describeRelayRefusal;
12761
15027
  exports.describeSnapshotRefusal = describeSnapshotRefusal;
@@ -12769,9 +15035,11 @@ exports.extractQueueOptions = extractQueueOptions;
12769
15035
  exports.extractSystemOneAnswer = extractSystemOneAnswer;
12770
15036
  exports.extractSystemOneUsage = extractSystemOneUsage;
12771
15037
  exports.extractWords = extractWords;
15038
+ exports.filterAdvertisedTools = filterAdvertisedTools;
12772
15039
  exports.filterAllowList = filterAllowList;
12773
- exports.findLedgerCall = findLedgerCall;
15040
+ exports.filterBuiltinCandidates = filterBuiltinCandidates;
12774
15041
  exports.findLedgerCut = findLedgerCut;
15042
+ exports.findToolCall = findToolCall;
12775
15043
  exports.fitSlope = fitSlope;
12776
15044
  exports.freezeJSON = freezeJSON;
12777
15045
  exports.handleAgentQueueJob = handleAgentQueueJob;
@@ -12779,7 +15047,12 @@ exports.hasCalls = hasCalls;
12779
15047
  exports.hasHiddenRecall = hasHiddenRecall;
12780
15048
  exports.identifyLookup = identifyLookup;
12781
15049
  exports.inferStop = inferStop;
15050
+ exports.instructionInputContract = instructionInputContract;
15051
+ exports.instructionInputShape = instructionInputShape;
15052
+ exports.intersectFilters = intersectFilters;
12782
15053
  exports.intersectKeys = intersectKeys;
15054
+ exports.isAgentBuiltin = isAgentBuiltin;
15055
+ exports.isAgentCaller = isAgentCaller;
12783
15056
  exports.isAgentError = isAgentError;
12784
15057
  exports.isAgentJobError = isAgentJobError;
12785
15058
  exports.isAuthorityDecision = isAuthorityDecision;
@@ -12809,6 +15082,7 @@ exports.isPositiveSafeInteger = isPositiveSafeInteger;
12809
15082
  exports.isProviderAbortError = isProviderAbortError;
12810
15083
  exports.isProviderError = isProviderError;
12811
15084
  exports.isProviderResult = isProviderResult;
15085
+ exports.isScope = isScope;
12812
15086
  exports.isSection = isSection;
12813
15087
  exports.isSystemOneAnswer = isSystemOneAnswer;
12814
15088
  exports.isSystemOneResponse = isSystemOneResponse;
@@ -12825,18 +15099,26 @@ exports.ledgerSnapshotShape = ledgerSnapshotShape;
12825
15099
  exports.ledgerWindowContract = ledgerWindowContract;
12826
15100
  exports.ledgerWindowShape = ledgerWindowShape;
12827
15101
  exports.linkOwners = linkOwners;
15102
+ exports.loadToolContract = loadToolContract;
15103
+ exports.loadToolShape = loadToolShape;
12828
15104
  exports.matchEntities = matchEntities;
12829
15105
  exports.matchesCutLine = matchesCutLine;
15106
+ exports.matchesHeadLists = matchesHeadLists;
12830
15107
  exports.matchesJSON = matchesJSON;
12831
15108
  exports.matchesJudgment = matchesJudgment;
12832
15109
  exports.matchesLookup = matchesLookup;
15110
+ exports.matchesPlainLine = matchesPlainLine;
12833
15111
  exports.measureAnswer = measureAnswer;
12834
15112
  exports.measureMessage = measureMessage;
12835
15113
  exports.measureRequest = measureRequest;
12836
15114
  exports.memoContract = memoContract;
15115
+ exports.memoInputContract = memoInputContract;
15116
+ exports.memoInputShape = memoInputShape;
12837
15117
  exports.memoShape = memoShape;
12838
15118
  exports.memoSnapshotContract = memoSnapshotContract;
12839
15119
  exports.memoSnapshotShape = memoSnapshotShape;
15120
+ exports.memoToolContract = memoToolContract;
15121
+ exports.memoToolShape = memoToolShape;
12840
15122
  exports.mergeSupersessions = mergeSupersessions;
12841
15123
  exports.messageContract = messageContract;
12842
15124
  exports.messageInputContract = messageInputContract;
@@ -12860,23 +15142,28 @@ exports.readProviderDelta = readProviderDelta;
12860
15142
  exports.relayFrameContract = relayFrameContract;
12861
15143
  exports.relayFrameShape = relayFrameShape;
12862
15144
  exports.releaseReader = releaseReader;
15145
+ exports.rememberToolContract = rememberToolContract;
15146
+ exports.rememberToolShape = rememberToolShape;
12863
15147
  exports.removeEntries = removeEntries;
12864
15148
  exports.renderArguments = renderArguments;
12865
15149
  exports.renderCauseChain = renderCauseChain;
12866
15150
  exports.renderFencedFile = renderFencedFile;
12867
15151
  exports.renderIndexEntry = renderIndexEntry;
15152
+ exports.renderIndexLine = renderIndexLine;
12868
15153
  exports.renderJSON = renderJSON;
12869
15154
  exports.renderLedgerAnnotation = renderLedgerAnnotation;
12870
15155
  exports.renderLedgerPinned = renderLedgerPinned;
12871
15156
  exports.renderLedgerRecord = renderLedgerRecord;
12872
15157
  exports.renderLookupCall = renderLookupCall;
12873
15158
  exports.renderSection = renderSection;
15159
+ exports.renderSkillBody = renderSkillBody;
12874
15160
  exports.renderStub = renderStub;
12875
15161
  exports.renderToolValue = renderToolValue;
12876
15162
  exports.renderTopicNames = renderTopicNames;
12877
15163
  exports.requestToRelay = requestToRelay;
12878
15164
  exports.requireAnswer = requireAnswer;
12879
15165
  exports.requireBudget = requireBudget;
15166
+ exports.requireBuiltins = requireBuiltins;
12880
15167
  exports.requireConversation = requireConversation;
12881
15168
  exports.requireConversationSnapshot = requireConversationSnapshot;
12882
15169
  exports.requireCumulative = requireCumulative;
@@ -12888,15 +15175,21 @@ exports.requireKeep = requireKeep;
12888
15175
  exports.requireLimit = requireLimit;
12889
15176
  exports.requireMessageInput = requireMessageInput;
12890
15177
  exports.requireProviderResult = requireProviderResult;
15178
+ exports.requireRecordInput = requireRecordInput;
12891
15179
  exports.requireRole = requireRole;
12892
15180
  exports.requireRules = requireRules;
15181
+ exports.requireScope = requireScope;
12893
15182
  exports.requireSectionsCap = requireSectionsCap;
12894
15183
  exports.requireSummary = requireSummary;
15184
+ exports.requireWorkspace = requireWorkspace;
12895
15185
  exports.resolveDecision = resolveDecision;
15186
+ exports.resolveDisclosure = resolveDisclosure;
12896
15187
  exports.resolvePredict = resolvePredict;
15188
+ exports.resolveWorkspace = resolveWorkspace;
12897
15189
  exports.sanitizeToken = sanitizeToken;
12898
15190
  exports.sanitizeUsage = sanitizeUsage;
12899
15191
  exports.scanAmendments = scanAmendments;
15192
+ exports.scanJobTree = scanJobTree;
12900
15193
  exports.scanJudgmentTies = scanJudgmentTies;
12901
15194
  exports.scanProbability = scanProbability;
12902
15195
  exports.sectionContract = sectionContract;
@@ -12904,12 +15197,16 @@ exports.sectionShape = sectionShape;
12904
15197
  exports.selectRecords = selectRecords;
12905
15198
  exports.settleAgentJob = settleAgentJob;
12906
15199
  exports.settleToolResult = settleToolResult;
15200
+ exports.skillInputContract = skillInputContract;
15201
+ exports.skillInputShape = skillInputShape;
12907
15202
  exports.splitResultLines = splitResultLines;
12908
15203
  exports.splitSentences = splitSentences;
12909
15204
  exports.splitTopic = splitTopic;
12910
15205
  exports.splitWords = splitWords;
12911
15206
  exports.stripThinking = stripThinking;
12912
15207
  exports.sumUsage = sumUsage;
15208
+ exports.switchToolContract = switchToolContract;
15209
+ exports.switchToolShape = switchToolShape;
12913
15210
  exports.tokenUsageShape = tokenUsageShape;
12914
15211
  exports.tokenUsageToJSON = tokenUsageToJSON;
12915
15212
  exports.toolCallShape = toolCallShape;