@sema-agent/core
Version:
Stateless, task-oriented AI agent core
88 lines (87 loc) • 7.12 kB
TypeScript
/**
* `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 {};