@sema-agent/core
Version:
Stateless, task-oriented AI agent core
668 lines (667 loc) • 45.9 kB
JavaScript
import { Type } from "typebox";
import { defineTool, errorResult } from "../core/tools.js";
import { governanceBaselineError, governanceBaselineProblem } from "./governance-baseline-validity.js";
import { egressText, egressThrownText, boundedRedactedSummary } from "../core/untrusted-egress.js";
import { withDelegationProvenance } from "../core/tool-policy.js";
import { LAUNCH_RECEIPT_OWN_WORDS_CLAUSE, launchReceiptNoQuoteClause } from "../agents/launch-receipt-contract.js";
import { startWorkflow } from "./workflow.js";
import { WORKFLOW_AGENT_PARKED_ERROR_CODE } from "./workflow-types.js";
import { checkpointStoreOfSeat } from "../core/checkpoint-seat.js";
import { buildWorkflowPrimitives } from "./workflow-primitives.js";
import { stripSpawnTurnQuestionFace } from "../core/ask-question.js";
import { parentSpecSeatOfCtx } from "../core/parent-spec-seat.js";
import { CLAMP_SEATS, CLAMP_SET, assertClampRecord, clampRecordError, clampSourceOf, foldClampSources } from "../core/child-clamp-seats.js";
import { assertNoRetiredKeys } from "../core/retired-keys.js";
const RETIRED_RUN_WORKFLOW_TOOL_DEPS_KEYS = {
principal: "the host run's principal rides `RunWorkflowToolDeps.parentSpecSeat.principal` (the parent SPEC seat the host's prepare mints), never a seat of its own",
oneShot: "the host run's one-shot fact rides `RunWorkflowToolDeps.parentSpecSeat.oneShot` (only `true` travels), never a seat of its own",
builtinWorkflows: "the engine ships no built-in named workflows — `{name}` resolves only the deployment's `scriptStore`; register any named workflow there, or pass `script` inline",
parentExcludeTools: "the host run's tool selector layers ride `RunWorkflowToolDeps.parentToolCeiling` (`ToolExecuteContext.toolSelector.layers`, the host's prepare-time selector) and bound every agent the workflow spawns as its ceiling — an exclusion list is no longer handed down by name",
...Object.fromEntries(CLAMP_SEATS.filter((seat) => CLAMP_SET[seat].carrier === "clamp").map((seat) => [
`parent${seat.charAt(0).toUpperCase()}${seat.slice(1)}`,
`the host's tighten-only clamps ride ONE getter, \`RunWorkflowToolDeps.parentClamps\` (every clamp row, present keys) — return \`${seat}\` from it`,
])),
};
import { parseWorkflowMeta, splitWorkflowMeta, workflowScriptReadsClockOrRandom } from "./workflow-meta.js";
import { mergeWorkflowArgs, normalizeStringArg } from "./workflow-script-store.js";
import { workflowSizeGuidelineSection } from "./workflow-size-guideline.js";
import { toolFace } from "../core/tool-catalog-entries.js";
import { thrownText } from "../core/thrown-value.js";
export const RUN_WORKFLOW_TOOL_NAME = "Workflow";
function stripCodeFences(s) {
const t = s.trim();
const m = /^```[A-Za-z]*\r?\n([\s\S]*?)\r?\n?```$/.exec(t);
return m ? m[1] : s;
}
const DEFAULTS = {
maxScriptChars: 100_000,
maxAgents: 50,
totalTimeoutMs: 600_000,
maxResultChars: 100_000,
maxLogChars: 10_000,
};
function completionSummary(name, run) {
const cleanName = name?.replace(/[^\p{L}\p{N} _.:/-]/gu, "").replace(/\s+/g, " ").trim().slice(0, 60);
const done = run.agents.filter((a) => a.status === "completed").length;
const failed = run.agents.filter((a) => a.status === "failed").length;
const secs = run.endedAt !== undefined ? Math.max(0, Math.round((run.endedAt - run.startedAt) / 1000)) : undefined;
const elapsed = secs === undefined ? "" : secs >= 60 ? ` in ${Math.floor(secs / 60)}m${secs % 60}s` : ` in ${secs}s`;
const resumeClause = run.resume === undefined
? ""
: run.resume.journalEntries === 0
? "; resume found NO journal entries — nothing replayed, the whole script re-ran live"
: `; resume: replayed ${run.resume.replayed} of ${run.resume.journalEntries} journaled results`;
return (`workflow${cleanName ? ` "${cleanName}"` : ""} completed${elapsed} — agents ${done}/${run.agents.length} done` +
`${failed > 0 ? ` (${failed} failed)` : ""}${resumeClause}; full result + per-agent rows via TaskOutput("${run.id}")`);
}
function completionIdFromRejection(err) {
if (err === null || (typeof err !== "object" && typeof err !== "function"))
return undefined;
const run = err.workflowRun;
return run?.completionId;
}
function workflowUsageBlock(run) {
const agents = run.agents;
return {
...run.stats,
agent_count: agents.length,
agents_done: agents.filter((a) => a.status === "completed").length,
agents_error: agents.filter((a) => a.status === "failed").length,
...(agents.some((a) => a.status === "parked") ? { agents_parked: agents.filter((a) => a.status === "parked").length } : {}),
agents_empty_result: agents.filter((a) => a.status === "completed" && (a.output === undefined || a.output === "")).length,
agents_replayed: run.resume?.replayed ?? 0,
...(run.journalSkips !== undefined && run.journalSkips > 0 ? { journal_skipped: run.journalSkips } : {}),
...(run.resume !== undefined ? { resume_journal_entries: run.resume.journalEntries } : {}),
subagent_tokens: run.stats.tokens + run.stats.nested.tokens,
tool_uses: agents.reduce((n, a) => n + (a.toolCalls ?? 0), 0),
...(run.endedAt !== undefined ? { duration_ms: run.endedAt - run.startedAt } : {}),
};
}
function workflowDiagnostics(runId, journalRef, resumeMiss) {
const journalTeach = journalRef === undefined
? ""
: journalRef.includes("://")
? ` Full journal: ${journalRef}.`
: ` Full journal: ${journalRef} (a deployment data path — if Read is denied there, use the TaskOutput route above instead).`;
const resumeMissTeach = resumeMiss
? ` NOTE: this was a resume, but the prior run's journal was not usable under this scope — verify the runId and that this deployment wires a journalStore; the results above are from a full live re-run.`
: "";
return (`Per-agent rows (status / error / elapsed): TaskOutput({ task_id: "${runId}" }) — read them before assuming an empty or unexpected result.` +
journalTeach +
resumeMissTeach +
` To continue after editing the script: re-invoke with resumeFromRunId: "${runId}" (completed agents replay from the journal; only new/changed calls run).`);
}
const SCRIPT_ERROR_CODES = new Set([
"workflow.script_error",
"workflow.parked_call_changed",
"workflow.nesting",
"workflow.model_not_allowed",
"workflow.agent_schema",
"workflow.max_agents",
"workflow.result_too_large",
"workflow.budget_exceeded",
]);
function failureSummary(err, runId) {
const e = err;
const code = typeof e?.code === "string" ? e.code : undefined;
if (code === WORKFLOW_AGENT_PARKED_ERROR_CODE) {
const msg = typeof e?.message === "string" ? egressText("same-principal", e.message) : "";
return (`workflow parked (${code}): ${msg.length > 200 ? `${msg.slice(0, 200)}…` : msg} — it did not fail and is not finished` +
(runId ? `; per-agent rows via TaskOutput("${runId}") (the parked row reads status "parked")` : "") +
`; do not re-issue the same call — once the approval is decided, the deployment resumes this run${runId ? ` (resumeFromRunId: "${runId}")` : ""} and the script continues from the parked call`);
}
const guide = runId
? `; per-agent rows via TaskOutput("${runId}"); after fixing, re-invoke with resumeFromRunId: "${runId}" (completed agents replay from the journal)`
: "";
if (code && SCRIPT_ERROR_CODES.has(code) && typeof e?.message === "string") {
const msg = egressText("same-principal", e.message);
return `workflow failed (${code}): ${msg.length > 200 ? `${msg.slice(0, 200)}…` : msg}${guide}`;
}
return `${code && code.startsWith("workflow.") ? `workflow failed (${code})` : "workflow failed"}${guide}`;
}
export function workflowWhenToUseText(m) {
return (m.whenToUse || m.description) || undefined;
}
export function renderNamedWorkflowListing(entries) {
if (entries.length === 0)
return undefined;
const lines = entries.map((e) => {
const w = workflowWhenToUseText(e);
return `- ${e.name}${w ? `: ${w}` : ""}`;
});
return ("Named workflows available via {name} (registered defaults merge UNDER your `args`; a top-level key you pass wins wholesale):\n" +
lines.join("\n"));
}
async function collectNamedWorkflowListings(store) {
const byName = new Map();
try {
const listed = await store?.list?.();
if (Array.isArray(listed)) {
const rows = listed.filter((e) => Boolean(e) && typeof e.name === "string" && e.name.length > 0);
for (const e of rows)
byName.set(e.name, e);
}
}
catch {
}
return [...byName.values()];
}
export function createRunWorkflowTool(d) {
assertNoRetiredKeys(d, RETIRED_RUN_WORKFLOW_TOOL_DEPS_KEYS, "RunWorkflowToolDeps");
if (d.parentClamps !== undefined && typeof d.parentClamps !== "function") {
throw clampRecordError(`RunWorkflowToolDeps.parentClamps must be a getter returning the host's clamp source, got ${typeof d.parentClamps}`);
}
return mountRunWorkflowTool(d);
}
async function mountRunWorkflowTool(d) {
if (d.scriptRunner.safeForUntrustedScripts !== true) {
throw new Error("createRunWorkflowTool: refusing to mount run_workflow on a runner that is not safeForUntrustedScripts (design/98 fail-closed)");
}
const lim = d.limits ?? {};
const maxScriptChars = lim.maxScriptChars ?? DEFAULTS.maxScriptChars;
const childCaps = {
perAgentMaxWalltimeMs: lim.perAgentMaxWalltimeMs,
childMaxCostUsd: lim.childMaxCostUsd,
childMaxTokens: lim.childMaxTokens,
childMaxTurns: lim.childMaxTurns,
};
const problem = governanceBaselineProblem(d.governanceBaseline);
if (problem !== null)
throw governanceBaselineError(problem);
const FACE_LIST_FIELDS = ["allowTools", "excludeTools", "deferTools", "alwaysLoadTools"];
const dropNullFaces = (slot) => {
let out = slot;
for (const field of FACE_LIST_FIELDS) {
if (slot[field] !== null)
continue;
out = { ...out };
delete out[field];
}
return out;
};
const dropNullishOnAsk = (slot) => {
if (!("onAsk" in slot) || (slot.onAsk !== undefined && slot.onAsk !== null))
return slot;
const { onAsk: _absent, ...rest } = slot;
return rest;
};
const sanitizedBaseline = (() => {
const wt = d.governanceBaseline.worktreeBase;
const { worktreeBase: _absentOverlay, ...rest } = d.governanceBaseline;
return {
...rest,
base: dropNullishOnAsk(dropNullFaces(d.governanceBaseline.base)),
...(wt === null || wt === undefined ? {} : { worktreeBase: dropNullishOnAsk(dropNullFaces(wt)) }),
};
})();
const unionFaceList = (own, parent) => own === undefined ? [...new Set(parent)] : [...new Set([...own, ...parent])];
const withParentFace = (base, inherit) => ({
...base,
...(d.parentDeferTools?.length
? { deferTools: unionFaceList(base.deferTools ?? inherit?.deferTools, d.parentDeferTools) }
: {}),
...(d.parentAlwaysLoadTools?.length
? { alwaysLoadTools: unionFaceList(base.alwaysLoadTools ?? inherit?.alwaysLoadTools, d.parentAlwaysLoadTools) }
: {}),
...(() => {
const parent = d.parentRestoreGatedTools;
if (parent !== true && !(Array.isArray(parent) && parent.length > 0))
return {};
const own = base.restoreGatedTools !== undefined ? base.restoreGatedTools : inherit?.restoreGatedTools;
return {
restoreGatedTools: parent === true || own === true
? true
: own === undefined
? parent
: Array.isArray(own)
? [...new Set([...own, ...parent])]
: own,
};
})(),
});
const withParentProfile = (base) => d.parentPromptProfile !== undefined && base.promptProfile === undefined ? { ...base, promptProfile: d.parentPromptProfile } : base;
const baselineWithParentFace = (d.parentDeferTools?.length ?? 0) > 0 || (d.parentAlwaysLoadTools?.length ?? 0) > 0 || d.parentPromptProfile !== undefined || d.parentRestoreGatedTools !== undefined
? {
...sanitizedBaseline,
base: withParentProfile(withParentFace(sanitizedBaseline.base)),
...(sanitizedBaseline.worktreeBase !== undefined
? { worktreeBase: withParentFace(sanitizedBaseline.worktreeBase, sanitizedBaseline.base) }
: {}),
}
: sanitizedBaseline;
const governance = { baseline: baselineWithParentFace, models: d.models, caps: childCaps, onNotice: d.onNotice };
const namedWorkflowSection = renderNamedWorkflowListing(await collectNamedWorkflowListings(d.scriptStore));
const sizeGuidelineSection = workflowSizeGuidelineSection(d.sizeGuideline ?? lim.sizeGuideline);
const structuredError = (message) => errorResult(JSON.stringify({ error: message }));
return defineTool({
name: RUN_WORKFLOW_TOOL_NAME,
...toolFace("workflow"),
approvalPreview: (raw) => {
const a = (raw ?? {});
if (typeof a.name === "string" && a.name)
return { form: "named", workflow: a.name, ...(a.args !== undefined ? { hasArgs: true } : {}) };
if (typeof a.scriptPath === "string" && a.scriptPath)
return { form: "scriptPath", scriptPath: a.scriptPath, ...(a.args !== undefined ? { hasArgs: true } : {}) };
if (typeof a.script !== "string" || !a.script)
return undefined;
try {
const meta = parseWorkflowMeta(a.script);
return {
form: "script",
name: meta.name,
description: meta.description,
...(meta.phases !== undefined ? { phases: meta.phases.map((ph) => ({ title: ph.title, ...(ph.detail !== undefined ? { detail: ph.detail } : {}) })) } : {}),
scriptChars: a.script.length,
...(a.args !== undefined ? { hasArgs: true } : {}),
};
}
catch (e) {
return { form: "script", parseError: thrownText(e).slice(0, 300), scriptChars: a.script.length };
}
},
description: "Author and run your own deterministic JS WORKFLOW that spawns and coordinates sub-agents. Pass `script` " +
"(begins with `export const meta = {...}`, then an async body) and optional `args` (a JSON value). Returns a " +
"runId immediately; the workflow runs asynchronously and you are notified on completion. Use for a " +
"SUBSTANTIAL task that genuinely decomposes.\n" +
"ONLY call this tool when multi-agent orchestration was explicitly opted into: the user/operator asked " +
"for a workflow or fan-out in their own words, a skill or task directive requests it, or your standing " +
"instructions authorize self-orchestration. For any other task — even one that would clearly benefit " +
"from parallelism — do NOT call this tool: delegate individual sub-agents instead, or briefly describe " +
"what a workflow could do and ask first. A task merely LOOKING decomposable does not count.\n" +
"Pick the MINIMAL primitive that fits — do NOT reach for parallel by default:\n" +
"• agent(spec): ONE sub-agent (sequential when awaited). The DEFAULT — use it for any single coherent task. " +
"Both forms work: agent({ objective: '…' }, { label: '…', phase: '…' }) and agent('prompt string', opts?). " +
"ALWAYS pass a short kebab-case label naming what THIS agent does (e.g. { label: 'fix-lint-errors' }) — " +
"label/phase go in OPTS (the second argument), never inside spec, where they are ignored; unlabeled " +
"agents render as anonymous agent-N rows. " +
"Do NOT split a task into parallel agents unless its parts are GENUINELY INDEPENDENT.\n" +
"• parallel(thunks): run agents concurrently with a BARRIER (await ALL). ONLY for INDEPENDENT fan-out whose " +
"results you need together. If the parts share state, depend on each other, or just 'feel like multiple " +
"things', it is NOT parallel. A flat independent fan-out IS parallel — not workflow (and if those agents " +
"WRITE the same repo, it is isolation, below).\n" +
"• pipeline(items, ...stages): each item flows through the stages on its own chain (no barrier). Use for " +
"per-item STAGED processing of many items (e.g. for each file: lint → test → report).\n" +
"• agent(spec, { isolation: \"worktree\" }): run the agent in an ISOLATED git worktree. Use specifically when " +
"concurrent agents WRITE THE SAME FILES/REPO and must not clobber each other — even as a parallel fan-out " +
"(plain parallel would clobber); this takes precedence over plain parallel. A separate-working-copy need, " +
"NOT merely 'several agents'.\n" +
"• resumeFromRunId (this tool's arg): RESUME a prior/crashed run — replay its completed agents, run only new work " +
"(a run that could park must have finished — a crashed park-capable run is decided through the deployment's approval channel, not resumed).\n" +
"• scriptPath / name (this tool's args): when a script store is wired, every run persists its script and " +
"returns its scriptPath — to iterate, EDIT that file and re-invoke with {scriptPath} instead of resending " +
"the full script; {name} runs a saved workflow.\n" +
"• budget: SCALE the number/depth of agents to a token/cost CEILING — use whenever the task names a budget. " +
"Guard budget-driven loops on budget.total: with no budget set, budget.remaining() returns Infinity and a " +
"while-loop runs straight into the agent cap — add a hard iteration cap or pass a budget.\n" +
"\n" +
"Environment facts (read before writing the script):\n" +
"• Sub-agents may run in an isolated working directory — when a task concerns specific files/repos, put " +
"ABSOLUTE paths in the agent's objective; never assume it sees your cwd.\n" +
"• The script itself has NO host APIs: no require/import, no process/fs/fetch, no setTimeout (schedule " +
"time-based work outside the workflow). Only agent/parallel/pipeline/phase/log/budget/args exist.\n" +
"• Scripts must be DETERMINISTIC: `Date.now()`/`Math.random()`/argless `new Date()` throw (they would " +
"break resume) — pass timestamps in via `args`, stamp results after the workflow returns, and for " +
"randomness vary the agent prompt/label by index.\n" +
"• schema: if an agent's result is consumed by CODE or a downstream stage (a scorer, a file writer, the " +
"next pipeline stage, your own JSON.parse), pass opts.schema to FORCE structured output — prose transfer " +
"silently loses fields. Only results a human/LLM reads as narrative may omit schema. Standard JSON Schema " +
'applies, unions included — a nullable field is {"type":["string","null"]} (or anyOf). Cross-agent ' +
"assertions belong in the SCRIPT body: compare/validate agent results in plain JS between stages.\n" +
"• RETURN SHAPE (differs from Claude Code): agent() ALWAYS returns the full TaskResult — never the bare " +
"validated object. With schema, the validated object rides `r.structuredOutput` (read fields as " +
"`r.structuredOutput.bugs`, NOT `r.bugs`); `r.result` keeps the text form. A schema'd agent that " +
"completes in prose THROWS (fail-loud) instead of returning undefined.\n" +
"• Research/audit workflows: document and file claims are HYPOTHESES, not evidence — add a VERIFY " +
"stage whose agents EXECUTE (run the command, the test, the reproducer) and cross-check claims " +
"against actual output before the synthesis stage reports them as fact.\n" +
"• r.terminal.kind: agent() does NOT throw when the sub-agent FAILS — it RETURNS the result with r.terminal " +
"({ kind: 'completed' } on success; kind 'failed' (with r.terminal.code / r.terminal.message) / 'blocked' (r.terminal.reason) / 'paused' otherwise, " +
"with any failure text also in r.result). ALWAYS check r.terminal.kind before building on a result, and GATE later phases on it — e.g. " +
"`const impl = await agent({ objective: '…' }, { label: 'implement-fix' }); if (impl.terminal.kind !== 'completed') return { failed: 'implement', why: impl.result }` " +
"— running later phases on a failed prerequisite burns tokens on garbage.\n" +
"• phase (two forms, both fine): bare marker — phase('Scan'); groups every later agent() under 'Scan' " +
"until the next phase() call. Scoped — await phase('Fix', async () => { ...agents... }); groups just the " +
"body (equivalently, pass { phase: 'Fix' } in an agent's opts). Give each stage a phase so progress " +
"renders as stage groups; use the same titles you declared in meta.phases.\n" +
"• spec.modelName works only when the deployment configured a workflow model allowlist, and its legal " +
"values are the names on THAT allowlist which the deployment's model CATALOG KEYS also hold — a name " +
"off the allowlist, and a listed name the catalog lacks, are both refused rather than approximated. " +
"Generic tier words ('opus'/'sonnet'/'haiku'/'pro'/'flash'/'best') are not a separate namespace: on a " +
"deployment that configured tier bindings they become ordinary catalog keys, but only the ones its " +
"bindings actually resolve; on one that did not they are simply absent, unless its catalog happens to " +
"declare that literal name. Either way the allowlist is the gate, so a tier word is legal here only " +
"when this deployment allowlisted it, never as a shortcut around it. " +
"It also goes on the SPEC (the first argument), never in an agent()'s options object; `model` is accepted " +
"there as an alias for the same field. If unsure, omit it and the agent runs on the deployment's default " +
"role.\n" +
"\n" +
"Don't race. After launching, you know nothing about what the workflow found. Never fabricate or predict its " +
"results in any format — not as prose, summary, or structured output. When a completion notifier is wired " +
"(the launch note will say so) END YOUR TURN and wait — the notification carries the result; poll " +
"TaskOutput(task_id) only when the user asks for progress. If the user asks a follow-up before it lands, " +
"give status, not a guess." +
(namedWorkflowSection !== undefined ? `\n\n${namedWorkflowSection}` : "") +
sizeGuidelineSection,
parameters: Type.Object({
script: Type.Optional(Type.String({
description: "Self-contained workflow script. Must begin with `export const meta = { name, description, phases }` " +
"(pure literal, no computed values) followed by the script body using agent()/parallel()/pipeline()/phase(). " +
"`phases` is an ARRAY OF OBJECTS — e.g. `phases: [{ title: 'Scan', detail: 'grep the logs' }, { title: 'Fix' }]` " +
"(one entry per phase() call, titles matched exactly; `detail`/`model` optional; never bare strings).",
})),
name: Type.Optional(Type.String({
description: "Name of a predefined workflow (a saved script in the deployment's workflow store — see the " +
"named-workflow list in this tool's description). Resolves to a self-contained script; the " +
"registration's default args merge under your `args`.",
})),
scriptPath: Type.Optional(Type.String({
description: "Path to a workflow script file persisted by a prior invocation (each run's tool result carries its " +
"scriptPath when the deployment wires a script store). To iterate, edit that file and re-invoke with the " +
"same `scriptPath` instead of re-sending the full script. Takes precedence over `script` and `name`.",
})),
args: Type.Optional(Type.Unknown()),
resumeFromRunId: Type.Optional(Type.String({
description: "design/97 CORE-9: resume a PRIOR run of the SAME script — replays the longest unchanged prefix of " +
"its agent results from the journal and runs only the first changed/new call + everything after live. " +
"Requires a journal-backed deployment; omit for a fresh run.",
})),
}),
execute: async (rawArgs, ctx) => {
const rawScript = rawArgs.script;
const rawName = rawArgs.name;
const rawScriptPath = rawArgs.scriptPath;
const scriptArgs = rawArgs.args;
const rawResume = rawArgs.resumeFromRunId;
const resumeFromRunId = typeof rawResume === "string" && rawResume.length > 0 ? rawResume : undefined;
const sourceTaskId = ctx.taskId ?? d.sourceTaskId;
const hostSeat = parentSpecSeatOfCtx(ctx) ?? d.parentSpecSeat;
const detachedSeat = hostSeat !== undefined ? stripSpawnTurnQuestionFace(hostSeat) : undefined;
const parentSpecSeat = detachedSeat?.spec;
const principal = parentSpecSeat?.principal;
const taskScope = ctx.principal ?? d.scope;
let script;
let registeredDefaultArgs;
let registeredStringArgKey;
if (typeof rawScriptPath === "string" && rawScriptPath.length > 0) {
if (!d.scriptStore)
return structuredError("scriptPath requires a deployment workflow script store — none is wired; pass `script` inline");
if (d.scriptStore.scopePartitioned !== true) {
return structuredError("the wired workflow script store predates the scope-partition contract (scopePartitioned marker absent) — upgrade the store implementation");
}
try {
script = await d.scriptStore.load(rawScriptPath, taskScope ?? "default");
}
catch (err) {
return structuredError(`failed to read workflow script at scriptPath: ${egressThrownText("same-principal", err).slice(0, 300)}`);
}
}
else if (typeof rawScript === "string" && rawScript.length > 0) {
script = stripCodeFences(rawScript);
}
else if (typeof rawName === "string" && rawName.length > 0) {
if (!d.scriptStore?.resolveName) {
return structuredError("named workflows require a deployment workflow script store — none is wired; pass `script` inline");
}
let resolved;
try {
resolved = await d.scriptStore.resolveName(rawName);
}
catch (err) {
return structuredError(`failed to resolve workflow name: ${egressThrownText("same-principal", err).slice(0, 300)}`);
}
const resolvedShape = resolved;
if (resolvedShape !== undefined && (resolvedShape === null || typeof resolvedShape !== "object")) {
return structuredError("the wired workflow script store's resolveName returned the retired bare-string form — the resolution shape is now { script, defaultArgs? } (one shape); upgrade the store implementation");
}
if (resolved === undefined)
return structuredError(`unknown workflow name: ${JSON.stringify(rawName)}`);
script = resolved.script;
if ("defaultArgs" in resolved)
registeredDefaultArgs = resolved.defaultArgs;
if (typeof resolved.stringArgKey === "string" && resolved.stringArgKey.length > 0) {
registeredStringArgKey = resolved.stringArgKey;
}
}
else {
return structuredError("Must provide script, name, or scriptPath");
}
const effectiveArgs = mergeWorkflowArgs(normalizeStringArg(scriptArgs, registeredDefaultArgs, registeredStringArgKey), registeredDefaultArgs);
if (script.length > maxScriptChars) {
return structuredError(`script too large: ${script.length} chars > max ${maxScriptChars}`);
}
let meta;
try {
meta = parseWorkflowMeta(script);
}
catch (err) {
return structuredError(thrownText(err));
}
try {
const { body } = splitWorkflowMeta(script);
new Function(`return (async function(agent, parallel, pipeline, phase, log, budget, args) {\n${body}\n})`);
if (workflowScriptReadsClockOrRandom(body)) {
return structuredError("Workflow scripts must be deterministic: Date.now()/Math.random()/new Date() are unavailable (breaks resume). " +
"Stamp results after the workflow returns, or pass timestamps via args.");
}
}
catch (err) {
return structuredError(`workflow script failed to compile: ${thrownText(err)}`);
}
const hostOnAsk = ctx.onAsk ?? d.parentOnAsk;
const hostDurableApproval = ctx.durableApprovalForChildren ?? d.parentDurableApproval;
const runGovernance = hostOnAsk !== undefined && governance.baseline.base.onAsk === undefined
? {
...governance,
baseline: {
...governance.baseline,
base: {
...governance.baseline.base,
onAsk: withDelegationProvenance(hostOnAsk, { parentToolCallId: ctx.toolCallId, depth: 1 }),
},
},
}
: governance;
const childParkSeat = ctx.checkpointStoreForChildren ?? d.parentCheckpointStore;
const parkStore = checkpointStoreOfSeat(childParkSeat);
const scriptFn = (wfCtx) => {
const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, childParkSeat, () => {
const host = d.parentClamps?.();
assertClampRecord(host, "RunWorkflowToolDeps.parentClamps()", "clamp");
return foldClampSources(clampSourceOf(ctx), host);
});
return d.scriptRunner.run({ scriptSource: script, primitives, scriptArgs: effectiveArgs, signal: wfCtx.signal }).then((r) => r.result);
};
if (ctx.signal?.aborted) {
return structuredError("the launching tool call was aborted before the workflow could start — not started");
}
let handle;
const workflowTaskId = d.taskRegistry?.mintTaskId("workflow");
const autoModeReview = ctx.autoModeReview ?? d.autoModeReview?.();
const centerFromCtx = ctx.centerArtifactDigest !== undefined;
const parentCenterArtifactDigest = centerFromCtx ? ctx.centerArtifactDigest : d.parentCenterArtifactDigest?.();
const parentCenterSourceRevision = centerFromCtx ? ctx.centerSourceRevision : d.parentCenterSourceRevision?.();
try {
handle = startWorkflow(d.runner, scriptFn, {
store: d.store,
...(parkStore !== undefined ? { checkpointStore: parkStore } : {}),
...(d.agents !== undefined ? { agents: d.agents } : {}),
...(d.builtinAgents !== undefined ? { builtinAgents: d.builtinAgents } : {}),
name: meta.name,
...(meta.description !== undefined ? { description: meta.description } : {}),
...(meta.phases !== undefined ? { phases: meta.phases } : {}),
...(effectiveArgs !== undefined ? { effectiveArgs } : {}),
...(sourceTaskId !== undefined ? { sourceTaskId } : {}),
...(d.originatingSessionId !== undefined ? { originatingSessionId: d.originatingSessionId } : {}),
...((ctx.rootSessionId ?? d.rootSessionId ?? d.originatingSessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? d.rootSessionId ?? d.originatingSessionId } : {}),
...((ctx.placementRoot ?? d.placementRoot) !== undefined ? { placementRoot: ctx.placementRoot ?? d.placementRoot } : {}),
...(workflowTaskId !== undefined ? { runId: workflowTaskId } : {}),
journalStore: d.journalStore,
...(resumeFromRunId !== undefined ? { resumeFromRunId } : {}),
...(d.onBackgroundChildEvent !== undefined ? { onBackgroundChildEvent: d.onBackgroundChildEvent } : {}),
scope: taskScope ?? d.scope,
budget: lim.budget,
maxAgents: lim.maxAgents ?? DEFAULTS.maxAgents,
totalTimeoutMs: lim.totalTimeoutMs ?? DEFAULTS.totalTimeoutMs,
maxResultChars: lim.maxResultChars ?? DEFAULTS.maxResultChars,
maxLogChars: lim.maxLogChars ?? DEFAULTS.maxLogChars,
...(d.parentCwd !== undefined ? { parentCwd: d.parentCwd } : {}),
parentToolCallId: ctx.toolCallId,
...(sourceTaskId !== undefined ? { parentTaskId: sourceTaskId } : {}),
...((ctx.interactionPosture ?? d.parentInteractionPosture) !== undefined ? { interactionPosture: (ctx.interactionPosture ?? d.parentInteractionPosture) } : {}),
...(() => {
if ("memoryCaptureOptedOut" in ctx) {
return {
parentMemoryCaptureState: () => {
const o = ctx.memoryCaptureOptedOut ?? false;
const i = ctx.memoryCaptureIndeterminate ?? false;
const build = (optedOut, indeterminate) => ({
optedOut: optedOut === true,
indeterminate: indeterminate === true,
...(ctx.memoryCaptureControlDir !== undefined ? { controlDir: ctx.memoryCaptureControlDir } : {}),
...(ctx.memoryCaptureAncestors !== undefined ? { ancestors: ctx.memoryCaptureAncestors } : {}),
});
return o instanceof Promise || i instanceof Promise ? Promise.all([o, i]).then(([ov, iv]) => build(ov, iv)) : build(o, i);
},
};
}
return d.parentMemoryCaptureState !== undefined ? { parentMemoryCaptureState: d.parentMemoryCaptureState } : {};
})(),
...(d.parentModel !== undefined ? { defaultModel: d.parentModel } : {}),
...(d.parentThinking !== undefined ? { defaultThinking: d.parentThinking } : {}),
...(d.parentGetApiKeyAndHeaders !== undefined ? { defaultGetApiKeyAndHeaders: d.parentGetApiKeyAndHeaders } : {}),
...(parentSpecSeat !== undefined ? { parentSpecSeat } : {}),
...(detachedSeat?.internalsFlag.questionFaceStripped === true || ctx.questionFaceStripped === true || d.parentQuestionFaceStripped === true ? { questionFaceStripped: true } : {}),
...(hostDurableApproval !== undefined && d.store !== undefined && parkStore !== undefined ? { defaultDurableApproval: { ...hostDurableApproval } } : {}),
...(resumeFromRunId !== undefined && d.parkedResume !== undefined && d.parkedResume(resumeFromRunId) !== undefined ? { parkedResume: d.parkedResume(resumeFromRunId) } : {}),
...(parentCenterArtifactDigest !== undefined ? { parentCenterArtifactDigest } : {}),
...(parentCenterSourceRevision !== undefined ? { parentCenterSourceRevision } : {}),
...((ctx.forwardEvent ?? d.forwardEvent) !== undefined ? { onForwardEvent: (ctx.forwardEvent ?? d.forwardEvent) } : {}),
...((ctx.inheritedGateForChildren ?? d.inheritedGateForChildren) !== undefined
? { inheritedGate: (ctx.inheritedGateForChildren ?? d.inheritedGateForChildren)() }
: {}),
...(autoModeReview !== undefined ? { autoModeReview } : {}),
}, { workflowDepth: d.workflowDepth, ...((ctx.toolSelector?.layers ?? d.parentToolCeiling) !== undefined ? { toolCeiling: (ctx.toolSelector?.layers ?? d.parentToolCeiling) } : {}) });
}
catch (err) {
return structuredError(thrownText(err));
}
const runId = handle.runId;
let persistedScriptPath;
if (d.scriptStore && d.scriptStore.scopePartitioned === true) {
try {
persistedScriptPath = await d.scriptStore.persist(runId, script, taskScope ?? "default");
}
catch {
}
}
if (d.taskRegistry) {
const taskOwner = ctx.taskId ?? d.taskOwner ?? d.sourceTaskId;
d.taskRegistry.registerWorkflow({
runId,
handle,
store: d.store,
description: meta.name ?? meta.description,
toolUseId: ctx.toolCallId,
...(taskOwner !== undefined ? { owner: taskOwner } : {}),
...(taskScope !== undefined ? { scope: taskScope } : {}),
...(d.originatingSessionId !== undefined ? { originatingSessionId: d.originatingSessionId } : {}),
onServedTerminal: () => {
void Promise.resolve()
.then(() => d.notifier?.ackServed?.({
runId,
...(d.originatingSessionId !== undefined ? { originatingSessionId: d.originatingSessionId } : {}),
...(sourceTaskId !== undefined ? { sourceTaskId } : {}),
...(principal !== undefined ? { principal } : {}),
}))
.catch(() => { });
},
});
}
{
let notified = false;
const fire = (notification) => {
if (notified)
return;
notified = true;
try {
d.taskNotification?.(notification, { priority: "next" });
}
catch {
}
if (!d.notifier)
return;
void Promise.resolve()
.then(() => d.notifier.notify({
runId,
task_id: notification.task_id,
task_type: "workflow",
...(notification.toolUseId !== undefined ? { toolUseId: notification.toolUseId } : {}),
sourceTaskId,
...(d.originatingSessionId !== undefined ? { originatingSessionId: d.originatingSessionId } : {}),
principal,
status: notification.status === "completed" ? "completed" : "failed",
summary: egressText("human", notification.summary),
...(notification.result !== undefined ? { result: egressText("human", notification.result) } : {}),
...(notification.usage !== undefined ? { usage: notification.usage } : {}),
...(notification.diagnostics !== undefined ? { diagnostics: egressText("human", notification.diagnostics) } : {}),
...(notification.completionId !== undefined ? { completionId: notification.completionId } : {}),
}))
.catch(() => { });
};
handle.done
.then((res) => fire({
task_id: runId,
task_type: "workflow",
...(ctx.toolCallId !== undefined ? { toolUseId: ctx.toolCallId } : {}),
status: "completed",
summary: completionSummary(meta.name, res.run),
result: boundedRedactedSummary(res.result, 4000),
usage: workflowUsageBlock(res.run),
diagnostics: workflowDiagnostics(runId, d.journalStore?.locator?.(runId, res.run.scope), res.run.resume?.journalEntries === 0),
completionId: res.run.completionId,
}), (err) => fire({
task_id: runId,
task_type: "workflow",
...(ctx.toolCallId !== undefined ? { toolUseId: ctx.toolCallId } : {}),
status: "failed",
summary: failureSummary(err, runId),
diagnostics: workflowDiagnostics(runId, undefined),
...(completionIdFromRejection(err) !== undefined ? { completionId: completionIdFromRejection(err) } : {}),
}))
.catch(() => { });
}
return {
details: { type: "workflow-run", status: "backgrounded", workflowRunId: runId, task_id: runId },
content: JSON.stringify({
runId,
task_id: runId,
status: "started",
...(persistedScriptPath !== undefined ? { scriptPath: persistedScriptPath } : {}),
handling: `${launchReceiptNoQuoteClause(" (the ids above, and scriptPath when present)")} ${LAUNCH_RECEIPT_OWN_WORDS_CLAUSE}`,
note: (() => {
const pollExpr = d.taskRegistry ? `TaskOutput({ task_id: "${runId}" })` : undefined;
const blockingPollExpr = d.taskRegistry ? `TaskOutput({ task_id: "${runId}", block: true })` : undefined;
const journalRef = d.journalStore?.locator?.(runId, taskScope ?? d.scope ?? "");
const carries = pollExpr
? ` A terminal ${pollExpr} reply carries its result AND per-agent rows — read them${journalRef !== undefined ? ` (full journal: ${journalRef})` : ""} before assuming an empty or unexpected result.`
: "";
if (parentSpecSeat?.oneShot === true) {
return blockingPollExpr
? `This is a ONE-SHOT submission (e.g. headless \`-p\`) — there is no later turn for a background notification to land in, so do NOT end your turn expecting one. Actively wait instead: ${blockingPollExpr}. If it is still running after the wait, wait again (bounded) rather than ending the turn, or write out your best available answer now if you are near your own time budget.${carries}`
: "This is a one-shot submission (e.g. headless `-p`) and no poll path is available — the workflow's outcome may not be recoverable after this turn ends.";
}
if (d.notifier) {
return `The workflow runs asynchronously. You will be NOTIFIED when it completes (the notification carries the result). End your turn and wait — do not poll unless the user asks for progress.${carries}`;
}
if (d.taskNotification) {
return `The workflow runs asynchronously. Its completion is injected into THIS session if it is still active; if you have ended your turn and hear nothing, ${pollExpr ? `poll ${pollExpr}` : "there is no poll path wired"}.${carries}`;
}
return pollExpr
? `The workflow runs asynchronously and no completion notifier is wired. Poll ${pollExpr}.${carries}`
: "The workflow runs asynchronously; no completion notifier is wired and no status/poll path is available.";
})(),
}),
};
},
});
}