@sema-agent/core
Version:
Stateless, task-oriented AI agent core
327 lines (326 loc) • 25.5 kB
TypeScript
/**
* PrepareTask's INHERITED-GATE phase, verbatim from the driver: the live/seed fold of the
* chain this run inherits (ancestor rules rev-monotonic per (session, principal), the shell floor, the
* re-supplied parent constraints with their rebuilt denial trackers, the permission mode's live seat, the org-admission
* freeze with its tighten-only intersection and the monotonic governed bit), the effective shell floor against the
* spec, the three cells later phases fill for the children's chain (own session rules, own admitted org scopes,
* own org verdict), the checkpoint-section closures the suspend serializer reads (face / org / F-012 stamps), the
* FROZEN ask and question seats (spec over deps), the parent SPEC seat the children inherit, the two delegation refs the memory phase binds late, the ONE
* child-chain assembly `inheritedGateForChildren`, the caller-tool ctx enrichment `enrichSpecToolCtx` shared by
* the roster wrap and the revival spawner, the denial-limit stop seat, and the full-shell reachability predicate.
* The interface is the dependency list the segment had implicitly.
*
* SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await (the one `await` in it sits inside the
* `hostSessionFork` closure a tool calls later), so the phase adds no yield between the run-refs phase before it
* and the caps phase after it (a stretch with no await is extracted as a sync function).
*
* LATE-BOUND SEATS. The closures minted here run long after this phase returns — at spawn time, at tool-call
* time, at suspend time — and several of the facts they read are resolved by LATER stations of the same prepare:
* the caps phase's `runtimeCaps` / fork verdict / observers grant / armed classifier trio, the hands mount's
* resolved face and deny additions, the policy-chain station's hook record and timeout, the wiring station's hook
* env face, the prompt-assembly station's center adoption, the memory phase's engine session. In the driver those
* were `let`s and later `const`s the closures captured by name; here each enters as a GETTER on the Input, and
* every closure reads it exactly where — and only when — the driver's closure read the variable (a head binding
* where the driver read a variable inside one synchronous evaluation; an in-place call where the driver's read
* sat behind a short-circuit). Read-stability of the host handles for the call: {@link RunInternals}
* (`@contract prepare.deps-read-stable`).
*
* Every cell that leaves here as `borrowed-mutable` carries its writer table on the Result member.
*/
import type { AgentHarness, ExecutionEnv } from "../../internal/harness.js";
import type { SubagentWorktreeIsolation } from "../../agents/subagent.js";
import { forkGovernanceDenial } from "../../agents/subagent.js";
import type { SubagentRetainLedger } from "../../agents/retain-ledger.js";
import type { PromptEpochArtifact } from "../../prompt-assembly/artifact.js";
import type { ContainmentFace } from "../../tools/fs/index.js";
import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
import { type OnQuestion } from "../ask-question.js";
import { type ParentSpecSeat } from "../parent-spec-seat.js";
import { type HookEnvCapabilities, type Hooks } from "../hooks.js";
import type { LockedPreflight } from "../locked-config.js";
import type { OwnOrgAdmissionVerdict } from "../memory-admission.js";
import type { DelegationContentSafety } from "../memory-engine/delegation-provenance.js";
import { type SessionPermissionRules } from "../session-policy-store.js";
import type { SessionStore } from "../session.js";
import { type OnAsk } from "../tool-policy.js";
import type { PermissionMode, RunnerDeps, RuntimeCaps, ShellGateDoctrine, TaskEvent, TaskSpec, ToolExecuteContext, ToolSpec } from "../types.js";
import { PermissionModeController } from "../mode-policy.js";
import { type SkillGrantTable } from "../skill-grant.js";
import type { InheritedGate, Prepared, PrepareResume, RunInternals, StopForDenialLimit, ToolFaceSnapshot, WriteFaceSeat } from "./contracts.js";
export interface PrepareInheritedGateInput {
/** borrowed-readonly — the REBOUND spec. Read: `shellGate` (the own doctrine the chain fold tightens), `durableApproval`
* (the mandate tests + the children's copy), `onAsk`/`onQuestion` (the frozen seats), `handsReadOnly` / `interactiveTools`
* / `oneShot` / `clientContext` / `principal` / `additionalDirectories` / `additionalReadDirectories` / `envFacts` /
* `memoryPersistenceCapable` / `getApiKeyAndHeaders` / `backgroundScope` / `enablePlanMode` (the ctx enrichment's
* trusted seats). Never mutated. */
spec: Pick<TaskSpec, "shellGate" | "durableApproval" | "onAsk" | "onQuestion" | "handsReadOnly" | "interactiveTools" | "oneShot" | "clientContext" | "principal" | "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "memoryPersistenceCapable" | "getApiKeyAndHeaders" | "backgroundScope" | "enablePlanMode" | "excludeAllTools">;
/** borrowed-readonly — the seat this run hands its children (`checkpointSeatOf`, minted once by the driver after the config doors): the ctx's `checkpointStoreForChildren`, absent when the run has no seat. */
checkpointSeat: import("../checkpoint-seat.js").CheckpointSeat | undefined;
/** borrowed-readonly — the deployment seats: `autoMode.denialLimit` (the rebuilt trackers' bounds), `memoryScopeAdmission` /
* `deploymentMemoryScopes` / `compliancePostureResolver` (the governed-provenance bit), `onError` (the screening-face crash
* lane), `onNotice` (the stop seat's notice), `executionEnv` (the hook-env identity predicate), `delegationEntryCaps` (resolved
* and validated once here), `rosterStore` / `onBackgroundChildEvent` (ctx seats), `onAsk` / `onQuestion` (the deps half of the
* frozen seats). */
deps: Pick<RunnerDeps, "autoMode" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "compliancePostureResolver" | "onError" | "onNotice" | "executionEnv" | "delegationEntryCaps" | "rosterStore" | "onBackgroundChildEvent" | "onAsk" | "onQuestion">;
/** borrowed-readonly — the trusted spawn-side channel: `inheritedGate` (the LIVE half of the chain fold), `ownOrgAdmissionRef`
* (the same-session continuation's freeze), `delegationProvenance` (the F-012 stamp), and the identity / peer / notification
* seats the ctx enrichment forwards. Never mutated. */
internals: Pick<RunInternals, "inheritedGate" | "sessionModeSeats" | "ownOrgAdmissionRef" | "delegationProvenance" | "memoryCaptureAncestors" | "parentTaskId" | "parentSessionId" | "rootSessionId" | "placementRoot" | "explicitAgentName" | "peerSelfRef" | "peerInboundChainRef" | "onTaskNotification" | "onSubagentSpawn" | "insideFork" | "questionFaceStripped"> | undefined;
/** borrowed-readonly — the resume leg, or undefined on a fresh one: `seed.inheritedGate` is the CHECKPOINT half of the chain
* fold (the trust floor the live half may only tighten), `seed.readFace` the hands-less re-park's carried section. */
resume: Pick<PrepareResume, "seed" | "outcome"> | undefined;
/** borrowed-readonly — the session store's fork face: `fork` / `release` are bound into the ctx's `hostSessionFork` (present
* only when the store can fork). */
sessions: Pick<SessionStore, "fork" | "release">;
/** borrowed-readonly — the acquired session id (the crash lane's context, the ctx seat, the ancestor chain's own row). */
sessionId: string;
/** borrowed-readonly — the run's invocation id (the stop seat's notice names it). */
runId: string;
/** borrowed-readonly — the per-task identity (`ctx.taskId`, the same fallback the runner uses). */
hostTaskId: string;
/** borrowed-readonly — the SETTLED task root (`ctx.parentCwd`). */
taskRootFinal: string;
/** borrowed-readonly — the run's resolved env: the full-shell predicate's kind test and the hook-env identity value. */
executionEnv: ExecutionEnv;
/** borrowed-readonly — the per-task owned env when a factory minted one (the hook-env identity predicate reads presence). */
ownedEnv: ExecutionEnv | undefined;
/** borrowed-readonly — the frozen preflight; only `toolPolicy` (the RESOLVED caller policy the children's chain carries). */
lockedPreflight: Pick<LockedPreflight, "toolPolicy">;
/** borrowed-readonly — the frozen tool-face snapshot (the ctx forwards exclude / defer / alwaysLoad / restoreGated; the
* full-shell predicate reads the exclusion of Bash). */
toolFaceSnapshot: ToolFaceSnapshot;
/** borrowed-readonly — the ctx's `mountsCallerTool` seat: identity membership in the caller's `spec.tools`, pre-gate. */
mountsCallerTool: (tool: ToolSpec) => boolean;
/** borrowed-readonly — the resolved prompt profile (ctx seat). */
promptProfile: ToolExecuteContext["promptProfile"];
/** borrowed-readonly — the resolved interaction posture (ctx seat; absent ⇒ no key). */
resolvedInteractionPosture: ToolExecuteContext["interactionPosture"];
/** borrowed-readonly — the task's own permission-mode key, screened and snapshotted before the first await (undefined =
* declared nothing: a root runs `default`, a child inherits). */
permissionModeSeat: PermissionMode | undefined;
/** borrowed-readonly — whether an execution env is wired at all (the full-shell predicate's first term). */
handsEnabled: boolean;
/** borrowed-readonly — the nested-usage sink the ctx forwards. */
reportUsage: ToolExecuteContext["reportUsage"];
/** borrowed-readonly — the harness seat (the ctx's model / thinking snapshots read it call-time). Not written here. */
harnessRef: {
current?: AgentHarness;
};
/** borrowed-readonly — the per-task skill grant table (the ctx's `activeSkillGrants` capture and the checkpoint section read it call-time). Not written here. */
skillGrants: SkillGrantTable;
/** borrowed-readonly — the request's allow-entry table (the ctx's `activeRequestAllowedTools` capture reads it call-time). Not written here. */
requestAllowedTools: Pick<import("../allow-entry.js").AllowEntryTable, "entries">;
/** borrowed-readonly — the filtered display-forwarding wrapper (ctx seat; absent ⇒ no key). */
forwardEvent: ((e: TaskEvent) => void) | undefined;
/** borrowed-readonly — the retain ledger (ctx seat; absent ⇒ no key). */
subagentRetain: SubagentRetainLedger | undefined;
/** borrowed-readonly — the worktree-isolation helper (ctx seat; absent ⇒ no key). */
worktreeIsolation: SubagentWorktreeIsolation | undefined;
/** borrowed-readonly — the three run-ref primitives the ctx exposes (plan mode only under `enablePlanMode`). */
requestReview: (opts?: {
reason?: string;
}) => void;
/** borrowed-readonly — see `requestReview`. */
requestStopAfterTurn: () => void;
/** borrowed-readonly — see `requestReview`; a steer of the leg's mode seat into `plan`. */
enterPlanMode: () => Promise<void>;
/** borrowed-readonly — the leg's reminder mark (ctx seat). */
reminderMark: string;
/** borrowed-readonly — the leg's disclosure counters (ctx seat; the outlets that bump them are not in this phase). */
reminderDisclosureCounts: Prepared["reminderDisclosureCounts"];
/** borrowed-readonly — the file-history fixed point the ctx threads to delegation tools (only when enabled). */
fileHistoryEnabled: boolean;
/** borrowed-readonly — see `fileHistoryEnabled`. */
historyScope: string;
/** borrowed-readonly — see `fileHistoryEnabled`. */
historyRoot: string;
/** borrowed-readonly — see `fileHistoryEnabled`. */
historyFs: string;
/** borrowed-readonly — the driver's composition of the run's abort (controller + harness), the stop seat's headless arm.
* Composed by the driver because the harness is built after this phase; the seat only ever fires at run time. */
abortRun: () => void;
/** borrowed-readonly — LATE-BOUND: the per-principal entitlements the caps phase resolves after this one; read only inside
* `inheritedGateForChildren` (the two mandate tests), at spawn time. */
runtimeCaps: () => RuntimeCaps | undefined;
/** borrowed-readonly — LATE-BOUND: the resolved hook record (`spec.hooks ?? deps.hooks`), read inside the chain assembly for
* this task's own screening face. */
hooks: () => Hooks | undefined;
/** borrowed-readonly — LATE-BOUND: whether the deployment declared its screening face observational (then it rides no entry). */
preToolUseObservational: () => boolean;
/** borrowed-readonly — LATE-BOUND: the validated per-invocation hook bound the screening-face entry's policy runs under. */
hookTimeoutMs: () => number;
/** borrowed-readonly — LATE-BOUND: the hook-env capability face the screening-face entry's policy resolves paths in. */
hookEnvFace: () => HookEnvCapabilities | undefined;
/** borrowed-readonly — LATE-BOUND: the armed classifier (undefined = not armed), read where the chain freezes it onto both
* entries and where the ctx exposes the spawn-review seat. */
autoModeDecider: () => AutoModeDecider | undefined;
/** borrowed-readonly — LATE-BOUND: the denial tracker built beside the decider; rides wherever the decider rides. */
autoModeDenialTracking: () => AutoModeDenialTracker | undefined;
/** borrowed-readonly — LATE-BOUND: the serializable arming recipe (persistArming opt-in only). */
autoModeArming: () => AutoModeArmingRecipe | undefined;
/** borrowed-readonly — LATE-BOUND: the fork-governance verdict (ctx `forkAccess`, present only when denied). */
agentForkDenial: () => ReturnType<typeof forkGovernanceDenial>;
/** borrowed-readonly — LATE-BOUND: the observers grant (ctx `observersAllowed`, present only when explicitly granted). */
observersActive: () => boolean;
/** borrowed-readonly — LATE-BOUND: the hands mount's resolved read face (undefined until/unless hands mount). */
resolvedReadFace: () => ContainmentFace | undefined;
/** borrowed-readonly — LATE-BOUND: the hands mount's WRITE face seat (the read seat's twin; read per call, and whether its fence is declared). */
resolvedWriteFace: () => WriteFaceSeat | undefined;
/** borrowed-readonly — LATE-BOUND: the normalized deny additions in force (empty until the hands mount resolves them). */
readDenyAdditionsNormalized: () => ReadonlyArray<{
pattern: string;
caseSensitive: boolean;
}>;
/** borrowed-readonly — LATE-BOUND: the hands-less legs' resolved face (the carrier's second source). */
handsLessResolvedFace: () => ContainmentFace | undefined;
/** borrowed-readonly — LATE-BOUND: the hands-less legs' resolved WRITE face (same second source, write seat). */
handsLessResolvedWriteFace: () => WriteFaceSeat | undefined;
/** borrowed-readonly — LATE-BOUND: the adopted center artifact (ctx `centerArtifactDigest` / `centerSourceRevision`). */
centerAdoption: () => {
artifact: PromptEpochArtifact;
sourceRevision?: string;
} | undefined;
/** borrowed-readonly — LATE-BOUND: the memory-engine session (the ctx's capture-floor getters read it at THEIR call time). */
memoryEngineSession: () => Prepared["memoryEngineSession"];
}
export interface PrepareInheritedGateResult {
/** owned — the LIVE half of the inherited chain as supplied (`internals.inheritedGate`); the durable-gate notice reads it. */
liveInheritedGate: RunInternals["inheritedGate"];
/** owned — the CHECKPOINT half (`resume.seed.inheritedGate`); the suspend serializer re-reads its frozen chain. */
seedInheritedGate: PrepareResume["seed"]["inheritedGate"];
/** owned — the rev-monotonic fold of the two halves' ancestor rules (live replaces per (session, principal) only at ≥ rev). */
inheritedAncestorRules: InheritedGate["ancestorRules"];
/** owned — the chain's shell floor (live or seed; the seed's migrated word), before the spec's own is applied. */
inheritedShellGate: InheritedGate["shellGate"];
/** owned — the live parent constraints with a fresh denial tracker attached per rebuilt decider. */
inheritedParentConstraints: NonNullable<InheritedGate["parentConstraints"]>[number][] | undefined;
/** owned — the leg's live permission-mode seat: the spec's request clamped under the chain's ceiling (a parent's live
* seat, or the checkpoint's recorded word), folded against the arming facts the caps phase binds later. Every mode
* consumer reads `current` at its own call; the steer verb writes it. */
permissionMode: PermissionModeController;
/** owned — the leg's commit of its mode seats, called by the driver once prepare succeeded (never on a failed one):
* makes this leg its SESSION's current leg and holds the leg's own engine ceiling until `legSignal` aborts, so each
* move of that ceiling re-judges this leg. */
commitModeSeats: (legSignal: AbortSignal) => void;
/** owned — the leg has STOPPED (the run leg's teardown, after its loop settled): from here its children read the
* session's current leg. Idempotent; a leg that never reaches it keeps answering its children itself. */
endModeCeiling: () => void;
/** owned — the seed ∩ live admitted org scopes (one side absent ⇒ the other; both absent ⇒ undefined). */
inheritedAdmittedOrgScopes: readonly string[] | undefined;
/** owned — the prior own org verdict: checkpoint ∩ same-session ref (either alone stands; neither ⇒ undefined). */
priorOwnOrgVerdict: OwnOrgAdmissionVerdict | undefined;
/** owned — the monotonic governed-provenance bit for this leg. */
orgGovernedProvenance: boolean;
/** owned — the operator's shell floor in force (the spec's, or the chain's); undefined = no floor. */
effectiveShellGate: ShellGateDoctrine | undefined;
/** borrowed-mutable — THIS task's own session-rule snapshot for its children's chain. Writers (R-3): the policy-chain
* station's session-policy read, once per leg, when a store is wired and the task carries its own sessionId. Read by
* `inheritedGateForChildren` at spawn time. */
ownSessionRulesRef: {
current?: {
sessionId: string;
principal?: string;
rev: number;
gen?: number;
rules: SessionPermissionRules;
};
};
/** borrowed-mutable — the ANCESTOR twin of {@link PrepareInheritedGateResult.ownSessionRulesRef}, keyed by
* the entry's POSITION in `inheritedAncestorRules`. Writers: the policy chain's ancestor layers, each time
* one comes to enforce a newer row than it prepared with. Read by {@link ancestorRulesNow}. Position, not
* `(sessionId, principal)`: the chain may legitimately carry two entries of the same identity with
* DIFFERENT floors (a leg whose own session is also an ancestor's, a seed and a live channel that both
* name it), each compiled into its own layer with its own floor — and a by-identity key lets the layer
* that advanced overwrite the entry of the layer that did not, publishing the weaker floor over the
* stronger one. A layer upgrades its OWN entry; position is what "its own" means. */
ancestorSessionRulesRef: {
current: Map<number, {
sessionId: string;
principal?: string;
rev: number;
gen?: number;
rules: SessionPermissionRules;
}>;
};
/** owned — the ancestor rules AS THIS LEG NOW ENFORCES THEM: the inherited entries with the seat above
* overlaid. The ONE projection, called by the child-chain assembly at spawn and by the checkpoint writer
* at suspend — two faces of one rule ("what a later reader inherits is what this leg enforces"), which
* were two implementations while the checkpoint cloned the prepare-time list. */
ancestorRulesNow: () => InheritedGate["ancestorRules"];
/** borrowed-mutable — THIS task's own frozen admitted org scopes for its children's chain. Writers: the memory phase's
* admission door, once per leg. Read by the chain assembly (always emitted, empty = no org plane). */
memoryAdmittedOrgScopesRef: {
current: readonly string[];
};
/** borrowed-mutable — the SESSION-freeze record for the suspend writer (present iff this leg adjudicated an org plane).
* Writers: the memory phase's admission door. Readers: the org stamp predicate here and the suspend serializer. */
ownOrgVerdictRef: {
current: OwnOrgAdmissionVerdict | undefined;
};
/** owned — the org-admission checkpoint stamp predicate (inherited set ∨ own verdict ∨ governed). */
orgAdmissionCheckpointState: () => boolean;
/** owned — the read-face checkpoint section builder (hands-less resume carries the seed; every other leg mints from the live
* resolution; undefined for the default posture). */
faceCheckpointSection: () => {
face: "open" | "roots";
denyEntries?: Array<{
pattern: string;
caseSensitive: boolean;
}>;
} | undefined;
/** owned — the face stamp predicate (`faceCheckpointSection() !== undefined`). */
faceCheckpointState: () => boolean;
/** owned — the WRITE-face checkpoint section builder: written ONLY for a non-default (open) write
* posture, so an absent section IS "roots" on every reader, including an engine that predates the
* seat. No version stamp rides it — dropping the section can only ever make a resume STRICTER
* (the opposite direction from the read section, whose deny additions a blind reader would lose). */
writeFaceCheckpointSection: () => {
face: "open" | "roots";
} | undefined;
/** owned — the SKILL-GRANT checkpoint section builder: the rows this run's table grew (the model's activations, a
* delegated child's capture), read off the live table at mint time; absent when there are none. */
skillGrantCheckpointSection: () => import("../skill-grant.js").SkillGrantRecord[] | undefined;
/** owned — the F-012 stamp predicate (opaque constraints present ∨ seed frozen chain ∨ provenance-armed). */
f012CheckpointState: () => boolean;
/** owned — the FROZEN approver seat (spec over deps) every station of this leg and its children's chain resolve at. */
frozenOnAsk: OnAsk | undefined;
/** owned — the env the screening face sees through `ctx.env` (identity half of a chain entry's `hookEnv`); the policy-chain
* station's pass-through arm compares against it. */
hookEnvSource: unknown;
/** owned — the deployment lane for a screening face that crashes while screening a descendant's call (phase "hook"). */
notifyOwnHookCrash: (err: unknown) => void;
/** owned — the FROZEN question seat (spec over deps), the content-ask twin of `frozenOnAsk`. */
frozenOnQuestion: OnQuestion | undefined;
/** owned — the parent SPEC seat this run hands its children (`parentSpecSeatOf(spec, frozenOnQuestion)`): the ctx seat and the Workflow mount dep are this one value; both lanes apply it with `applyParentSpecSeat` (one law). */
parentSpecSeat: ParentSpecSeat;
/** borrowed-mutable — the delegation runtime-provenance arming face. Writer: the memory-engine-session phase
* (prepareMemoryEngineSession), once per leg when it arms. Read lazily by delegation tools through the ctx. */
provenanceForChildrenRef: {
current?: DelegationContentSafety;
};
/** borrowed-mutable — the delegation-settlement coordinates. Writer: the memory-engine-session phase, once per leg when the
* mounted session carries a settlement seat. Read lazily by delegation tools through the ctx. */
delegationSettlementRef: {
current?: {
controlDir: string;
sessionId: string;
};
};
/** owned — chain assembly SINGLE SITE: upstream chain + this task's own contribution, called by delegation tools at spawn. */
inheritedGateForChildren: () => InheritedGate;
/** owned — the ONE carrier expression both delegation mounts consume (resolved face ?? hands-less face). */
carrierReadFace: () => ContainmentFace | undefined;
/** owned — the same expression on the WRITE seat. */
carrierWriteFace: () => ContainmentFace | undefined;
/** owned — the full shell's structural reachability from spec-time facts. */
fullShellReachable: boolean;
/** borrowed-mutable — the gate's typed stop seat (`Prepared.gateStopRef`). Writers: `stopForDenialLimit` only. */
gateStopRef: Prepared["gateStopRef"];
/** owned — the ONE closure that fills the stop seat (the denial-limit fallback's headless arm). */
stopForDenialLimit: StopForDenialLimit;
/** owned — the caller-tool ctx enrichment shared by the roster wrap and the revival spawner. */
enrichSpecToolCtx: (ctx: ToolExecuteContext) => ToolExecuteContext;
}
/** The M3b phase body — prepareTask's inherited-gate stretch, verbatim (see the module header). */
export declare function prepareInheritedGate(input: PrepareInheritedGateInput): PrepareInheritedGateResult;