@sema-agent/core
Version:
Stateless, task-oriented AI agent core
345 lines (344 loc) • 22.9 kB
TypeScript
import type { ExecutionEnv } from "../../internal/harness-types.js";
import type { AssistantMessage, Message, Model, Usage } from "../../internal/llm.js";
import type { CwdRef } from "../../tools/fs/fs-shared.js";
import { type PermissionResult, type ToolCallRequest } from "../tool-policy.js";
import type { HookInvocationIdentity, PermissionDeniedPayload } from "../hooks.js";
import type { PermissionMode } from "../ask-origin.js";
import type { RunnerDeps } from "../runner-deps.js";
import type { TaskSpec } from "../task-spec.js";
import { type ModBus, type ModMountedHandler, type ModDispatchOutcome, type ModEventName, type ModEventPayload, type ModRegistration } from "./kernel/index.js";
import { type SourcedOverlay } from "../../prompt-assembly/overlay.js";
import type { BandId } from "../../prompt-assembly/catalog/schema.js";
import type { ModParticipant } from "./kernel/dispatch.js";
import { type EngineDollar, type ModelCompleteRequest, type ModelCompleteResult, type ModSeatView } from "./dollar.js";
/**
* What the engine's core answers at the bottom of every classic chain: the engine itself has nothing to add.
* Frozen and empty, so it reads as "no opinion" in every classic station's answer form, and a handler that
* builds on it (`{ ...(await next(e)), additionalContext }`) builds a well-formed answer.
*/
export declare const ENGINE_CORE_ANSWER: Readonly<Record<string, never>>;
/** The classic chains' bottom. */
export declare function engineCore(): Readonly<Record<string, never>>;
/** The leg's bus seat, as the stations read it. */
export type LegModBus = Pick<ModBus, "hasHandlers" | "dispatch">;
/**
* The id a delegated leg's events carry as `agentId` (the leg's task id — the key its `task_progress` frames
* and its parent's fleet row use). A top-level leg carries none.
*/
export declare function agentIdOf(identity: HookInvocationIdentity | undefined): string | undefined;
/**
* The stop event a leg raises when its run would otherwise end: `classic.SubagentStop` on a delegated child's leg — the
* delegation fact, which a child resumed from its checkpoint keeps — else `classic.Stop`. Not the leg kind: a resumed
* leg's kind reads `"resume"` whoever it runs as.
*/
export declare function stopEventOf(identity: Pick<HookInvocationIdentity, "isDelegatedChild"> | undefined): "classic.Stop" | "classic.SubagentStop";
/**
* The args as a judging handler receives them: the gate pass's own immutable value ({@link ownArgs} — the value itself when
* the gate already took it, which is every call the gate raises an event over), so the bytes a handler judges are the bytes
* that run — an in-place edit throws instead of moving what executes, or what a handler further down judges (a rewrite is
* an answer's `updatedInput`). A graph the copy refuses is handed as is (the gate refuses such args at its entry).
*/
export declare function judgedInputOf(args: unknown): unknown;
/** One link of a leg's chain as a constraint entry names it: a registration the leg's admission mounted, with the handlers it hung. */
export interface ModChainLink {
readonly registration: ModRegistration<EngineDollar>;
readonly handlers: readonly ModMountedHandler[];
}
/**
* A bus's chain identity: the registrations its admission MOUNTED (a refused one judges nothing there), in run order, each
* with the handlers it actually hung (pattern, matcher, observe, `.catch`, in hang order). Two legs that offered the same
* registrations but admitted — or hung — differently are different chains: an inherited entry collapses into a child's phase
* 1 only when the child mounted the very same. A handler's code is not compared (each leg's `register` builds its own
* closures); what it was hung on is. Every engine admission records it once ({@link chainOfBus}).
*/
export declare function admittedChainOf(bus: Pick<ModBus, "records" | "mounted">, spec: Pick<TaskSpec, "mods">, deps: Pick<RunnerDeps, "mods">): readonly ModChainLink[];
/** The chain identity a bus's admission recorded, or `undefined` for a bus no engine admission mounted (its chain cannot be
* read, so it is the same chain as none). */
export declare function chainOfBus(bus: object): readonly ModChainLink[] | undefined;
/** Whether two chains are the same links: the same registration objects, in the same order, having hung the same handlers. */
export declare function sameModChain(a: readonly ModChainLink[], b: readonly ModChainLink[]): boolean;
/**
* Whether the leg's chain JUDGES a tool call: a judging handler on either event a call is judged on — `classic.PreToolUse`
* (the gate's phase 1) or `tool.check` (the gate's verdict read). Such a chain is what a leg hands its children as a
* screening constraint, and what a child's own gate stands in for when it mounted the same.
*/
export declare function chainJudges(bus: LegModBus): boolean;
/** Record what the constraint mint minted `policy` over. */
export declare function recordScreeningChain(policy: object, chain: LegModBus): void;
/** The chain a screening constraint was minted over, or `undefined` when the mint did not make this object. */
export declare function screeningChainOf(policy: object): LegModBus | undefined;
/** How an inherited installation's question is settled: under the context its installation froze (its approver, its durable
* mandates, its classifier — the settlement its `classic.PreToolUse` ask takes in the fold), never at this leg's approver. An
* allow carrying `updatedInput` is a person's approval of an edit; an ask is one the settlement floated (the call is marked for
* the park). */
export type InstallationAskSettlement = (ask: Extract<PermissionResult, {
action: "ask";
}>, call: ToolCallRequest, signal: AbortSignal | undefined) => Promise<PermissionResult>;
/** An inherited installation as a leg's gate raises `tool.check` on it: the bus that mounted that chain, which of its handlers
* judge here, and how its question is settled. */
export interface InheritedToolCheck {
readonly bus: LegModBus;
readonly judges: (p: ModParticipant) => boolean;
readonly settle: InstallationAskSettlement;
}
/** An inherited chain entry as its installation identity reads it: the policy (whose mint names the bus, whose admission names
* the chain), the approver it froze, its two durable mandates, the environment it was installed against, its classifier. */
export interface InstallationEntry {
readonly policy: object;
readonly onAsk?: unknown;
readonly durableMandate?: boolean;
readonly contentMandate?: boolean;
readonly hookEnv?: unknown;
readonly autoMode?: {
readonly decider: unknown;
};
}
/** A leg's own installation context: its chain identity, its frozen approver, its two durable mandates, the environment its
* phase 1 runs in, its classifier instance. */
export interface LegInstallation {
readonly chain: readonly ModChainLink[];
readonly onAsk: unknown;
readonly durableMandate: boolean;
readonly contentMandate: boolean;
readonly hookEnv: unknown;
readonly decider: unknown;
}
/**
* Whether an inherited entry IS a leg's installation — the ONE installation identity. The chain is read off the entry's own
* bus (the one its policy was minted over, as that bus's admission recorded it), never off a list the entry carries: an entry
* whose policy the mint did not make, or whose bus no engine admission mounted, is no leg's installation. The rest is the
* context its questions are settled in: the same approver ({@link askApproverIdentity}), the same two durable mandates, the
* same environment, the same classifier instance. Readers: chain assembly (a leg already on the chain is not appended again)
* and {@link standsInFor} (the fold's pass-through and the `tool.check` raise).
*/
export declare function sameInstallation(pc: InstallationEntry, leg: LegInstallation): boolean;
/**
* Whether a leg's own gate stands in for an inherited entry: the leg's chain judges, and the entry is the leg's installation
* settled with no durable mandate — its phase 1 then judges the call with the same chain, and its questions go to the same
* approver under no mandate, so the entry adds nothing on either judging event. The fold's pass-through for the entry's
* `classic.PreToolUse` and the skip of its `tool.check` raise are this one test (an entry it does not hold for is judged under
* its own context on both: a second judgment, never a lost one).
*/
export declare function standsInFor(pc: InstallationEntry, leg: Omit<LegInstallation, "durableMandate" | "contentMandate">, legChainJudges: boolean): boolean;
/** An inherited screening installation as the carrier check reads it: the bus its policy was minted over (absent when the mint
* did not make the policy) and the chain the entry declares (its `mods`; absent when it declares none). The declaration is read
* for one thing — a guard the leg cannot raise ({@link unraisableGuardsOf}); identity is read off the bus ({@link sameInstallation}). */
export interface ScreeningInstallation {
readonly bus: LegModBus | undefined;
readonly chain: readonly ModChainLink[] | undefined;
}
/** The screening installations among a chain's entries, in the chain's order: every entry whose policy the mint made or that
* declares a chain. */
export declare function screeningInstallationsOf(entries: ReadonlyArray<{
readonly policy: object;
readonly mods?: readonly ModChainLink[];
}>): readonly ScreeningInstallation[];
/** Why an inherited chain cannot hold its guards, or `undefined`: an entry that names a judging `tool.check` handler beside a
* policy the mint did not make has no bus to raise that guard on. Read where the chain enters a leg — before a resume's claim
* and at the leg's prepare — so it is refused, never run without the guard. */
export declare function unraisableGuardsOf(installations: readonly ScreeningInstallation[]): string | undefined;
/**
* The inherited installations a leg's gate raises `tool.check` on after its own bus — the rule's one home. Every entry whose
* policy the mint made is one installation, raised whole in the chain's order (the order its `classic.PreToolUse` folds in),
* except one the leg's own gate stands in for (`standsIn` — the fold's pass-through test, {@link standsInFor}). Raised whole
* because a handler's answer depends on the chain it sits in (an outer handler may answer without calling `next`, or rule on
* what an inner one answered), so a registration the leg also mounts in a chain of its own judges again inside the ancestor's
* chain, as the PreToolUse constraint judges it (a second judgment, never a lost one). Each carries its entry's settlement of a
* question (`settleOf`): an installation's `ask` goes where its PreToolUse ask goes. Observe handlers never take part (a
* constraint carries no observer). An entry that cannot raise its guard is refused (`config.mods_invalid`, {@link unraisableGuardsOf}).
*/
export declare function inheritedToolChecks<E extends InstallationEntry & {
readonly mods?: readonly ModChainLink[];
}>(entries: readonly E[], standsIn: (pc: E) => boolean, settleOf: (pc: E) => InstallationAskSettlement): readonly InheritedToolCheck[];
/** The members every classic `e` of a leg carries: the identity envelope, the delegated leg's `agentId`, the leg's observed mode. */
export declare function classicEventBase(identity: HookInvocationIdentity | undefined, permissionMode?: PermissionMode): ModEventPayload;
/**
* Raise an observation event (a skip-on-failure event whose answer no station reads): no handler, no dispatch.
* Its failures are the bus's to report; a rejected dispatch (an engine fault, never a handler's) is swallowed —
* an observation must never change what it observes.
*/
export declare function observeClassic(bus: LegModBus, event: ModEventName, e: ModEventPayload, signal: AbortSignal | undefined): Promise<void>;
/**
* Raise a classic event whose answer the station reads: `undefined` when no handler hangs on it (the station
* runs as if the bus were not there), else the dispatch outcome. A rejection is an engine fault, never a
* handler's (handlers fail into the outcome); a decision station fails closed on it, an observation swallows it.
*/
export declare function dispatchClassic(bus: LegModBus, event: ModEventName, e: ModEventPayload, options: {
readonly signal?: AbortSignal;
readonly checkAnswer?: (answer: unknown) => string | undefined;
}): Promise<ModDispatchOutcome | undefined>;
/** The answer form of a classic event whose answer is an object of optional string members (`block`, `additionalContext`, …). */
export declare function stringMembersAnswer(event: string, members: readonly string[]): (answer: unknown) => string | undefined;
/** The string members of an answer the station reads (a member of any other type reads as absent — the answer check has already refused it). */
export declare function stringMembersOf<M extends string>(answer: unknown, members: readonly M[]): {
[K in M]?: string;
};
/** The clause a fail-closed station puts in its sentence for the handler that failed it: `crashed (extension "x" threw boom)`. */
export declare function failedHandlerClause(f: Extract<ModDispatchOutcome, {
kind: "failed";
}>): string;
/**
* The PermissionDenied station: the leg's `classic.PermissionDenied` handlers, then a host's own raw observer (a host
* driving the tool gate itself) as one more observe handler of the same event on a bus of its own — so every observer
* of a deny runs under that station's columns (the handler's budget, skipped on a failure, a short grace past an
* abort) and none can hold the deny it reports. The raw observer's failure goes to `onHostObserverFailure`.
*/
export declare function observePermissionDenied(input: {
readonly legBus: LegModBus | undefined;
readonly hostObserver: ((payload: PermissionDeniedPayload) => void | Promise<void>) | undefined;
readonly payload: PermissionDeniedPayload;
readonly signal: AbortSignal | undefined;
readonly onHostObserverFailure: (err: Error) => void;
}): Promise<void>;
/** What a `classic.UserPromptSubmit` screen came to, as the four entrances read it. */
export type PromptScreen = {
readonly kind: "answered";
readonly block?: string;
readonly additionalContext?: string;
} | Extract<ModDispatchOutcome, {
kind: "failed";
}> | {
readonly kind: "aborted";
};
/**
* Raise `classic.UserPromptSubmit` (undefined when no handler hangs on it). A decision event: a handler that throws,
* runs past its budget or breaks the site's contract ends the screen `failed`, and every entrance fails closed on it;
* a cancelled screen ends `aborted` at once. A dispatch the engine itself could not run is `failed` too.
*/
export declare function screenUserPrompt(bus: LegModBus, e: ModEventPayload, signal: AbortSignal | undefined): Promise<PromptScreen | undefined>;
/** The three words a `tool.check` verdict speaks. */
export declare const TOOL_CHECK_DECISIONS: readonly ["allow", "ask", "deny"];
export type ToolCheckDecision = (typeof TOOL_CHECK_DECISIONS)[number];
/**
* The gate's standing verdict as the bottom of the `tool.check` chain answers it — what a handler reads from `next(e)`.
* Never `deny` (a standing refusal raises nothing). On an ask: its classes, and `mandated` — true when an `allow`
* answer cannot clear it at this point of the gate. `ceiling` mirrors `e.ceiling`. Read-only metadata: the gate reads
* the answer's `decision` and `reason` and nothing else of it.
*/
export interface ToolCheckProjection {
readonly decision: "allow" | "ask";
readonly reason?: string;
readonly classes?: readonly string[];
readonly mandated?: boolean;
readonly ceiling?: "ask";
}
/** What one `tool.check` raise came to: the chain's verdict (`by` = the registration whose answer the chain returned;
* absent when no handler spoke and the gate's own verdict came back up), a handler's failure, or a cancel. */
export type ToolCheckOutcome = {
readonly kind: "answered";
readonly decision: ToolCheckDecision;
readonly reason?: string;
readonly by?: string;
} | Extract<ModDispatchOutcome, {
kind: "failed";
}> | {
readonly kind: "aborted";
};
/**
* Raise `tool.check` over the gate's standing verdict (undefined when no handler hangs on it — nothing is raised). A
* refusing event: a handler that throws, runs past its budget or answers out of the site's form ends the raise
* `failed`, a cancel ends it `aborted`, and the gate refuses the call on either. A dispatch the engine itself could not
* run is `failed` too. `judges` = which handlers take part (absent ⇒ every handler on the bus: the leg's own raise; an
* inherited installation's raise passes its {@link InheritedToolCheck.judges}).
*/
export declare function raiseToolCheck(bus: LegModBus, e: ModEventPayload, projection: ToolCheckProjection, signal: AbortSignal | undefined, judges?: (p: ModParticipant) => boolean): Promise<ToolCheckOutcome | undefined>;
/** What a `prompt.compose` raise came to: each source class's overlays in application order, and the operator lines
* for every handler whose answer was skipped. */
export interface PromptComposeOutcome {
readonly deploy: readonly SourcedOverlay[];
readonly task: readonly SourcedOverlay[];
readonly warnings: readonly string[];
}
/**
* Raise `prompt.compose` — the collection point of the deploy and task source classes' overlays — over the leg's
* bus (nothing is raised when no handler hangs on it). A collecting event: a handler's contribution is what its
* answer adds after what its `next(e)` returned, and what `next(e)` returned must stand unchanged, in order, at the
* head of its answer — a handler that answers without its `next(e)` (or before it returned), rewrites or removes a
* lower contribution, or adds an overlay the band table does not grant its seat's class (or one that carries locked
* text, or one that is not data), is skipped, and what is below it stands (runs in its place, when it never called
* `next`). The engine records, at each handler's boundary, the overlays as it validated them and who contributed
* each (the registration and its seat: the deployment seat is the deploy class, the task seat the task class); what
* applies is that record, never the answer read again. Each class applies its contributions in chain order, so a
* later registration's overlay on the same target wins.
*
* `lowerAdds` — the entries the classes below deploy already added (the only entries a deploy `drop` may name
* besides a built-in section).
*/
export declare function raisePromptCompose(bus: LegModBus, e: ModEventPayload, options: {
readonly lowerAdds: ReadonlyMap<string, BandId>;
readonly signal?: AbortSignal;
}): Promise<PromptComposeOutcome>;
/** The seats behind the engine facade, bound once the leg has them. Every member is read at its call. */
export interface LegFacadeSeats {
readonly exec: ModSeatView["env"]["exec"];
complete(request: ModelCompleteRequest, signal: AbortSignal): Promise<ModelCompleteResult>;
readonly sessionId: string;
/** The run's account of side model calls ({@link ModSeatView.book}). */
book: ModSeatView["book"];
cwd(): string;
root(): string;
model(): string;
messages(): readonly Message[];
now(): number;
tools(): readonly string[];
agents(): readonly string[];
}
/** The leg's admitted bus, its chain identity (the one its admission recorded, {@link chainOfBus}) and the binding of the
* facade its `$` reaches. */
export interface LegMods {
readonly bus: ModBus;
readonly chain: readonly ModChainLink[];
/** Bind the engine facade once the leg is prepared. Until then a `$` call throws, naming the method. */
bindFacade(seats: LegFacadeSeats): void;
}
/**
* Admit the leg's bus: both seats (`RunnerDeps.mods`, `TaskSpec.mods`), screened and put to `plugin.register`.
* Called once per leg, at that leg's own prepare (a delegated child and a resumed leg each admit their own);
* each refused registration becomes one `mods.registration_refused` notice. Throws `config.mods_invalid` for a
* registration the kernel cannot take.
*/
export declare function admitLegMods(input: {
readonly spec: Pick<TaskSpec, "mods">;
readonly deps: Pick<RunnerDeps, "mods" | "onError" | "onNotice">;
readonly sessionId: string;
}): Promise<LegMods>;
/** What a leg hands {@link legFacadeSeats}: the seats it already has. */
export interface LegFacadeInput {
readonly deps: Pick<RunnerDeps, "brain" | "models" | "now">;
readonly spec: Pick<TaskSpec, "getApiKeyAndHeaders">;
readonly sessionId: string;
readonly model: Model;
readonly executionEnv: Pick<ExecutionEnv, "exec">;
readonly taskRoot: string;
readonly cwdRef: CwdRef | undefined;
/** The leg's latest model request as recorded at the provider seam (the session's messages as the model last saw them). */
readonly lastBrainContext: () => {
readonly messages: readonly Message[];
} | undefined;
readonly activeTools: ReadonlySet<string>;
readonly agentListing: {
readonly entries: ReadonlyArray<{
readonly name: string;
}>;
} | undefined;
/** The run's one account of side model calls (the same entry the classifier's side calls book on), handed the model a
* `$.model.complete` call resolved to, the usage its reply reported (absent ⇒ none reported) and the reply's
* diagnostics when it carried any (a degraded brain's fallback is priced by them). */
readonly book: (model: Model, reply: {
readonly usage: Usage | undefined;
readonly diagnostics?: AssistantMessage["diagnostics"];
}) => void;
}
/** The engine facade's seats over a prepared leg — each one a seat the leg already has (no new capability). */
export declare function legFacadeSeats(input: LegFacadeInput): LegFacadeSeats;
/**
* A chain admitted outside any leg — what a host rebuilds to re-supply an inherited screening entry on a fresh Runner
* (a cross-process resume hands back `createPreToolUseConstraintPolicy(chain, env)` over the same registrations). The
* same admission a leg runs (both seats, `plugin.register`); no leg exists, so its `$` facade is never bound and a `$`
* call throws. Handler failures go to `onError` (absent ⇒ unreported). Its chain identity is recorded as a leg's is
* ({@link chainOfBus}), so an entry minted over it is read by what it mounted — whatever list the entry carries.
*/
export declare function admitModChain(seats: {
readonly deployment?: readonly ModRegistration<EngineDollar>[];
readonly task?: readonly ModRegistration<EngineDollar>[];
}, onError?: (err: Error) => void): Promise<ModBus>;