@sema-agent/core 7.17.6 → 7.18.0
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.
- package/CHANGELOG.md +165 -13
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -0
- package/dist/agents/teacher.js +1 -0
- package/dist/agents/verify.js +3 -0
- package/dist/brain/route-adjudicator.d.ts +18 -0
- package/dist/brain/route-adjudicator.js +16 -0
- package/dist/core/agent-definition.d.ts +10 -0
- package/dist/core/artifact-host.d.ts +41 -4
- package/dist/core/artifact-host.js +18 -2
- package/dist/core/auto-mode.d.ts +141 -45
- package/dist/core/auto-mode.js +109 -3
- package/dist/core/checkpoint-store.d.ts +16 -2
- package/dist/core/engine-notice.d.ts +47 -1
- package/dist/core/engine-notice.js +7 -0
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +17 -2
- package/dist/core/gate-outcome.js +16 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +8 -0
- package/dist/core/hook-seats.d.ts +66 -0
- package/dist/core/hook-seats.js +37 -0
- package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
- package/dist/core/runner/assemble-result.d.ts +2 -1
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/classifier-ladder.d.ts +89 -0
- package/dist/core/runner/classifier-ladder.js +65 -0
- package/dist/core/runner/contracts.d.ts +69 -13
- package/dist/core/runner/decide-continuation.d.ts +1 -3
- package/dist/core/runner/decide-continuation.js +1 -7
- package/dist/core/runner/deferral-census.d.ts +52 -0
- package/dist/core/runner/deferral-census.js +44 -0
- package/dist/core/runner/derived-route-fallback.d.ts +8 -4
- package/dist/core/runner/derived-route-fallback.js +8 -3
- package/dist/core/runner/gate-exit.d.ts +12 -0
- package/dist/core/runner/gate-exit.js +6 -3
- package/dist/core/runner/prepare-artifact.d.ts +10 -2
- package/dist/core/runner/prepare-artifact.js +5 -4
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +30 -31
- package/dist/core/runner/prepare-config-doors.d.ts +1 -1
- package/dist/core/runner/prepare-config-doors.js +5 -3
- package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
- package/dist/core/runner/prepare-defer-classify.js +8 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
- package/dist/core/runner/prepare-hands-readface.js +60 -24
- package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
- package/dist/core/runner/prepare-inherited-gate.js +10 -2
- package/dist/core/runner/prepare-lsp.d.ts +2 -2
- package/dist/core/runner/prepare-lsp.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +2 -0
- package/dist/core/runner/prepare-project-context.js +4 -2
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
- package/dist/core/runner/prepare-prompt-inputs.js +4 -1
- package/dist/core/runner/prepare-run-refs.d.ts +9 -1
- package/dist/core/runner/prepare-run-refs.js +3 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
- package/dist/core/runner/prepare-suspend-saga.js +5 -2
- package/dist/core/runner/prepare-task.js +18 -13
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
- package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
- package/dist/core/runner/prepare-turn-wiring.js +3 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/resume-checkpoint-screen.js +7 -0
- package/dist/core/runner/run-harness-handlers.js +4 -1
- package/dist/core/runner/run-identity-wiring.js +6 -2
- package/dist/core/runner/run-leg.d.ts +2 -0
- package/dist/core/runner/run-leg.js +35 -41
- package/dist/core/runner/run-notification-lane.js +55 -27
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +16 -13
- package/dist/core/runner/run-turn-boundary.js +3 -1
- package/dist/core/runner/runtask.d.ts +6 -5
- package/dist/core/runner/runtask.js +26 -14
- package/dist/core/runner/skill-base-dir.d.ts +54 -0
- package/dist/core/runner/skill-base-dir.js +42 -0
- package/dist/core/runner/synthetic-tools.d.ts +16 -19
- package/dist/core/runner/synthetic-tools.js +18 -10
- package/dist/core/runner/tool-defer-gate.d.ts +22 -9
- package/dist/core/runner/tool-defer-gate.js +15 -5
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +2 -0
- package/dist/core/runner/tool-progress-lane.d.ts +42 -0
- package/dist/core/runner/tool-progress-lane.js +61 -0
- package/dist/core/runner-deps.d.ts +48 -7
- package/dist/core/swappable-deps.d.ts +9 -7
- package/dist/core/swappable-deps.js +7 -3
- package/dist/core/task-event.d.ts +96 -29
- package/dist/core/task-notification.d.ts +142 -16
- package/dist/core/task-notification.js +106 -0
- package/dist/core/task-result.d.ts +9 -1
- package/dist/core/task-spec.d.ts +20 -3
- package/dist/core/tighten-task-spec.js +7 -5
- package/dist/core/tool-model-gate.d.ts +1 -1
- package/dist/core/tool-search-setting.d.ts +78 -0
- package/dist/core/tool-search-setting.js +90 -0
- package/dist/core/tool-spec.d.ts +8 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +46 -0
- package/dist/core/wiring-manifest.js +9 -1
- package/dist/engine/execution-env/node-execution-env.js +10 -4
- package/dist/engine/harness/agent-harness.d.ts +42 -0
- package/dist/engine/harness/agent-harness.js +61 -29
- package/dist/engine/loop/agent-loop.d.ts +68 -3
- package/dist/engine/loop/agent-loop.js +109 -16
- package/dist/index.d.ts +10 -5
- package/dist/index.js +9 -5
- package/dist/internal/harness.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +6 -1
- package/dist/orchestration/workflow-governance.d.ts +7 -1
- package/dist/orchestration/workflow-governance.js +9 -5
- package/dist/orchestration/workflow-primitives.d.ts +3 -0
- package/dist/orchestration/workflow-primitives.js +6 -1
- package/dist/orchestration/workflow-types.d.ts +48 -13
- package/dist/orchestration/workflow-types.js +1 -1
- package/dist/orchestration/workflow.js +45 -17
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/section-usage.d.ts +26 -0
- package/dist/prompt-assembly/section-usage.js +10 -0
- package/dist/prompt-assembly/types.d.ts +8 -6
- package/dist/prompts/default.d.ts +13 -0
- package/dist/prompts/default.js +16 -8
- package/dist/tools/artifact/artifact-text.d.ts +10 -1
- package/dist/tools/artifact/artifact-text.js +6 -3
- package/dist/tools/artifact/artifact-tool.d.ts +12 -2
- package/dist/tools/artifact/artifact-tool.js +203 -70
- package/dist/tools/artifact/conformance.d.ts +56 -0
- package/dist/tools/artifact/conformance.js +349 -0
- package/dist/tools/artifact/local-stub.js +1 -1
- package/dist/tools/artifact/reject-breaker.d.ts +28 -0
- package/dist/tools/artifact/reject-breaker.js +42 -0
- package/dist/tools/fs/bash-lexer.d.ts +132 -0
- package/dist/tools/fs/bash-lexer.js +563 -0
- package/dist/tools/fs/bash-program-position.d.ts +142 -0
- package/dist/tools/fs/bash-program-position.js +125 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +54 -16
- package/dist/tools/fs/bash-readonly-classifier.js +375 -530
- package/dist/tools/fs/containment-face.d.ts +89 -0
- package/dist/tools/fs/containment-face.js +81 -0
- package/dist/tools/fs/fs-bash.d.ts +46 -1
- package/dist/tools/fs/fs-bash.js +158 -22
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +24 -5
- package/dist/tools/fs/index.js +18 -6
- package/dist/tools/fs/repo-map.d.ts +2 -2
- package/dist/tools/fs/safety.d.ts +3 -3
- package/dist/tools/fs/safety.js +4 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +85 -9
- package/wire-changes/core@7.17.6.json +81 -0
- package/wire-changes/core@7.17.7.json +49 -0
- package/dist/tools/fs/read-face.d.ts +0 -68
- package/dist/tools/fs/read-face.js +0 -47
|
@@ -85,9 +85,12 @@ export interface EngineNotice {
|
|
|
85
85
|
* The SEAT is announced, not a leg's resolved face: resolution (task seat, governance, the
|
|
86
86
|
* read-only mount) still runs per prepare. In-flight legs keep the face they prepared under;
|
|
87
87
|
* every later prepare resolves against the new declaration. A candidate outside the value gate
|
|
88
|
-
* (`
|
|
88
|
+
* (`assertContainmentFaceValue`) throws atomically and mints nothing. Own `occurrenceId` for the same
|
|
89
89
|
* reason as the catalog line (no run, no session, a two-word payload). Dedup key:
|
|
90
90
|
* (code, occurrenceId).
|
|
91
|
+
* - `"config.write_face_swapped"` — the same line for the WRITE seat (`RunnerDeps.writeFace`),
|
|
92
|
+
* same payload, same laws. A SEPARATE code rather than a face field on the line above: the audience
|
|
93
|
+
* table and every consumer table are keyed by code.
|
|
91
94
|
* - `"route.fallback_to_primary"` (key↔URL pairing, `src/brain/route-adjudicator.ts`) — a
|
|
92
95
|
* DERIVED-leg model (role/tier/system-default resolution, never a caller-explicit one) failed
|
|
93
96
|
* the pairing pre-flight and the seat fell back to the primary model instead of sinking the
|
|
@@ -100,6 +103,21 @@ export interface EngineNotice {
|
|
|
100
103
|
* session falling back on the same seat used to mint byte-identical lines; `runId` is what
|
|
101
104
|
* tells one task falling back twice apart from two tasks falling back once each.
|
|
102
105
|
* Explicitly-named models never mint this: they refuse at the brain's request gate instead.
|
|
106
|
+
* - `"route.classifier_unroutable"` (same pre-flight, the other POSTURE) — the derived classifier seat
|
|
107
|
+
* failed the same pairing pre-flight while the deployment had declared NO fallback
|
|
108
|
+
* (`RunnerDeps.autoMode.classifierFallback: "none"`), so the seat was KEPT and its calls are refused at
|
|
109
|
+
* the request gate rather than served by the primary model. The line is why "no swap" is still not "no
|
|
110
|
+
* word". `detail: { seat, from, cause, fixHint, action: "refused", sessionId?, runId? }` — the twin
|
|
111
|
+
* above's keys MINUS `to` (nothing moved, and a `to` naming a model that was not used is the one lie a
|
|
112
|
+
* consumer cannot detect); `action` is the discriminator, so a reader never infers the posture from that
|
|
113
|
+
* absence. Audience `operator`, same entitlement reading as its twin. Dedup key: (code, seat) per run.
|
|
114
|
+
* - `"classifier.fallback"` (the classifier's candidate ladder, `src/core/runner/classifier-ladder.ts`)
|
|
115
|
+
* — a gated call's classification did NOT rule on the classifier seat and was re-asked on the model
|
|
116
|
+
* behind it. Sibling of `route.fallback_to_primary` one layer later: that one fires when the derived
|
|
117
|
+
* model is not ROUTABLE at all, this one when it is routable and still does not answer.
|
|
118
|
+
* `detail: { from, to, cause, sessionId?, runId? }` — `cause` is the `AutoModeFallbackCause` word, the
|
|
119
|
+
* two ids are the rungs. Dedup key: (code, from, cause) PER RUN; a second CAUSE on the same rung is
|
|
120
|
+
* its own line.
|
|
103
121
|
* - `"route.base_url_changed_key_unchanged"` (key↔URL pairing) — `Runner.swapDeps` moved a
|
|
104
122
|
* same-name entry's `baseUrl` while its Model-visible credential half (auth-bearing headers)
|
|
105
123
|
* did not change: legal (a provider changing domains), but worth one loud line — if the
|
|
@@ -120,6 +138,9 @@ export interface EngineNotice {
|
|
|
120
138
|
* a library-direct `createHandsToolkit` mount announces at toolkit creation (one per mount,
|
|
121
139
|
* through the band-local `HandsToolkitOptions.onNotice` seat, absent ⇒ `console.warn`);
|
|
122
140
|
* `detail: { seat, declared, inForce, cause }`.
|
|
141
|
+
* - `"config.write_face_deployment_clamped"` — the write seat's twin of the line above, with the
|
|
142
|
+
* same stations, the same once-per-sink ledger (its OWN ledger row) and the same detail shape. The
|
|
143
|
+
* cause differs in prose only: that mount carries no write faces at all.
|
|
123
144
|
* - `"config.tool_model_gate_removed"` (design/277) — the model gate trimmed default-mounted
|
|
124
145
|
* scaffold entries from a task's roster. One notice per (model, class), de-duplicated once per
|
|
125
146
|
* `onNotice` SINK on the (modelId, class, canonical sorted removed-name set) line (same unit
|
|
@@ -629,6 +650,31 @@ export declare function deliverEngineNotice(onNotice: ((notice: EngineNotice) =>
|
|
|
629
650
|
* one repeated disclosure about one run. The account is per-RUN by its own contract sentence ("at most
|
|
630
651
|
* once per family, per run"), so it must carry the run's own id. Same omit-never-fabricate rule.
|
|
631
652
|
*/
|
|
653
|
+
/**
|
|
654
|
+
* What this leg still OWES a person, one count per family — the input to {@link undrainedUserInputNotices}.
|
|
655
|
+
* TWO lanes accept caller input and either can be holding some when a run ends: the harness's live queue
|
|
656
|
+
* (`held`, the at-agent_end snapshot, minus what a durable park migrated onto the new checkpoint) and a resumed
|
|
657
|
+
* leg's undelivered continuation frames (`owedContinuation` — the checkpoint's parked steers and the caller's
|
|
658
|
+
* wake message, minus what the same migration carried). They are ONE book: a frame is owed for the same reason
|
|
659
|
+
* on either lane — accepted, and never in the transcript — and counting the live queue alone is what let a leg
|
|
660
|
+
* that committed no pause drop a person's instruction in silence (the original row is already spent by then).
|
|
661
|
+
*/
|
|
662
|
+
export declare function owedUserInputCounts(held: {
|
|
663
|
+
steer: number;
|
|
664
|
+
followUp: number;
|
|
665
|
+
} | undefined, migrated: {
|
|
666
|
+
steer: number;
|
|
667
|
+
followUp: number;
|
|
668
|
+
}, continuation: {
|
|
669
|
+
frames: ReadonlyArray<{
|
|
670
|
+
seq?: number;
|
|
671
|
+
}>;
|
|
672
|
+
carried: number;
|
|
673
|
+
rowMayReturn: boolean;
|
|
674
|
+
}): {
|
|
675
|
+
steer: number;
|
|
676
|
+
followUp: number;
|
|
677
|
+
};
|
|
632
678
|
export declare function undrainedUserInputNotices(counts: {
|
|
633
679
|
steer: number;
|
|
634
680
|
followUp: number;
|
|
@@ -31,6 +31,13 @@ export function deliverEngineNotice(onNotice, notice) {
|
|
|
31
31
|
}
|
|
32
32
|
console.warn(notice.message);
|
|
33
33
|
}
|
|
34
|
+
export function owedUserInputCounts(held, migrated, continuation) {
|
|
35
|
+
const owedContinuation = (continuation.rowMayReturn ? continuation.frames.filter((f) => f.seq === undefined).length : continuation.frames.length) - continuation.carried;
|
|
36
|
+
return {
|
|
37
|
+
steer: Math.max(0, (held?.steer ?? 0) - migrated.steer) + Math.max(0, owedContinuation),
|
|
38
|
+
followUp: Math.max(0, (held?.followUp ?? 0) - migrated.followUp),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
34
41
|
export function undrainedUserInputNotices(counts, taskId, sessionId, runId) {
|
|
35
42
|
const tid = taskId !== undefined ? { taskId } : {};
|
|
36
43
|
const sid = sessionId !== undefined ? { sessionId } : {};
|
package/dist/core/gate-lanes.js
CHANGED
|
@@ -369,6 +369,7 @@ export async function runGateLanes(pass) {
|
|
|
369
369
|
const verdict = await input.autoMode.decider
|
|
370
370
|
.decide({ req: pass.req, askMessage: decisionText(pass.decision) }, input.abortSignal)
|
|
371
371
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
372
|
+
pass.classifierRound = verdict.classifier;
|
|
372
373
|
if (verdict.kind === "allow") {
|
|
373
374
|
input.autoMode.denialTracking?.recordAllow();
|
|
374
375
|
pass.decision = {
|
|
@@ -381,7 +382,7 @@ export async function runGateLanes(pass) {
|
|
|
381
382
|
else if (verdict.kind === "unavailable") {
|
|
382
383
|
pass.decision = attestClassifierDenyCause({
|
|
383
384
|
action: "deny",
|
|
384
|
-
message: classifierUnavailableDenyMessage(toolName, verdict.cause),
|
|
385
|
+
message: classifierUnavailableDenyMessage(toolName, verdict.cause, verdict.classifier),
|
|
385
386
|
decisionReason: "classifier",
|
|
386
387
|
}, "unavailable", pass.req);
|
|
387
388
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* verdict ended.
|
|
9
9
|
*/
|
|
10
10
|
import { type AskOrigin, type AssertAllKeysHandled } from "./ask-origin.js";
|
|
11
|
+
import { type AutoModeClassifierRound } from "./auto-mode.js";
|
|
11
12
|
/**
|
|
12
13
|
* How the wait an ask was in ENDED — the closed set. One word per way a wait can end; the `who` on the
|
|
13
14
|
* record says which party ended it and is fixed BY the word (see {@link Settlement}).
|
|
@@ -189,13 +190,23 @@ export type CauseTableCoversEveryDeniedBy = AssertAllKeysHandled<Exclude<DeniedB
|
|
|
189
190
|
/** The final disposition of a gate pass. `cause` is present exactly when the deny came out of an auto-mode
|
|
190
191
|
* classifier round that did not RULE ({@link ClassifierDenyCause}); a ruling block and every non-classifier
|
|
191
192
|
* deny carry none. Invariant I5 ({@link screenGateOutcome}): a cause stands only beside a layer word in
|
|
192
|
-
* {@link DENIED_BY_MAY_CARRY_CAUSE}.
|
|
193
|
+
* {@link DENIED_BY_MAY_CARRY_CAUSE}.
|
|
194
|
+
*
|
|
195
|
+
* `classifier` — the classification's model attribution, on BOTH arms (a classification ends either way,
|
|
196
|
+
* allow or deny): which model it was for, which one answered, and — only when the ladder moved — the rung it
|
|
197
|
+
* left and why. ABSENT reads UNKNOWN, never "the seat answered" (that is `fallback` absent WITH the member
|
|
198
|
+
* present), and a consumer cannot tell its three causes apart from the record alone: no classification
|
|
199
|
+
* happened; one ran on a decider given no candidate ladder, which can name no model; or one ran on an
|
|
200
|
+
* ANCESTOR's inherited chain, which carries the FORM (`cause`) but has no seat for the attribution yet.
|
|
201
|
+
* Additive. Invariant I6 ({@link screenGateOutcome}) screens its shape. */
|
|
193
202
|
export type GateDisposition = {
|
|
194
203
|
kind: "allowed";
|
|
204
|
+
classifier?: AutoModeClassifierRound;
|
|
195
205
|
} | {
|
|
196
206
|
kind: "denied";
|
|
197
207
|
deniedBy: DeniedBy;
|
|
198
208
|
cause?: ClassifierDenyCause;
|
|
209
|
+
classifier?: AutoModeClassifierRound;
|
|
199
210
|
};
|
|
200
211
|
/**
|
|
201
212
|
* The WHOLE record of one tool-gate pass: the final disposition, plus — when this pass settled an ask —
|
|
@@ -212,7 +223,11 @@ export type GateDisposition = {
|
|
|
212
223
|
* on the settlement (it is not the approver's refusal);
|
|
213
224
|
* I4 `disposition.kind === "allowed"` ⇒ `settlement` absent or `human_allowed`;
|
|
214
225
|
* I5 `disposition.cause` present ⇒ a member of {@link CLASSIFIER_DENY_CAUSES} beside a layer word
|
|
215
|
-
* {@link DENIED_BY_MAY_CARRY_CAUSE} admits (the classifier station, or the inherited chain's `policy`)
|
|
226
|
+
* {@link DENIED_BY_MAY_CARRY_CAUSE} admits (the classifier station, or the inherited chain's `policy`);
|
|
227
|
+
* I6 `disposition.classifier` present ⇒ both model ids are non-empty strings, and a `fallback` on it
|
|
228
|
+
* names a `to` that IS `modelUsed` (the ladder landed where the record says) with a cause in
|
|
229
|
+
* {@link import("./auto-mode.js").AUTO_MODE_FALLBACK_CAUSES} — a record whose `to` and `modelUsed`
|
|
230
|
+
* disagree would let a reader answer "which model decided this call?" two ways.
|
|
216
231
|
* Nothing here is read out of an ABSENCE: an ordinary allow with no ask is `{ disposition: { kind: "allowed" } }`,
|
|
217
232
|
* a direct policy deny is `{ disposition: { kind: "denied", deniedBy: "policy" } }`, and neither carries a
|
|
218
233
|
* settlement because neither settled one.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAskOrigin } from "./ask-origin.js";
|
|
2
|
+
import { isAutoModeFallbackCause } from "./auto-mode.js";
|
|
2
3
|
export const SETTLEMENT_KINDS = [
|
|
3
4
|
"human_allowed",
|
|
4
5
|
"human_refused",
|
|
@@ -89,5 +90,20 @@ export function screenGateOutcome(g) {
|
|
|
89
90
|
else if (isDeniedBy(d.deniedBy) && !DENIED_BY_MAY_CARRY_CAUSE[d.deniedBy])
|
|
90
91
|
defects.push(`I5: a classifier cause cannot stand beside deniedBy "${d.deniedBy}"`);
|
|
91
92
|
}
|
|
93
|
+
const attribution = d.classifier;
|
|
94
|
+
if (attribution !== undefined) {
|
|
95
|
+
const named = (v) => typeof v === "string" && v !== "";
|
|
96
|
+
if (!named(attribution.modelRequested) || !named(attribution.modelUsed))
|
|
97
|
+
defects.push("I6: disposition.classifier must name both the model the round was for and the model that answered");
|
|
98
|
+
const fb = attribution.fallback;
|
|
99
|
+
if (fb !== undefined) {
|
|
100
|
+
if (!named(fb.from) || !named(fb.to))
|
|
101
|
+
defects.push("I6: disposition.classifier.fallback must name the rung it left and the rung it arrived at");
|
|
102
|
+
else if (fb.to !== attribution.modelUsed)
|
|
103
|
+
defects.push(`I6: the fallback landed on "${fb.to}" but the record names "${attribution.modelUsed}" as the model that answered`);
|
|
104
|
+
if (!isAutoModeFallbackCause(fb.cause))
|
|
105
|
+
defects.push(`I6: fallback cause "${String(fb.cause)}" is outside the closed set`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
92
108
|
return defects;
|
|
93
109
|
}
|
|
@@ -104,7 +104,7 @@ export type NoticeAudience = "user" | "operator";
|
|
|
104
104
|
* src/ for notice mint shapes and names any code that is minted but unregistered, or registered but
|
|
105
105
|
* no longer minted.
|
|
106
106
|
*/
|
|
107
|
-
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "route.fallback_to_primary", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "tool_result.offload_put_failed"];
|
|
107
|
+
export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "tool_result.offload_put_failed"];
|
|
108
108
|
/** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
|
|
109
109
|
* `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
|
|
110
110
|
* sink is a supported shape, and narrowing that field would break it. */
|
|
@@ -102,6 +102,8 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
102
102
|
"config.models_swapped",
|
|
103
103
|
"config.read_face_swapped",
|
|
104
104
|
"config.read_face_deployment_clamped",
|
|
105
|
+
"config.write_face_swapped",
|
|
106
|
+
"config.write_face_deployment_clamped",
|
|
105
107
|
"config.tool_model_gate_removed",
|
|
106
108
|
"config.tool_model_gate_unknown_class",
|
|
107
109
|
"config.tool_model_gate_env_invalid",
|
|
@@ -116,6 +118,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
116
118
|
"peer.held_settled",
|
|
117
119
|
"peer.idle_subscription",
|
|
118
120
|
"classifier.denial_limit",
|
|
121
|
+
"classifier.fallback",
|
|
119
122
|
"checkpoint.execution_outcome_unrecorded",
|
|
120
123
|
"delegation.transcript_integrity",
|
|
121
124
|
"delegation.ask_unresolvable",
|
|
@@ -137,6 +140,7 @@ export const ENGINE_NOTICE_CODES = [
|
|
|
137
140
|
"memory.consolidation_refused",
|
|
138
141
|
"memory.consolidation_withheld",
|
|
139
142
|
"route.fallback_to_primary",
|
|
143
|
+
"route.classifier_unroutable",
|
|
140
144
|
"route.base_url_changed_key_unchanged",
|
|
141
145
|
"task.user_steer_undrained",
|
|
142
146
|
"task.user_followup_undrained",
|
|
@@ -159,6 +163,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
159
163
|
"task.user_steer_undrained": "user",
|
|
160
164
|
"task.user_followup_undrained": "user",
|
|
161
165
|
"classifier.denial_limit": "user",
|
|
166
|
+
"classifier.fallback": "operator",
|
|
162
167
|
"task.turn_interrupted": "user",
|
|
163
168
|
"steering.parked_input_blocked": "user",
|
|
164
169
|
"task.halt_unconsumed": "user",
|
|
@@ -175,6 +180,8 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
175
180
|
"config.models_swapped": "operator",
|
|
176
181
|
"config.read_face_swapped": "operator",
|
|
177
182
|
"config.read_face_deployment_clamped": "operator",
|
|
183
|
+
"config.write_face_swapped": "operator",
|
|
184
|
+
"config.write_face_deployment_clamped": "operator",
|
|
178
185
|
"config.tool_model_gate_removed": "operator",
|
|
179
186
|
"config.tool_model_gate_unknown_class": "operator",
|
|
180
187
|
"config.tool_model_gate_env_invalid": "operator",
|
|
@@ -200,6 +207,7 @@ const NOTICE_AUDIENCE_TABLE = {
|
|
|
200
207
|
"memory.consolidation_incomplete": "operator",
|
|
201
208
|
"memory.consolidation_refused": "operator",
|
|
202
209
|
"route.fallback_to_primary": "operator",
|
|
210
|
+
"route.classifier_unroutable": "operator",
|
|
203
211
|
"route.base_url_changed_key_unchanged": "operator",
|
|
204
212
|
"tool_result.offload_put_failed": "operator",
|
|
205
213
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hook seam's SEAT discipline and its disclosure vocabulary: which callback seats exist, what a
|
|
3
|
+
* deployment is allowed to put in one, and how a wiring disclosure names the ones that are wired.
|
|
4
|
+
*
|
|
5
|
+
* Kept beside the interface rather than inside it: `hooks.ts` owns the seam's BEHAVIOUR, and this module owns
|
|
6
|
+
* the seat as a CONFIGURATION VALUE — the same split `wiring-manifest.ts` keeps for the ask/question seats.
|
|
7
|
+
*/
|
|
8
|
+
import type { Hooks } from "./hooks.js";
|
|
9
|
+
import type { AssertAllKeysHandled } from "./ask-origin.js";
|
|
10
|
+
import type { SeamProvenance } from "./wiring-manifest.js";
|
|
11
|
+
/**
|
|
12
|
+
* The CALLBACK SEATS of {@link Hooks} as a word list — the vocabulary a wiring disclosure names a wired seam
|
|
13
|
+
* by (`WiringManifest.hooks[].family`), in declaration order. `preToolUseObservational` is deliberately NOT a
|
|
14
|
+
* member (it is a DECLARATION ABOUT `preToolUse`); `timeoutMs` is not a seat either.
|
|
15
|
+
*/
|
|
16
|
+
export declare const HOOK_FAMILIES: readonly ["preToolUse", "postToolUse", "userPromptSubmit", "stop", "postToolUseFailure", "postToolBatch", "preCompact", "postCompact", "stopFailure", "permissionDenied"];
|
|
17
|
+
/**
|
|
18
|
+
* One hook seat's name.
|
|
19
|
+
*
|
|
20
|
+
* @contract hooks.family_vocabulary — {@link HOOK_FAMILIES} is the only spelling of a hook seat's name on any
|
|
21
|
+
* disclosure face, and {@link HookFamiliesCoverEverySeat} makes a new callback member of {@link Hooks} red at
|
|
22
|
+
* `tsc` until it has a word there — a disclosure that omits a seat under-states what runs on the critical path.
|
|
23
|
+
*/
|
|
24
|
+
export type HookFamily = (typeof HOOK_FAMILIES)[number];
|
|
25
|
+
/** Every CALLBACK member of {@link Hooks} — derived from the interface, never a hand-kept second list. */
|
|
26
|
+
type HookCallbackSeat = {
|
|
27
|
+
[K in keyof Hooks]-?: NonNullable<Hooks[K]> extends (...args: never[]) => unknown ? K : never;
|
|
28
|
+
}[keyof Hooks];
|
|
29
|
+
/** The closed-set fence: `never` while every callback seat has a word, the MISSING SEAT'S NAME when one does
|
|
30
|
+
* not (the constraint form names it; a conditional type says only "something changed"). */
|
|
31
|
+
export type HookFamiliesCoverEverySeat = AssertAllKeysHandled<Exclude<HookCallbackSeat, HookFamily>>;
|
|
32
|
+
/** The typed refusal a hook seat holding a present NON-CALLBACK value raises at prepare. */
|
|
33
|
+
export declare const HOOKS_INVALID_CODE = "config.hooks_invalid";
|
|
34
|
+
/**
|
|
35
|
+
* Screen the resolved hook record's seats (`spec.hooks ?? deps.hooks`).
|
|
36
|
+
*
|
|
37
|
+
* @contract hooks.seat_value — a hook seat is a FUNCTION or it is absent. A PRESENT non-function
|
|
38
|
+
* (`preToolUse: null`, a string, a bare object) is a configuration error and refuses the whole prepare with
|
|
39
|
+
* {@link HOOKS_INVALID_CODE}, naming the family and the type received. It is not folded to "unwired": the tool
|
|
40
|
+
* gate arms on PRESENCE (`hooks?.preToolUse !== undefined`) and would invoke the non-function and fail closed
|
|
41
|
+
* on every call, while a callability-based disclosure reported no hooks at all. The sibling seams already
|
|
42
|
+
* refuse the same way (`resolveQuestionSeam` / `resolveElicitSeam`). `undefined` stays exactly what it was:
|
|
43
|
+
* an unwired seat, silent.
|
|
44
|
+
*/
|
|
45
|
+
export declare function assertHookSeatsCallable(hooks: Hooks | undefined, provenance: SeamProvenance): void;
|
|
46
|
+
/**
|
|
47
|
+
* One WIRED HOOK SEAT of this leg. `family` is the seat's name; `owner` is WHICH SEAT supplied the record, the
|
|
48
|
+
* `spec > deps` provenance the ask/question seams report. Deliberately no third owner word: core mounts NO
|
|
49
|
+
* engine-owned hooks, and a delegated child's inherited `preToolUse` rides the gate's constraint fold.
|
|
50
|
+
*/
|
|
51
|
+
export interface WiringManifestHookEntry {
|
|
52
|
+
family: HookFamily;
|
|
53
|
+
owner: SeamProvenance;
|
|
54
|
+
/** Only on the `preToolUse` entry, and only when the deployment DECLARED the face observational
|
|
55
|
+
* (`Hooks.preToolUseObservational`) — it still runs on every call but is not folded into a delegated
|
|
56
|
+
* child's inherited constraints. Absence means "not declared", never "declared false". */
|
|
57
|
+
observational?: true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The wiring manifest's hook read face ({@link WiringManifestHookEntry}): one entry per CALLBACK seat the
|
|
61
|
+
* resolved record wires, in the vocabulary's declaration order. The VOCABULARY is the judge — families are
|
|
62
|
+
* walked, never the record's own keys. `owner` is a property of the RECORD, so every entry agrees. Callability
|
|
63
|
+
* and presence cannot diverge here: {@link assertHookSeatsCallable} has already refused the leg where they would.
|
|
64
|
+
*/
|
|
65
|
+
export declare function wiredHookEntries(hooks: Hooks | undefined, owner: SeamProvenance, preToolUseObservational: boolean): WiringManifestHookEntry[];
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const HOOK_FAMILIES = [
|
|
2
|
+
"preToolUse",
|
|
3
|
+
"postToolUse",
|
|
4
|
+
"userPromptSubmit",
|
|
5
|
+
"stop",
|
|
6
|
+
"postToolUseFailure",
|
|
7
|
+
"postToolBatch",
|
|
8
|
+
"preCompact",
|
|
9
|
+
"postCompact",
|
|
10
|
+
"stopFailure",
|
|
11
|
+
"permissionDenied",
|
|
12
|
+
];
|
|
13
|
+
export const HOOKS_INVALID_CODE = "config.hooks_invalid";
|
|
14
|
+
export function assertHookSeatsCallable(hooks, provenance) {
|
|
15
|
+
if (hooks === undefined)
|
|
16
|
+
return;
|
|
17
|
+
for (const family of HOOK_FAMILIES) {
|
|
18
|
+
const seat = hooks[family];
|
|
19
|
+
if (seat === undefined || typeof seat === "function")
|
|
20
|
+
continue;
|
|
21
|
+
const e = new Error(`the resolved hooks record (${provenance}) holds ${seat === null ? "null" : typeof seat} at \`${family}\` — a hook seat is a callback or it is absent. ` +
|
|
22
|
+
`A present non-callback is not an unwired seat: the tool gate arms on presence and would refuse every call through it. Omit the key, or wire a function.`);
|
|
23
|
+
e.code = HOOKS_INVALID_CODE;
|
|
24
|
+
throw e;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function wiredHookEntries(hooks, owner, preToolUseObservational) {
|
|
28
|
+
if (hooks === undefined)
|
|
29
|
+
return [];
|
|
30
|
+
const entries = [];
|
|
31
|
+
for (const family of HOOK_FAMILIES) {
|
|
32
|
+
if (typeof hooks[family] !== "function")
|
|
33
|
+
continue;
|
|
34
|
+
entries.push({ family, owner, ...(family === "preToolUse" && preToolUseObservational ? { observational: true } : {}) });
|
|
35
|
+
}
|
|
36
|
+
return entries;
|
|
37
|
+
}
|
|
@@ -21,9 +21,14 @@
|
|
|
21
21
|
* no marker and an absent directory narrows.
|
|
22
22
|
*
|
|
23
23
|
* Reverse check (the seat's own invariant): nothing enters `roots` except through `admit`, and every
|
|
24
|
-
* caller of `admit` is an advertiser deciding its advertisement off the returned verdict — so
|
|
25
|
-
*
|
|
26
|
-
* that
|
|
24
|
+
* caller of `admit` is an advertiser deciding its advertisement off the returned verdict — so no
|
|
25
|
+
* directory reaches the ROOTS FENCE through this seat without the prompt advertising it, and none
|
|
26
|
+
* the prompt advertises is refused by that fence.
|
|
27
|
+
*
|
|
28
|
+
* The invariant is about this seat, not about the whole write surface: under an open write face
|
|
29
|
+
* ({@link import("../../tools/fs/containment-face.js").ContainmentFace}) the roots fence judges
|
|
30
|
+
* nothing, so what the prompt advertises is a floor rather than the whole list — the `# Environment`
|
|
31
|
+
* block says so in its own line, and the advertisement stays true (these directories ARE writable).
|
|
27
32
|
*/
|
|
28
33
|
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
29
34
|
export interface AdvertisedWritableDir {
|
|
@@ -153,11 +153,12 @@ export interface ResultFlags {
|
|
|
153
153
|
* distinct codes from arriving as one anonymous `limits.max_turns_exceeded`. Empty/absent ⇒ the field is omitted. */
|
|
154
154
|
remoteEnvFailures?: TaskResult["remoteEnvFailures"];
|
|
155
155
|
/** The run's effective read posture (resolved face + normalized deny
|
|
156
|
-
* additions), echoed on `TaskResult.effectiveReadFace` / `.effectiveReadDenyPatterns`. Pure
|
|
156
|
+
* additions), echoed on `TaskResult.effectiveReadFace` / `.effectiveWriteFace` / `.effectiveReadDenyPatterns`. Pure
|
|
157
157
|
* pass-through (assembly neither adds nor filters) and INDEPENDENT of the status/errorCode chain:
|
|
158
158
|
* the posture is a fact about the leg that ran, whatever terminal it reached — a post-completion
|
|
159
159
|
* spawner folds it stricter-wins into follow-on legs; every other consumer may ignore it. */
|
|
160
160
|
effectiveReadFace?: TaskResult["effectiveReadFace"];
|
|
161
|
+
effectiveWriteFace?: TaskResult["effectiveWriteFace"];
|
|
161
162
|
effectiveReadDenyPatterns?: TaskResult["effectiveReadDenyPatterns"];
|
|
162
163
|
/** The memory-visibility observation, echoed on
|
|
163
164
|
* `TaskResult.effectiveMemoryScopes`. Pure pass-through on every terminal, same law as the
|
|
@@ -166,5 +166,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
166
166
|
if (flags.unpricedSpend)
|
|
167
167
|
delete publicStats.costMicroUsd;
|
|
168
168
|
const stampHaltedByUser = haltConsumed(flags.haltConsumption);
|
|
169
|
-
return { taskId, ...(flags.runId !== undefined ? { runId: flags.runId } : {}), sessionId, terminal, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, ...(apiFailure !== undefined ? { apiFailure } : {}), ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.editedFiles !== undefined && flags.editedFiles.length > 0 ? { editedFiles: flags.editedFiles } : {}), ...(flags.haltedOnUserRejection === true ? { haltedOnUserRejection: true } : {}), ...(stampHaltedByUser ? { haltedByUser: true } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: [...flags.remoteEnvFailures] } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), ...(flags.effectiveReasoning !== undefined ? { effectiveReasoning: flags.effectiveReasoning } : {}), stats: publicStats };
|
|
169
|
+
return { taskId, ...(flags.runId !== undefined ? { runId: flags.runId } : {}), sessionId, terminal, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, ...(apiFailure !== undefined ? { apiFailure } : {}), ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.editedFiles !== undefined && flags.editedFiles.length > 0 ? { editedFiles: flags.editedFiles } : {}), ...(flags.haltedOnUserRejection === true ? { haltedOnUserRejection: true } : {}), ...(stampHaltedByUser ? { haltedByUser: true } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: [...flags.remoteEnvFailures] } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveWriteFace !== undefined ? { effectiveWriteFace: flags.effectiveWriteFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), ...(flags.effectiveReasoning !== undefined ? { effectiveReasoning: flags.effectiveReasoning } : {}), stats: publicStats };
|
|
170
170
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The auto-mode classifier's CANDIDATE LADDER, as one module: which models a classification may be asked on,
|
|
3
|
+
* its per-rung reasoning seat, and the once-per-run disclosure its first move mints.
|
|
4
|
+
*
|
|
5
|
+
* Beside `derived-route-fallback.ts` and in the same family — both are seats the caps phase's classifier
|
|
6
|
+
* arming reaches DOWN for, and both answer "this leg's model did not work out" one layer apart. Which is why
|
|
7
|
+
* the rung comparison here borrows the route adjudicator's own identity law instead of inventing one.
|
|
8
|
+
*
|
|
9
|
+
* THE TWO LAYERS, since one sentence ("the classifier seat did not work out ⇒ use the run's main model") is
|
|
10
|
+
* spelled in two places. They are ordered, and each has its own trigger and its own record face:
|
|
11
|
+
*
|
|
12
|
+
* ① PRE-FLIGHT — `derivedRouteFallsBack`, BEFORE any call. Trigger: the classifier model's route differs
|
|
13
|
+
* from the primary's AND the brain's own route judge finds the credential/URL pairing broken. Effect:
|
|
14
|
+
* the arming SWAPS the seat, so the ladder below is built from the main model and rung 0 IS the main
|
|
15
|
+
* model. Record: the once-per-run operator notice `route.fallback_to_primary` (`detail.{seat, from, to,
|
|
16
|
+
* cause}`) — and only that. `GateDisposition.classifier.modelRequested` reads the MAIN model's id,
|
|
17
|
+
* because the ladder it describes never carried the configured seat; the gate record answers "which
|
|
18
|
+
* model was asked", and "what did the deployment configure, and was it swapped" is the notice's
|
|
19
|
+
* question. `classifierFallback` governs THIS layer too: under `"none"` the pre-flight does not swap —
|
|
20
|
+
* it keeps the declared seat and announces `route.classifier_unroutable`, so the deployment that said
|
|
21
|
+
* "deny rather than fall back" gets the denial (the seat's calls are refused at the request gate)
|
|
22
|
+
* instead of a silent fallback the knob had just declined.
|
|
23
|
+
* ② THE LADDER — here, AFTER a call. Trigger: the rung produced no RULING. Effect: the next rung is asked.
|
|
24
|
+
* Record: `GateDisposition.classifier.fallback` on every classification this run's own gate decided (an
|
|
25
|
+
* inherited-ancestor chain carries the form and no attribution) + the throttled `classifier.fallback`.
|
|
26
|
+
*
|
|
27
|
+
* They FOLD rather than stack: after a ① SWAP the seat and the main model are the same route, so
|
|
28
|
+
* {@link classifierLadderRungs} returns ONE rung and ② cannot fire in the same run (and under `"none"`, where
|
|
29
|
+
* ① does not swap, the ladder is one rung by the knob). The split's cost is the
|
|
30
|
+
* `modelRequested` reading above; the unification (the pre-flight as rung 0 under its own cause word, one
|
|
31
|
+
* record face for both) changes the notice set and is a ticket, not a repair.
|
|
32
|
+
*/
|
|
33
|
+
import type { Model } from "../../internal/llm.js";
|
|
34
|
+
import { type AutoModeClassified } from "../auto-mode.js";
|
|
35
|
+
import type { RunnerDeps } from "../types.js";
|
|
36
|
+
/**
|
|
37
|
+
* The rungs a classification walks, in order: the classifier seat, and — under `classifierFallback: "main"` (the
|
|
38
|
+
* default) — the run's main model behind it. The knob is screened by `autoModeClassifierFallbackOf`, here and
|
|
39
|
+
* (idempotently) at the caps phase that reads the same word for the pre-flight's posture, so a value outside
|
|
40
|
+
* the closed set refuses the prepare loudly (`config.auto_mode_classifier_fallback_invalid`) instead of
|
|
41
|
+
* reading as the default.
|
|
42
|
+
*
|
|
43
|
+
* ONE RUNG when the two resolve to the SAME ROUTE. Route identity, not `Model.id`: two catalog entries may
|
|
44
|
+
* legitimately share a wire id while pointing at different endpoints (the route adjudicator's own law, and
|
|
45
|
+
* the split its pre-flight exists for) — an id-only comparison would collapse a healthy main endpoint and a
|
|
46
|
+
* broken classifier endpoint into one rung, turning the ladder off for the deployment that needs it most.
|
|
47
|
+
* The collapse where it does apply is deliberate: re-asking the SAME model is a retry, which is what CC's
|
|
48
|
+
* `unavailableOuterRetries` does and what this ladder is not. A catalog value the identity law cannot
|
|
49
|
+
* normalize (a non-string `baseUrl` — it throws) reads as TWO rungs: being wrong that way costs one extra
|
|
50
|
+
* call on a broken catalog, the other way turns the feature off invisibly, and the bad value still earns its
|
|
51
|
+
* loud refusal at the brain's request gate.
|
|
52
|
+
*/
|
|
53
|
+
export declare function classifierLadderRungs(knob: unknown, classifier: Model, main: Model): readonly Model[];
|
|
54
|
+
/**
|
|
55
|
+
* The classifier's REASONING SEAT, read off the model a classification is actually asked on — ONE predicate:
|
|
56
|
+
* can the wire say off? Yes ⇒ `off` + the verdict-sized cap (the request pays for the answer alone); no ⇒
|
|
57
|
+
* `low` + the verdict cap + the low-reasoning budget (a model that thinks regardless is told to think the
|
|
58
|
+
* LEAST its wire can spell, in exactly the room that tier needs). PER RUNG, not once per arming: the second
|
|
59
|
+
* rung is a different model with its own wire, and asking it under the first rung's seat is how a correct
|
|
60
|
+
* fallback comes back with zero text. The `low` seat is also the re-ask seat: an unknown model that answers
|
|
61
|
+
* NOTHING at the `off` cap has just told us it thinks anyway, and is re-asked as one that does.
|
|
62
|
+
*/
|
|
63
|
+
export declare const CLASSIFIER_OFF_SEAT: {
|
|
64
|
+
reasoning: "off";
|
|
65
|
+
maxTokens: number;
|
|
66
|
+
};
|
|
67
|
+
export declare const CLASSIFIER_LOW_SEAT: {
|
|
68
|
+
reasoning: "low";
|
|
69
|
+
maxTokens: number;
|
|
70
|
+
};
|
|
71
|
+
export declare function classifierRungSeat(model: Model): typeof CLASSIFIER_OFF_SEAT | typeof CLASSIFIER_LOW_SEAT;
|
|
72
|
+
/**
|
|
73
|
+
* The per-decision READ of a classification, as one seat: the `auto_mode.classified` trace frame (wall time,
|
|
74
|
+
* verdict, cause) and the ladder's throttled `classifier.fallback` disclosure. Both are observers — a
|
|
75
|
+
* throwing sink never reaches the gate (the decider swallows it), and neither changes a verdict.
|
|
76
|
+
*
|
|
77
|
+
* The frame's `model` is the model the classification RAN ON, which is what the field has always meant (its
|
|
78
|
+
* declaration reads "the resolved classifier role, or the main model it fell back to"): the route
|
|
79
|
+
* pre-flight's swap and the ladder's walk are two ways to reach that same sentence. `seatModelId` is the
|
|
80
|
+
* fallback for a classification that carries no attribution at all.
|
|
81
|
+
*/
|
|
82
|
+
export declare function classifierRoundObserver(args: {
|
|
83
|
+
tracer: RunnerDeps["tracer"];
|
|
84
|
+
onNotice: RunnerDeps["onNotice"];
|
|
85
|
+
taskId: string;
|
|
86
|
+
seatModelId: string;
|
|
87
|
+
sessionId?: string;
|
|
88
|
+
runId?: string;
|
|
89
|
+
}): (info: AutoModeClassified) => void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { sameRouteIdentity } from "../../brain/route-adjudicator.js";
|
|
2
|
+
import { thinkingOffExpressible } from "../../brain/reasoning.js";
|
|
3
|
+
import { AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "../auto-mode-defaults.js";
|
|
4
|
+
import { autoModeClassifierFallbackOf } from "../auto-mode.js";
|
|
5
|
+
import { emitTrace } from "../trace.js";
|
|
6
|
+
import { deliverEngineNotice } from "../types.js";
|
|
7
|
+
export function classifierLadderRungs(knob, classifier, main) {
|
|
8
|
+
if (autoModeClassifierFallbackOf(knob) === "none")
|
|
9
|
+
return [classifier];
|
|
10
|
+
let sameRoute;
|
|
11
|
+
try {
|
|
12
|
+
sameRoute = sameRouteIdentity(classifier, main);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
sameRoute = false;
|
|
16
|
+
}
|
|
17
|
+
return sameRoute ? [classifier] : [classifier, main];
|
|
18
|
+
}
|
|
19
|
+
function classifierFallbackAnnouncer(args) {
|
|
20
|
+
const announced = new Set();
|
|
21
|
+
return (attribution) => {
|
|
22
|
+
const fallback = attribution?.fallback;
|
|
23
|
+
if (fallback === undefined)
|
|
24
|
+
return;
|
|
25
|
+
const key = `${fallback.from}\u0000${fallback.cause}`;
|
|
26
|
+
if (announced.has(key))
|
|
27
|
+
return;
|
|
28
|
+
announced.add(key);
|
|
29
|
+
deliverEngineNotice(args.onNotice, {
|
|
30
|
+
code: "classifier.fallback",
|
|
31
|
+
message: `the auto-mode classifier model "${fallback.from}" did not rule (${fallback.cause}) — this run re-asks each gated call on "${fallback.to}". ` +
|
|
32
|
+
'Check that the classifier role names a model this deployment\'s gateway serves; set RunnerDeps.autoMode.classifierFallback to "none" to deny instead of falling back.',
|
|
33
|
+
detail: {
|
|
34
|
+
from: fallback.from,
|
|
35
|
+
to: fallback.to,
|
|
36
|
+
cause: fallback.cause,
|
|
37
|
+
...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
|
|
38
|
+
...(args.runId !== undefined ? { runId: args.runId } : {}),
|
|
39
|
+
},
|
|
40
|
+
...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export const CLASSIFIER_OFF_SEAT = { reasoning: "off", maxTokens: AUTO_MODE_CLASSIFIER_MAX_TOKENS };
|
|
45
|
+
export const CLASSIFIER_LOW_SEAT = { reasoning: "low", maxTokens: AUTO_MODE_CLASSIFIER_MAX_TOKENS + AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS };
|
|
46
|
+
export function classifierRungSeat(model) {
|
|
47
|
+
return thinkingOffExpressible(model) ? CLASSIFIER_OFF_SEAT : CLASSIFIER_LOW_SEAT;
|
|
48
|
+
}
|
|
49
|
+
export function classifierRoundObserver(args) {
|
|
50
|
+
const announce = classifierFallbackAnnouncer(args);
|
|
51
|
+
return (info) => {
|
|
52
|
+
emitTrace(args.tracer, () => ({
|
|
53
|
+
kind: "auto_mode.classified",
|
|
54
|
+
version: 1,
|
|
55
|
+
taskId: args.taskId,
|
|
56
|
+
toolCallId: info.toolCallId,
|
|
57
|
+
model: info.classifier?.modelUsed ?? args.seatModelId,
|
|
58
|
+
ms: info.ms,
|
|
59
|
+
verdict: info.verdict,
|
|
60
|
+
...(info.cause !== undefined ? { cause: info.cause } : {}),
|
|
61
|
+
ts: Date.now(),
|
|
62
|
+
}));
|
|
63
|
+
announce(info.classifier);
|
|
64
|
+
};
|
|
65
|
+
}
|