UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

327 lines (326 loc) • 25.5 kB
/** * 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;