@sema-agent/core
Version:
Stateless, task-oriented AI agent core
382 lines (381 loc) • 28.5 kB
TypeScript
/**
* design/98 §3.1 (S8c) — the `run_workflow` TOOL an LLM calls to author + run its own workflow. Mounted ONLY
* when self-orchestration is active (design/98 §1.2: opt-in + a HARD `WorkflowScriptRunner` + a governance
* baseline). Params are ONLY `{ script, args }` — it NEVER accepts `depth` (the nesting depth is a trusted
* internal channel, design/98 §0.1). `effect: "write"` (spawning agents is an irreversible side effect → it
* passes the design/37 policy gate; each spawned agent then passes its OWN governed gate).
*
* Flow: size-cap the script → statically validate `meta` (no eval) → `startWorkflow` with the GOVERNED
* primitives + hard caps → return `{ runId }` IMMEDIATELY → on completion, notify the originator via the
* {@link WorkflowCompletionNotifier} seam.
*/
import type { AgentTool } from "../internal/harness-types.js";
import type { Model } from "../internal/llm.js";
import type { RunnerSelfSeat } from "../core/runner/contracts.js";
import type { WorkflowGovernanceBaseline } from "../core/types.js";
import type { WorkflowRunStore } from "../core/workflow-run-store.js";
import type { WorkflowJournalStore } from "../core/workflow-journal-store.js";
import type { TaskRegistry } from "../core/task-registry.js";
import type { TaskNotificationPayload } from "../core/task-notification.js";
import type { WorkflowAgentHandle } from "./workflow.js";
import type { WorkflowScriptRunner } from "./workflow-script-runner.js";
import { type NamedWorkflowListing, type WorkflowScriptStore } from "./workflow-script-store.js";
import { type WorkflowSizeGuideline } from "./workflow-size-guideline.js";
/** Reserved name of the injected self-orchestration tool. Audit 2026-07-07: CC's primary
* name is `Workflow` with `RunWorkflow` as the alias — CC-trained prompts address "Workflow", so the
* primary flipped to match. #181-F2 订正: since RB-476-A retired alias resolution, `RunWorkflow`/
* `run_workflow` are NOT callable — they live in RETIRED_TOOL_NAMES as validation data only (a call
* under either gets a loud roster miss), so every model-visible mention must use THIS constant. */
export declare const RUN_WORKFLOW_TOOL_NAME = "Workflow";
/**
* design/98 §D.5 — the seam the deployment implements to RE-INVOKE the originating LLM when its workflow
* finishes. Core calls `notify` once per run (at most once — `runId` dedup) with a REDACTED, BOUNDED summary;
* the service routes it back through its task-notification path (cross-replica via the WorkflowRunStore).
*/
export interface WorkflowCompletionNotifier {
notify(input: {
runId: string;
task_id?: string;
task_type?: "workflow";
toolUseId?: string;
sourceTaskId?: string;
/** The ORIGINATING session id (the run that spawned this workflow), threaded from the
* Runner so a deployment's completion inbox resolves the target session lookup-free. */
originatingSessionId?: string;
principal?: string;
status: "completed" | "failed";
/** Redacted + length-bounded — never the host's internal paths/tokens. */
summary: string;
result?: string;
usage?: unknown;
/** The diagnosis route (per-agent rows / journal locator / resumeFromRunId guidance).
* Added 1.353 on the payload but silently dropped at this hand-written subset seam until 1.404 —
* keep this interface in lockstep with the payload fields the settle legs mint. */
diagnostics?: string;
/** See {@link "../core/task-notification.js".TaskNotificationPayload.completionId}.
* This CROSS-REPLICA seam is exactly one of the read faces the cross-channel correlation contract
* promises the same value on — added here in the same car specifically so this interface does not
* repeat the diagnostics field's own drift history (the comment right above this one). */
completionId?: string;
}): Promise<void> | void;
/**
* Poll-then-also-notify dedup: the ORIGINATING session was served this run's TERMINAL
* state IN-BAND (a `TaskOutput` poll through the task registry returned a non-running snapshot) —
* the deployment should ack/drop any still-pending completion-inbox entry for (originatingSessionId,
* runId), so a later stream-open doesn't re-deliver a `workflow_complete` the model already
* consumed. Fired at most once per run, AFTER `notify` may already have enqueued (the whole point:
* the poll usually wins the race in-turn). Optional; errors are swallowed. The shell-side same-process
* seed dedup remains the belt — this is the durable/cross-restart half.
*/
ackServed?(input: {
runId: string;
originatingSessionId?: string;
sourceTaskId?: string;
principal?: string;
}): Promise<void> | void;
}
/** design/98 §D.6 — the deployment's hard CEILINGS for an LLM-authored workflow (the tool forces them; a
* script may only tighten). All optional; sensible defaults bound a runaway script. */
export interface WorkflowLimits {
/** Max script source length (chars). Default 100_000. */
maxScriptChars?: number;
/** Max cumulative agents. Default 50. */
maxAgents?: number;
/** Whole-workflow wall-clock cap (ms). Default 600_000 (10 min). */
totalTimeoutMs?: number;
/** design/164 — per-child wall-clock ceiling (ms). No default: unset means the children are not
* wall-clock bounded (state a ceiling to get one). */
perAgentMaxWalltimeMs?: number;
/** Per-child cost ceiling (USD). Forced onto every child. */
childMaxCostUsd?: number;
/** Per-child token ceiling. Forced onto every child. */
childMaxTokens?: number;
/** Per-child turn ceiling. */
childMaxTurns?: number;
/** Workflow token budget (`ctx.agent` throws once reached). */
budget?: number;
/** Max returned-result size (chars). Default 100_000. */
maxResultChars?: number;
/** Max single log-line length (chars). Default 10_000. */
maxLogChars?: number;
/**
* CC 2.1.202+ config key `workflowSizeGuideline`: ADVISORY workflow size guidance
* (NOT a cap — the hard ceiling stays `maxAgents`). small/medium/large append CC's verbatim
* "keep workflows under N agents … guideline, not a hard limit" section to the Workflow tool card
* (matching observed CC behavior); an EXPLICIT "unrestricted" injects nothing, while unset resolves
* to the medium DEFAULT arm (RB-339 rebaseline — see workflow-size-guideline.ts).
* Lives on the limits object so it rides the EXISTING `RunnerDeps.workflowLimits` threading —
* deployments configure it as `workflowLimits: { sizeGuideline }` with no new prepare-task seam.
* `RunWorkflowToolDeps.sizeGuideline` (direct construction) takes precedence when both are set.
*/
sizeGuideline?: WorkflowSizeGuideline;
}
/**
* design/140 §6 1b — pick the LLM-facing guidance text for one named workflow: `whenToUse` when declared,
* else the description (the agents-side `agentWhenToUseText`/CC `tIl` selection shape, workflows arm —
* workflows have no lean variant, so the fallback chain is just whenToUse → description).
*/
export declare function workflowWhenToUseText(m: {
description?: string;
whenToUse?: string;
}): string | undefined;
/**
* design/140 §6 1b — render the named-workflow roster block appended to the Workflow tool card (the
* consumption face `meta.whenToUse` previously lacked). Line shape mirrors the agent roster (CC `tIl`,
* subagent.ts precedent): `- name: whenToUse-or-description`. Returns undefined when nothing is registered
* (the card stays byte-identical to the pre-140 form).
*/
export declare function renderNamedWorkflowListing(entries: ReadonlyArray<NamedWorkflowListing>): string | undefined;
export interface RunWorkflowToolDeps {
/** The Runner that executes child tasks (the workflow's `ctx.agent` → `runner.runTask`). */
runner: RunnerSelfSeat;
/** The HARD sandbox (asserted `safeForUntrustedScripts:true`). */
scriptRunner: WorkflowScriptRunner;
/** Deployment-trusted governance every spawned agent inherits (design/98 §2.5). */
governanceBaseline: WorkflowGovernanceBaseline;
/** (tool-face round 5): the HOST task's tool selector layers (prepare-time) — what the host may use bounds every agent this
* workflow spawns, exactly like the Agent path: threaded into each spawned agent's trusted internals as its ceiling. */
parentToolCeiling?: readonly import("../core/tool-face.js").ToolSelectorLayer[];
/** The HOST task's deferred-disclosure list (prepare-time snapshot), merged (union) into the baseline base for every child spec. */
parentDeferTools?: readonly string[];
/** DD-3 (RB-422): the defer face's subtract valve rides the same snapshot — a pin the operator
* declared for the tree must survive this direct-mount lane exactly like the deferral it exempts
* from. Union (widen-the-exemption never tightens the child beyond the parent's own face). */
parentAlwaysLoadTools?: readonly string[];
/** design/277: the HOST task's model-gate restore selector
* ({@link import("../core/types.js").TaskSpec.restoreGatedTools}, prepare-time frozen snapshot) —
* the workflow lane is a delegation lane too, so the selector inherits here exactly like the
* three tool-face controls above (union with the baseline's own; `true` on either side wins).
* Without this seat, a restored parent's workflow child on the same strong model lost the
* scaffold nobody chose to drop — the exact tree inconsistency design/277 §3.4 forbids. Only a
* loosening toward tools the DEPLOYMENT's own baseline composes (the script cannot name this
* field; excludeTools still wins downstream). */
parentRestoreGatedTools?: readonly string[] | true;
/** R2 双形轴 — the HOST task's resolved prompt profile, inherited by every workflow-spawned child
* (base spec seat; the child's own explicit promptProfile would win in prepare, but workflow
* scripts cannot name this field, so in practice the tree speaks the host's profile). */
parentPromptProfile?: "simple" | "classic";
/** Model catalog (for the name→Model allowlist resolution). */
models?: Record<string, Model>;
/** F4 agentType registry: deployment agent definitions `agent(…, {agentType})` resolves (SHADOW over
* built-in Explore/Plan; `builtinAgents:false` removes the built-ins). Same registry the Agent tool uses. */
agents?: import("../core/types.js").AgentDefinition[];
builtinAgents?: boolean;
/** Optional run store for cross-replica `/workflows` observability (opt-in). */
store?: WorkflowRunStore;
/** design/97 CORE-9 (Part A) — optional LOAD-BEARING resume journal: records each agent result so a
* `resumeFromRunId` re-run replays the unchanged prefix (durable cross-replica resume). Opt-in. */
journalStore?: WorkflowJournalStore;
/** design/149: the process-level BackgroundChildEvent sink — workflow-spawned agents
* mint synthetic `wa*` rows (spawn/tick/terminal) on the SAME observer bg subagents use
* (RunnerDeps.onBackgroundChildEvent), immune to any run-leg forwarding freeze. Opt-in. */
onBackgroundChildEvent?: (event: import("../core/types.js").BackgroundChildEvent) => void;
/** design/97 CORE-9 (Part B) — optional steerable-handle sink. When set, the script's `agent()` primitive runs
* each agent STEERABLE and emits its {@link WorkflowAgentHandle} here (the script never sees the handle); the
* deployment registers it by runId+label to route a human/cross-replica steer. Unset ⇒ non-steerable. Opt-in. */
onAgentSpawn?: (handle: WorkflowAgentHandle) => void;
/** Tenant/grouping key for the run. */
scope?: string;
/** Completion re-invoke seam. */
notifier?: WorkflowCompletionNotifier;
/** The originating session id, threaded into every completion notify. */
originatingSessionId?: string;
/** The ROOT host session of the delegation tree (prepare-task passes
* `internals?.rootSessionId ?? sessionId` per task). The AUTO-MOUNTED tool's execute ctx is the
* harness-native one (no internals enrichment — that wrapper covers spec.tools only), so the
* root MUST ride deps; `ctx.rootSessionId` stays first for a deployment-composed mount that
* does get the enriched ctx. */
rootSessionId?: string;
/** design/380 O1② (C12) — the host run's EXPLICIT placement fixed point
* (`RunInternals.placementRoot`), riding deps for the same auto-mounted-ctx reason as
* `rootSessionId` above; threaded into the workflow run so every workflow-spawned agent keeps
* the ladder/gate placement. Absent ⇒ nothing extra travels (the rootSessionId chain is the
* placement root through prepare's mint middle segment). */
placementRoot?: string;
/** Process-local unified task registry. When present, RunWorkflow returns `task_id === runId` with a `w*` id. */
taskRegistry?: TaskRegistry;
/** design/115 P2 core slice: run-local task-notification sink for SDK event + live model XML injection.
* design/373 §3.7: the sink accepts the injection tier — workflow terminals declare `"next"`
* explicitly (a completion must reach a busy model at the boundary; the parameterless funnel
* default is the EXTERNAL lane's `"later"` and no internal lane may lean on it). */
taskNotification?: (notification: TaskNotificationPayload, opts?: {
priority?: "now" | "next" | "later";
}) => void;
/** Runner-owned owner fallback for registry access when the execute context is unavailable. */
taskOwner?: string;
/** Hard ceilings. */
limits?: WorkflowLimits;
/** B5/F2 (CC scriptPath/name surface): optional script-persistence seam. When wired, EVERY invocation's
* resolved script is persisted (best-effort) and its path returned in the tool result; `scriptPath`
* re-runs a persisted file and `name` resolves a saved workflow. Absent ⇒ inline `script` only. */
scriptStore?: WorkflowScriptStore;
/** CC `workflowSizeGuideline` — advisory size guideline appended to the tool card.
* Direct-construction face; wins over `limits.sizeGuideline` (the RunnerDeps-reachable channel).
* See {@link WorkflowLimits.sizeGuideline}. */
sizeGuideline?: WorkflowSizeGuideline;
/** The originating task id + principal, threaded to the notifier. */
sourceTaskId?: string;
/** The HOST run's parent SPEC seat ({@link import("../core/parent-spec-seat.js").ParentSpecSeat}: principal / oneShot /
* clientContext / the frozen question face / the fs-fence widening / envFacts / the declared persistence statement),
* minted once by the host's prepare and threaded as a MOUNT dep because this auto-mount's execute ctx is minimal
* (`ctx.parentSpecSeat` wins when a wrapping path provides it). Applied onto every spawned agent at the launch site
* (`RunWorkflowOptions.parentSpecSeat` → `applyParentSpecSeat`, the Task lane's one law); the tool also reads its
* `principal` for the registry/notify scope and its `oneShot` for the launch receipt's guidance branch (a one-shot
* caller has no later turn for a notification to land in, so "end your turn and wait" is wrong for it). */
parentSpecSeat?: import("../core/parent-spec-seat.js").ParentSpecSeat;
/** The HOST run's own per-request question face was already stripped by the engine (`RunInternals.questionFaceStripped`
* of the host leg): the fact inherits onto every agent this workflow spawns (`ctx.questionFaceStripped` wins on a rich-ctx
* mount; the dep covers the auto-mount), beside the strip this tool performs on the seat itself. */
parentQuestionFaceStripped?: true;
/** design/173 §8.3 (r3-F1) — the HOST run's RESOLVED interaction posture, threaded as a MOUNT
* dep because prepare-task's auto-mount seals this tool without the rich-ctx wrapper (the same
* reason `forwardEvent`/`inheritedGateForChildren` ride deps); `ctx.interactionPosture` still
* wins when a wrapping path provides it. */
parentInteractionPosture?: "interactive" | "headless";
/**
* design/383 §2.5 (rescan post-6.0.0-RC) — the HOST session's live capture opt-out state,
* threaded as a MOUNT dep for the same unenriched-auto-mount reason as the posture seat above.
* A call-time GETTER on purpose (`ctx.memoryCaptureOptedOut` is live — a mid-run flip must
* floor agents the workflow spawns after it); the enriched-ctx getters win when a wrapping
* mount provides them. Rides `RunWorkflowOptions.parentMemoryCaptureState` into both spawn legs.
*/
parentMemoryCaptureState?: () => {
optedOut: boolean;
indeterminate: boolean;
controlDir?: string;
ancestors?: ReadonlyArray<{
sessionId: string;
controlDir?: string;
}>;
} | Promise<{
optedOut: boolean;
indeterminate: boolean;
controlDir?: string;
ancestors?: ReadonlyArray<{
sessionId: string;
controlDir?: string;
}>;
}>;
/** TRUSTED nesting depth from the run's internals (NOT a tool param) — passed to `startWorkflow` so a
* cross-process child workflow is rejected by the one-level guard. */
workflowDepth?: number;
/** The HOST task's effective working root — threaded into every spawned agent's trusted internals so a
* TOC env factory can root the child at the parent's cwd (CC parity, 2026-07-03). */
parentCwd?: string;
/** Call-time getter for the level a spawned agent runs at when neither the script, the agentType
* definition nor the governance baseline set `thinking` (the mount folds the deployment's STATED
* `roles.subagent.thinking` over the HOST task's current level — the child thinking seat's lower rungs).
* Threaded to the workflow as `defaultThinking` and applied at the launch site AFTER the agentType fold,
* the `parentModel` → `defaultModel` companion. */
parentThinking?: () => import("../core/types.js").TaskSpec["thinking"];
/** Call-time getter for the HOST task's tighten-only clamps — every clamp-carrier row of the clamp table as the host
* holds it (resolved faces, the effective deny list, the approval declaration, the write-hands and hard-headless
* clamps), read when each workflow child spawns and folded stricter-wins with a rich-ctx mount's own seats into every
* spawned child through the one carrier (only the restricting value of each seat travels). The auto-mount's minimal
* ctx makes this dep the host's only carrier. Absent ⇒ the ctx seats alone. */
parentClamps?: () => import("../core/child-clamp-seats.js").ChildClampSource;
/** Call-time getter for the HOST task's RESOLVED model AND the word that chose it: a spawned agent whose
* fold chain produced no model inherits the object on its spec and the word on its trusted channel
* (the subagent lane's `ctx.model` / `ctx.modelRef` pair). Word-less ⇒ the object alone, i.e. a pin. */
parentModel?: () => import("../agents/child-model-seat.js").InheritedModelPick | undefined;
/** The HOST task's per-model auth hook: the CREDENTIAL half of model
* inheritance. The Model object fixed the URL routing but the child brain still authenticated
* with the boot env token (rerun: 7/7 kimi-route 401s with the qwen key). The subagent lane
* has inherited this hook since; the workflow lane never did — the governance whitelist
* rightly blocks SCRIPTS from setting it, but host inheritance is a different lane. */
parentGetApiKeyAndHeaders?: import("../core/types.js").TaskSpec["getApiKeyAndHeaders"];
/** backlog #342 — the HOST task's frozen effective approver (`spec.onAsk ?? deps.onAsk`, the exact
* value the host's own gate resolves an `ask` at — see
* {@link import("../core/types.js").ToolExecuteContext.onAsk}). The workflow lane is a delegation
* lane too (the subagent lane has forwarded this seat since the 2026-08-04 ruling), and it was the
* one lane that never carried it: a deployment that wires its approver per TASK had every workflow
* child's own ask resolve at the headless auto-deny while a live operator sat attached to the host.
* A dep for the same reason as `forwardEvent`/`parentGetApiKeyAndHeaders`: the auto-mounted tool's
* execute ctx is minimal `{toolCallId, signal}`, so a ctx-only read was dead on the one lane every
* Runner deployment uses; `ctx.onAsk` still wins when a wrapping path provides it. The governance
* whitelist rightly blocks SCRIPTS from naming `onAsk` — host inheritance is a different, TRUSTED
* lane (the `parentGetApiKeyAndHeaders` precedent): it rides the governed baseline's BASE
* slot at execute time (see the fold in `execute`), and only when the deployment's own baseline
* does not pin the seat — a deployment-pinned `base.onAsk` (function or blanket) always wins.
* Absent on both seats ⇒ byte-identical baseline: a child's asks keep the pre-#342 resolution
* (`RunnerDeps.onAsk`, else the fail-closed headless auto-deny). */
parentOnAsk?: import("../core/tool-policy.js").OnAsk;
/** #642 — the HOST run's `durableApproval` opt-in (value copy; the Runner mints it from `spec.durableApproval`
* beside `parentOnAsk`, the same seat the delegation tool reads as `ctx.durableApprovalForChildren`). Folded
* into `RunWorkflowOptions.defaultDurableApproval` at execute — ONLY when a `store` is wired (the wa* row's
* durable home; without it the child keeps the pre-#642 lifecycle: its unavailable ask denies fail-closed
* and the `delegation.ask_unresolvable` notice says so). `ctx.durableApprovalForChildren` wins when a
* wrapping path provides it; this dep covers the auto-mounted tool's minimal execute ctx, like `parentOnAsk`.
* Absent on both seats ⇒ byte-identical: a workflow child's ask keeps the host-seat resolution alone. */
parentDurableApproval?: {
scope: string;
ttlMs?: number;
};
/** #642 — the decisions a host holds for PARKED wa* rows of a prior run, read at execute when the model
* re-invokes with `resumeFromRunId` (a call-time getter over the run id: the decisions arrive on the host's
* trusted run channel, `RunInternals.workflowParkedResume`, and only the ones naming THIS resume's source run
* apply). Threaded into `RunWorkflowOptions.parkedResume`: the resume drives the parked child on with the
* decision instead of re-parking. Never a tool argument — a model cannot decide an approval. */
parkedResume?: (resumeFromRunId: string) => ReadonlyArray<import("./workflow.js").WorkflowParkedResume> | undefined;
/** The HOST run's display sink (its `RunInternals.onForwardEvent` behind the runner's ctx wrapper:
* `task_progress` always, plus the children's content events — `text_delta`/`text_end`/
* `reasoning_delta`/`tool_start`/`tool_end`, UNTRUSTED-RAW: the consumer must redact — when the HOST spec set
* `forwardSubagentEvents: true`) — threaded via `startWorkflow` into every spawned agent's trusted
* internals so a workflow child's events bubble to the deployment's one sink, the same
* channel a `createSubagentTool` delegation threads. Display-only; absent ⇒ ticks stay in each
* child's own stream. (A dep, not read off the execute ctx: the mounted tool's `AgentTool.execute`
* wrapper builds a minimal `{toolCallId, signal}` ctx — the rich-ctx injection only wraps
* `spec.tools`. `ctx.forwardEvent` still wins when a wrapping path provides it.) */
forwardEvent?: (event: import("../core/types.js").TaskEvent) => void;
/** Parent effective-policy inheritance: the HOST task's `inheritedGateForChildren` chain accessor,
* called at LAUNCH time and threaded via `RunWorkflowOptions.inheritedGate` into every spawned agent's
* trusted internals — workflow children inherit the host's evaluated gate exactly like a
* `createSubagentTool` delegation. (A dep for the same reason as `forwardEvent`: the direct mount's
* execute ctx is minimal; `ctx.inheritedGateForChildren` still wins when a wrapping path provides it.) */
inheritedGateForChildren?: () => import("../core/runner/contracts.js").InheritedGate;
/** RB-201 FO-3 — the HOST session's auto-mode classifier, threaded via `RunWorkflowOptions.autoModeReview`
* so every child this workflow spawns is reviewed before it starts, exactly like a `createSubagentTool`
* delegation's child. (A dep for the same reason as `forwardEvent`/`inheritedGateForChildren`: the direct
* mount's execute ctx is minimal; `ctx.autoModeReview` still wins when a wrapping path provides it.)
* A call-time getter, not a value — the decider is built during prepare and this dep is read at launch.
* Absent on both seats ⇒ auto-mode is not armed and the review legs are a complete no-op. */
autoModeReview?: () => {
decider: import("../core/auto-mode.js").AutoModeDecider;
} | undefined;
/** The HOST run's RESOLVED checkpoint seat — the store object the host parks in, else the word `"disabled"`
* (the same two-state value the delegation ctx carries as `ToolExecuteContext.checkpointStoreForChildren`;
* `ctx` wins on a rich-ctx mount). Every agent this workflow spawns inherits it verbatim unless its own
* spec (baseline / trusted script) pinned a store — and `"disabled"` lands unconditionally (a removal the
* baseline must not undo); see `buildWorkflowPrimitives`. A dep for the same reason as `forwardEvent`/
* `inheritedGateForChildren`: the auto-mounted tool's execute ctx is minimal `{toolCallId, signal}`. */
parentCheckpointStore?: import("../core/checkpoint-store.js").CheckpointStore | "disabled";
/** #235 — the deployment's structured notice sink (`RunnerDeps.onNotice`), forwarded into the governed
* build so the fields a script's agent spec wrote that did NOT reach the child are announced instead of
* vanishing (`workflow.governance_key_stripped`). #505 ① — the same seat now also carries the OPTIONS
* membrane's twin announcement (`workflow.agent_option_ignored`: keys of `agent(spec, opts)`'s second
* argument that did not apply). A dep for the same reason as the seats above: this auto-mount's execute
* ctx is minimal. Absent ⇒ the historic `console.warn` loudness. */
onNotice?: (n: import("../core/types.js").EngineNotice) => void;
/** Twin of the above for design/148 S1's center-artifact inheritance (see the `ctx.centerArtifactDigest`/
* `ctx.centerSourceRevision` reads at the `startWorkflow` options site) — `centerAdoption` resolves
* LATE in prepare-task.ts (well after this tool's mount point), so this is a call-time getter, not
* a value, mirroring `parentReadFace`. `ctx.centerArtifactDigest` still wins when a wrapping path
* provides it. */
parentCenterArtifactDigest?: () => string | undefined;
/** Publish-provenance companion of the above. */
parentCenterSourceRevision?: () => string | undefined;
}
/**
* Build the `run_workflow` tool. ASSERTS the runner is a hard sandbox (defense in depth — prepare-task only
* mounts it under the same gate). Returns a structured error (not a throw) for an LLM-correctable problem
* (oversized / malformed-meta / nesting), so the model can fix and retry.
*
* Async since: the card's named-workflow roster is derived from the SAME sources the execute path
* resolves against (the deployment registry's awaited `list()`), so the card can never advertise a workflow
* the call would resolve differently.
*
* The deps' SHAPE is judged at the call, synchronously, before any await: a retired mount seat refuses by name
* (`config.deps_retired_key`) and a `parentClamps` that is not a getter is refused, so a caller that never awaits the
* mount still learns at once — never as an unhandled rejection later.
*/
export declare function createRunWorkflowTool(d: RunWorkflowToolDeps): Promise<AgentTool>;