UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

88 lines (87 loc) • 7.12 kB
/** * `delegation.ask_unresolvable` — the one mint of the notice the gate raises when an `ask` reached its * final deny with NOBODY having judged it. Three ways that happens, one event class ("a call was denied * fail-closed because its approval could not be resolved"), one closed cause set: * · `approver_unavailable` — the approver consulted for the call (the run's own seat, or an ancestor's * frozen seat on a delegated child) answered the ROUTING question `unavailable` and no durable park * took the call afterwards (`tool_end.gate.settlement.kind:"approver_unavailable"`); * · `no_approver_wired` — no approver (or, for a question, no question face) is wired for this run, or * the seat is the deny posture string: the headless auto-deny (`settlement.kind:"no_approver"`); * · `mandate_unreconstructible` — an inherited durable-approval mandate binds the ask and THIS leg has no * park facility to honor it, so the delegated child refuses in-fold rather than let a synchronous layer * resolve what its ancestor bound to a park (an in-fold deny; no settlement rides the frame). * The deny itself is older than this notice and stands unchanged (the model reads the sentence as its * tool result); what was missing was any face a PERSON watching notices could see — an operator reading * notices alone saw nothing, and the person whose work was refused learned it only from a transcript. * * ONE exit composes it: the gate's single deny exit (`exitGate`), which reads the settlement the deny * carries or the cause an in-fold arm attested, after every park re-route has had its attempt. That exit is * the only caller of this factory: one call, one notice, once per tool call. * * Audience `"user"`: the refused work is the user's own (their run, or a delegation their run made), and * the recovery — a checkpoint store with a durable approval opt-in so such asks can wait for a person, or * a live approver reachable for delegated children — is a deployment posture the operator hears through * the sink like every other code; the row only adds the session-stream projection so the person whose * call was refused sees WHY it did not run. `sessionId` is therefore REQUIRED at the mint (the routing * half of a user row). Dedup unit: per tool call — the gate's obligation (it owns the call); this module * is pure. */ import type { EngineNotice } from "./engine-notice.js"; import type { AssertAllKeysHandled, HeadlessApproverSeat } from "./ask-origin.js"; /** WHY the ask could not be resolved — the closed set (see the module note for each word). */ export declare const ASK_UNRESOLVABLE_CAUSES: readonly ["approver_unavailable", "no_approver_wired", "mandate_unreconstructible"]; export type AskUnresolvableCause = (typeof ASK_UNRESOLVABLE_CAUSES)[number]; /** Membership test for {@link AskUnresolvableCause} — the word rides a notice detail across process boundaries. */ export declare function isAskUnresolvableCause(v: unknown): v is AskUnresolvableCause; /** What the gate knows at the deny exit — facts only, no words of the engine's vocabulary composed. */ export interface AskUnresolvableFacts { /** The session whose call was refused — the notice's routing key, required (see module note). */ readonly sessionId: string; /** The tool the ask was for (the message quotes it verbatim). */ readonly toolName: string; /** The refused call's id — the correlation key to the `tool_end` frame carrying the same deny. */ readonly toolCallId: string; /** Why the ask could not be resolved — carried as a fact from the exit, not re-derived here. */ readonly cause: AskUnresolvableCause; /** Whether a durable approval park lane was ARMED for this run (`true`: the park had its one attempt and * did not take the call — it declined or failed; `false`: no park lane at all, the usual shape). */ readonly parkLaneExisted: boolean; /** Present for the causes that say nobody could be asked ({@link askUnresolvableRemedySeat}): the caller-side change that * would have let the call run, as the gate minted it for the refusal's tool text (`headlessApprovalRemedy`, ask-origin.ts) * — carried verbatim, never re-composed here. */ readonly remedy?: string; } /** * Which causes take the caller-side remedy (`headlessApprovalRemedy`, ask-origin.ts), and in which seat's clause — the table IS * the rule, and it is total over the closed set (a cause with no row is a red build: {@link CauseRemedySeatCoversEveryCause}). * The two causes that say NOBODY COULD BE ASKED take it — no approver is wired (`absent`: the clause says to wire one), or the * wired approver reported nobody reachable (`unavailable`: the clause says to make a person reachable through it, never to * wire what is wired): one fact, one sentence about the way out, whichever way nobody was there. The inherited-mandate cause * takes none (`undefined`): its recovery is a park facility on this leg, which neither a permission mode nor an `onAsk` here * supplies, so the remedy's arms would name a change that does not release the call. (The settlement-axis table * `SETTLEMENT_NOBODY_JUDGED` keeps `approver_unavailable` false because there a park route may still take the ask; this table is * read at the deny exit, where every park route has had its attempt.) */ declare const CAUSE_REMEDY_SEAT: { readonly approver_unavailable: "unavailable"; readonly no_approver_wired: "absent"; readonly mandate_unreconstructible: undefined; }; export type CauseRemedySeatCoversEveryCause = AssertAllKeysHandled<Exclude<AskUnresolvableCause, keyof typeof CAUSE_REMEDY_SEAT>>; /** The seat whose clause the refusal for `cause` carries its remedy in, or `undefined` when the cause takes no remedy * ({@link CAUSE_REMEDY_SEAT}); read once at the deny exit, which hands the sentence to the tool text and the notice alike. */ export declare function askUnresolvableRemedySeat(cause: AskUnresolvableCause): HeadlessApproverSeat | undefined; /** * Compose the `delegation.ask_unresolvable` notice from the gate's deny-exit facts. * * @contract delegation.ask_unresolvable.single_mint — the `delegation.ask_unresolvable` notice is composed * by CORE from the gate's deny-exit FACTS (session, tool, call id, the cause, whether a park lane existed) * at the ONE deny exit every unresolvable arm reaches; no other site spells the code or the sentence, and * an empty session or tool name, or a cause outside the closed set, is refused at the mint with a * `TypeError` rather than shaped into a notice that would route nowhere or say nothing. * * `detail: { sessionId, toolName, toolCallId, cause, parkLaneExisted, remedy? }` — `remedy` rides the causes that say nobody * could be asked ({@link askUnresolvableRemedySeat}: the sentence the refusal's tool text carries); `sessionId` is lifted onto the typed key by {@link import("./engine-notice.js").deliverEngineNotice}, the one throat. */ export declare function askUnresolvableNotice(facts: AskUnresolvableFacts): EngineNotice; export {};