UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

226 lines (225 loc) • 16.6 kB
/** * PrepareTask's CAPS-AND-WORKFLOW phase, verbatim from the driver: the caller-tool roster * map (the defineTool-product branch and the rich-ctx wrap through the large-result wrapper), the ReportBlocked / * ReportFindings / plan-pair mounts, the per-principal runtime caps resolve with its fail-closed degrade and value * screen, the compliance posture veto with its two refusal codes, the fork verdict and the observers grant, the * auto-mode arming (classifier model, lane rule, decider + denial tracker + persisted recipe) with its manifest * reason, the self-orchestration gate with its governance note, and the Workflow mount with its size-guideline * seat. The three arming helpers and the parent-capture-state join moved with the segment (only this phase calls * them). The interface is the dependency list the segment had implicitly. * * ASYNC PHASE: the segment awaits the two resolvers, the classifier route pre-flight and the Workflow factory, * exactly where the driver awaited them; the roster map and the synthetic mounts before the first of those awaits * run synchronously as they did. Read-stability of the host handles for the call: {@link RunInternals} * (`@contract prepare.deps-read-stable`). * * THE ROSTER IS MINTED HERE (`tools`): every later phase that mounts pushes into this same array — the writer table * lives on {@link Prepared.tools}. LATE-BOUND SEATS: the Workflow mount's call-time getters read * three facts later stations resolve (the memory session, the center adoption, the hands mount's deny additions); * each enters as a getter on the Input and is read exactly where the driver's getter read the variable. */ import type { AgentHarness, AgentTool, ThinkingLevel } from "../../internal/harness.js"; import type { Model } from "../../internal/llm.js"; import { forkGovernanceDenial } from "../../agents/subagent.js"; import type { PromptArtifact } from "../../prompt-assembly/artifact.js"; import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js"; import { type AutoModeArmingRecipe } from "../auto-mode-arming.js"; import { type PermissionModeController } from "../mode-policy.js"; import { type AutoModeDecider, type AutoModeDenialTracker } from "../auto-mode.js"; import { type CheckpointSeat } from "../checkpoint-seat.js"; import { type ComplianceCapability } from "../compliance.js"; import type { LockedPreflight } from "../locked-config.js"; import type { StoredSession } from "../session.js"; import type { RunnerDeps, RuntimeCaps, TaskEvent, ToolExecuteContext } from "../types.js"; import type { AutoModeArmFact } from "../wiring-manifest.js"; import type { AssertAllKeysHandled } from "../ask-origin.js"; import { type ChildClampSource } from "../child-clamp-seats.js"; import type { InheritedGate, Prepared, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js"; import { type BlockedRef } from "./synthetic-tools.js"; import { RosterBuilder } from "../tool-roster.js"; import type { ParentSpecSeat } from "../parent-spec-seat.js"; import { type RosterSpec } from "./caller-roster.js"; export interface PrepareCapsAndWorkflowInput { /** borrowed-readonly — the REBOUND spec, whole: the roster map reads `tools`; the mounts read `enableBlockedReport`, * `enablePlanMode`, `interactiveTools`; the caps/compliance stations read `principal`, `selfOrchestration`, * `enableFork`, `getApiKeyAndHeaders`; the Workflow mount forwards `handsReadOnly`, `interactiveTools`. The * self-orchestration predicate takes the whole spec. Never mutated. Typed as a spec whose roster is snapshot faces. */ spec: RosterSpec; /** borrowed-readonly — the deployment seats, whole: `runtimeCapsResolver`, `compliancePostureResolver`, `onError`, `onNotice`, * `autoMode`, `brain`, `models`, and the Workflow mount's dozen deps (`workflowScriptRunner`, * `workflowGovernanceBaseline`, `workflowRunStore`, `workflowJournalStore`, `workflowScriptStore`, * `onWorkflowAgentSpawn`, `workflowCompletionNotifier`, `workflowLimits`, `agents`, `builtinAgents`, `onBackgroundChildEvent`). * The self-orchestration predicate takes the whole record. */ deps: RunnerDeps; /** borrowed-readonly — the seat this run hands its children (`checkpointSeatOf`, minted once by the driver right after the * config doors): the plan-pair face reads the store it names, the Workflow mount hands the seat itself to its children. */ checkpointSeat: CheckpointSeat | undefined; /** borrowed-readonly — the trusted spawn-side channel; the Workflow mount forwards `rootSessionId`, `placementRoot`, * `onTaskNotification`, `workflowDepth`, the capture-floor getter reads `memoryCaptureAncestors`, and the arming reads * the chain's auto-mode face (`inheritedGate.autoModeFace`). */ internals: Pick<RunInternals, "rootSessionId" | "placementRoot" | "onTaskNotification" | "workflowDepth" | "memoryCaptureAncestors" | "workflowParkedResume" | "questionFaceStripped" | "inheritedGate"> | undefined; /** borrowed-readonly — the acquired session; the classifier leg rebuilds its transcript window from `buildContext`. */ session: Pick<StoredSession, "buildContext">; /** borrowed-readonly — the acquired session id (operator-line context, the refusal codes' phase record). */ sessionId: string; /** borrowed-readonly — the run's invocation id (the classifier route pre-flight's notice names it). */ runId: string; /** borrowed-readonly — the per-task identity the Workflow mount registers its children under. */ hostTaskId: string; /** borrowed-readonly — the registry scope the Workflow mount registers in. */ taskScope: string; /** borrowed-readonly — the SETTLED task root the Workflow mount hands its children as `parentCwd`. */ taskRootFinal: string; /** borrowed-readonly — the run's resolved model: the classifier leg's fallback and the route pre-flight's primary. */ model: Model; /** borrowed-readonly — the run's resolved thinking level, when one is (the Workflow mount's parentThinking fallback). */ thinking: ThinkingLevel | undefined; /** borrowed-readonly — the frozen preflight; only `mcp` (the compliance veto's mcp_servers conflict check). */ lockedPreflight: Pick<LockedPreflight, "mcp">; /** borrowed-mutable — the effect collection (`Prepared.toolEffects`). Writer here: ONE set, `Workflow → write`, at its mount. * Every other writer is the safety scan (the seed) and the later mounts (see the roster table on `Prepared.tools`). */ toolEffects: Prepared["toolEffects"]; /** borrowed-readonly — the frozen tool-face snapshot the Workflow mount forwards (exclude / defer / alwaysLoad / restoreGated). */ toolFaceSnapshot: ToolFaceSnapshot; /** borrowed-readonly — the resolved prompt profile (the Workflow mount's parentPromptProfile). */ promptProfile: ToolExecuteContext["promptProfile"]; /** borrowed-readonly — the resolved interaction posture (the Workflow mount's parentInteractionPosture; absent ⇒ no key). */ resolvedInteractionPosture: ToolExecuteContext["interactionPosture"]; /** borrowed-readonly — the harness seat; the Workflow mount's parentModel / parentThinking getters read it call-time. */ harnessRef: { current?: AgentHarness; }; /** borrowed-readonly — the run's approval seat as the config doors froze it (the Workflow mount's parentOnAsk). */ askSeat: import("../wiring-manifest.js").AskSeat; /** borrowed-readonly — the parent SPEC seat the children inherit (minted once by the inherited-gate phase; the Workflow mount's dep, the same value the delegation ctx carries). */ parentSpecSeat: ParentSpecSeat; /** borrowed-readonly — the run's clamp source (minted once by the inherited-gate phase; the Workflow mount's `parentClamps`, the same source the delegation ctx carries). */ runClampSource: () => ChildClampSource; /** borrowed-readonly — the filtered display-forwarding wrapper (the Workflow mount's forwardEvent; absent ⇒ no key). */ forwardEvent: ((e: TaskEvent) => void) | undefined; /** borrowed-readonly — the child-chain assembly (the Workflow mount's inheritedGateForChildren). */ inheritedGateForChildren: () => InheritedGate; /** borrowed-readonly — the caller-tool ctx enrichment the roster map wraps every raw ToolSpec with. */ enrichSpecToolCtx: (ctx: ToolExecuteContext) => ToolExecuteContext; /** borrowed-readonly — the large-result wrapper every roster entry passes through (per-tool policy honored inside). */ maybeOffload: (tool: AgentTool, perTool?: { offload?: boolean; offloadThresholdChars?: number; }) => AgentTool; /** borrowed-readonly — the plan-review primitive the ExitPlanMode tool calls. */ requestReview: (opts?: { reason?: string; }) => void; /** borrowed-readonly — the plan-mode primitive the EnterPlanMode tool calls: a steer of the leg's mode seat into `plan`. */ enterPlanMode: () => Promise<void>; /** borrowed-readonly — the leg's live permission-mode seat: the arming's first arm is `declared === "auto"`, and the * arming facts (the deny bits, the face, the re-entrant arm) are bound onto it here. */ permissionMode: PermissionModeController; /** borrowed-readonly — LATE-BOUND: the effective read-deny table the hands phase compiles (the rows the classifier's deny * paragraph renders); read at ARMING, which the driver runs after that phase — never at this phase's own call — once the * run's newest tables are compiled (an arming is no call). */ readDenyEntries: () => Promise<{ readonly entries: ReadonlyArray<{ readonly pattern: string; readonly caseSensitive?: boolean; }>; readonly ruleRows: ReadonlyArray<{ readonly tool: string; readonly pattern: string; }>; }>; /** borrowed-readonly — the peer lane's mount verdict (the classifier's cross-session rule fills only while mounted). */ peerLaneActive: boolean; /** borrowed-readonly — the leg's effective delegation fact (`effectiveDelegationFacts`): the classifier window shows its user lane as the launching agent's. */ delegated: boolean; /** borrowed-mutable — `Prepared.sideCallBookRef`: written by the run body (never here); the classifier books each of its replies through it. */ sideCallBookRef: Prepared["sideCallBookRef"]; /** borrowed-readonly — whether the BUILT-IN SendMessage is on this face (the lane rule's second term). */ peerSendMessageBuiltIn: boolean; /** borrowed-readonly — the trusted Runner self-reference the Workflow mount executes children through; undefined when * prepareTask runs standalone (then Workflow is simply not mounted). */ runnerSelf: RunnerSelfSeat | undefined; /** borrowed-readonly — LATE-BOUND: the memory-engine session; the Workflow mount's capture-floor getter reads it at ITS call time. */ memoryEngineSession: () => Prepared["memoryEngineSession"]; /** borrowed-readonly — LATE-BOUND: the adopted center artifact; the Workflow mount's two center getters read it at THEIR call time. */ centerAdoption: () => { artifact: PromptArtifact; sourceRevision?: string; } | undefined; } export interface PrepareCapsAndWorkflowResult { /** owned — THE run's roster BUILDER (the mount array is private to it; `Prepared.tools` is its read face, * whose JSDoc carries the writer table). Minted here from the caller's tools; every later mount goes through its verbs. * Writers here: the caller mounts, then the ReportBlocked, ReportFindings, ExitPlanMode, EnterPlanMode and Workflow mounts. */ roster: RosterBuilder; /** borrowed-mutable — the ReportBlocked tool's write seat (`Prepared.blockedRef`). Writer: the tool at run time; read at * result assembly. */ blockedRef: BlockedRef; /** owned — the per-principal entitlements as resolved (or the fail-closed degrade), undefined = no restriction. */ runtimeCaps: RuntimeCaps | undefined; /** owned — whether the caps resolve FAULTED (throw or value screen) — never read off a coined explicit value. */ runtimeCapsFaulted: boolean; /** owned — the compliance deny set (every capability under a resolver fault). */ complianceDenies: ReadonlySet<ComplianceCapability>; /** owned — whether the compliance resolve faulted (the refusal code distinguishes it). */ complianceDegraded: boolean; /** owned — the fork-governance verdict (present only when denied; the ctx reads it call-time). */ agentForkDenial: ReturnType<typeof forkGovernanceDenial>; /** owned — the observers grant (`allowObservers === true`, explicit opt-in). */ observersActive: boolean; /** owned — the arming outcome as a closed reason (the manifest's auto-mode face), read LIVE: a leg steered into `auto` * after prepare arms then, and the fact follows. */ autoModeArmReason: () => AutoModeArmFact; /** owned — the armed classifier's live seat (`current` undefined until an arming succeeds — at prepare for an `auto` * leg, or at a steer into `auto`); every consumer reads `current` at its own call. */ autoModeDeciderRef: { readonly current: AutoModeDecider | undefined; }; /** owned — the denial tracker built beside the decider (same face, same arming), same live seat. */ autoModeDenialTrackingRef: { readonly current: AutoModeDenialTracker | undefined; }; /** owned — the serializable arming recipe (persistArming opt-in only), same live seat. */ autoModeArmingRef: { readonly current: AutoModeArmingRecipe | undefined; }; /** owned — arm the classifier for THIS leg (idempotent; a no-op unless the effective mode is `auto` and the face and the * deny bit admit it). The driver calls it once after the hands phase (the deny table must exist first); the mode seat * calls it again on a steer into `auto`. */ armAutoMode: () => Promise<void>; /** owned — self-orchestration is ACTIVE for this leg (deployment ready ∧ not entitlement-denied); drives the prompt injection. */ selfOrchestrationActive: boolean; /** owned — the Workflow tool is on the roster (the delegation surface's second door). */ workflowToolsActive: boolean; /** owned — the mid-session size-guideline seat, armed with the Workflow mount (`Prepared.workflowSizeGuideline`). */ workflowSizeGuideline: { legGuideline: WorkflowSizeGuideline; current: () => WorkflowSizeGuideline; } | undefined; } /** * The resolved-caps value screen (the caps-side twin of `refuseInvalidAutoModeSeat`): the arming arm reads * `autoMode !== false`, so a resolver that returns a MARSHALLED value — a non-boolean member (`"false"`, * `0`, `null`) or a non-record container (`false`, `"false"`, `[]`; the resolver is an HTTP-backed seam the * type system does not validate at runtime) — would pass a value meant as a denial straight into arming. * Either shape is treated as a disclosed resolver fault: announced through onError (phase config), and * coined fail-closed — a bad MEMBER to `autoMode:false` (the rest of the record kept), a bad CONTAINER to * the same degrade object the resolver-throw arm uses (every `!== false`-gated cap denied), with * `faulted` raised so the posture-keyed consumers read it as an outage, not as a verdict. `undefined`, or * a record whose `autoMode` is `undefined`/`true`/`false`, passes untouched (byte-identical). */ /** The supported capability keys, fenced total over {@link RuntimeCaps}: a key added to the type without a row here * is a red build, so the snapshot below can never silently drop a new restriction. */ declare const RUNTIME_CAP_KEYS: { readonly allowWorkflows: true; readonly allowFork: true; readonly allowObservers: true; readonly forceDurableGate: true; readonly autoMode: true; readonly autoModeDenySource: true; readonly bypassPermissions: true; readonly allowMemoryOptOut: true; }; export type RuntimeCapKeysCoverEveryCap = AssertAllKeysHandled<Exclude<keyof RuntimeCaps, keyof typeof RUNTIME_CAP_KEYS>>; /** The M4 phase body — prepareTask's caps-and-workflow stretch, verbatim (see the module header). */ export declare function prepareCapsAndWorkflow(input: PrepareCapsAndWorkflowInput): Promise<PrepareCapsAndWorkflowResult>; export {};