workflow: meta rides the seam as data — the engine never evaluates it
P1 review finding: extractMeta timed only the literal's vm evaluation;
materializing the RESULT then read properties ordinarily on the HOST
stack, so a meta literal smuggling a getter (get name() { while(true){} })
could wedge the host outside any timeout — defeating the exact spin
isolation the worker thread exists for.
Rather than harden the evaluator (descriptor walks, AST validation),
delete the mechanism: the workflow's identity now reaches the seam as a
plain JSON field (WorkflowStartRequest.meta), carried by the tool as a
schema-validated `meta` object parameter the model fills directly. The
engine only shape-validates data (validateMeta, every violation named)
and pre-parses the body; the scanner, the vm evaluation, and the
host-side materialization are gone, and with them the hole. A body
still opening with a Claude Code-style `export const meta` statement
gets a pointed SCRIPT_PARSE message (the likeliest authoring slip; a
CC script's body stays drop-in, only its meta header moves into the
parameter). syncTimeoutMs now governs exactly one thing: the initial
synchronous slice inside the worker.
The RFC's decision section is rewritten in place (implemented-RFC
rule); the embedded-meta format moves to alternatives-considered with
the hole as the reason. Tool description, presentation (title now reads
meta.name directly — the textual sniff is gone), seam vocabulary docs,
and catalogs follow.
This commit is contained in:
@@ -11,7 +11,7 @@ The protected `emitWorkflowEvent` helper dispatches the `workflow/*` events with
|
||||
## Vocabulary
|
||||
|
||||
- `WorkflowStartRequest` — `{ script, args?, parent: Agent, signal? }`. `parent` is REQUIRED: every child the script spawns is attributed to it. `args` must be plain host-realm JSON data.
|
||||
- `WorkflowMeta` / `WorkflowPhase` — the script's validated `export const meta` block (Claude Code format: required `name`/`description`, optional `whenToUse`/`phases`).
|
||||
- `WorkflowMeta` / `WorkflowPhase` — the workflow's identity block, carried as plain JSON data on the start request (Claude Code meta vocabulary: required `name`/`description`, optional `whenToUse`/`phases`) and shape-validated by the engine.
|
||||
- `WorkflowRun` — `{ id, meta, result, cancel(reason?), dispose() }`; the consumer awaits `result` and MUST `dispose` on every path.
|
||||
- `WorkflowResult` — `{ value, stopReason: 'completed'|'cancelled'|'error', error?, agentsStarted }`; `value` is the script's materialized return (plain JSON data; `null` for no return).
|
||||
- `WorkflowError` — `HarnessError` with a `WorkflowErrorCode` and a `fatal` flag driving the combinator discipline: a fatal error (bad hook arguments, unsupported options/schemas, tripped caps, seam start failures, cancellation) always propagates through `parallel()`/`pipeline()` instead of dissolving into a per-item `null`. `isFatalWorkflowError(error)` is the catch-site predicate.
|
||||
|
||||
@@ -35,9 +35,11 @@ export interface WorkflowPhase {
|
||||
}
|
||||
|
||||
/**
|
||||
* The script's `export const meta` block, validated by the engine before the
|
||||
* body runs. `name`/`description` are required; the rest is optional
|
||||
* annotation. Matches the Claude Code dynamic-workflows script format.
|
||||
* The script's identity block, provided as plain JSON data alongside the
|
||||
* script body (the model-facing tool carries it as its `meta` parameter) and
|
||||
* validated by the engine before the body runs. `name`/`description` are
|
||||
* required; the rest is optional annotation. The field vocabulary matches the
|
||||
* Claude Code dynamic-workflows meta block.
|
||||
*/
|
||||
export interface WorkflowMeta {
|
||||
/** Short kebab-case workflow name (display + persistence key). */
|
||||
@@ -51,14 +53,18 @@ export interface WorkflowMeta {
|
||||
}
|
||||
|
||||
/**
|
||||
* What a caller asks for when starting a workflow run. `parent` is REQUIRED —
|
||||
* every `agent()` the script spawns is attributed to it (cwd, lineage, depth
|
||||
* flow through the subagent seam). `args` must be plain host-realm JSON data;
|
||||
* the engine exposes it to the script as the `args` global.
|
||||
* What a caller asks for when starting a workflow run. `meta` and `args` are
|
||||
* plain JSON DATA by the seam contract (the tool builds both from the model's
|
||||
* schema-validated call; the engine validates `meta`'s shape and rejects loud
|
||||
* before anything runs) — an engine never evaluates script text to obtain
|
||||
* them. `parent` is REQUIRED — every `agent()` the script spawns is
|
||||
* attributed to it (cwd, lineage, depth flow through the subagent seam).
|
||||
*/
|
||||
export interface WorkflowStartRequest {
|
||||
/** The full script text: `export const meta = {...}` + a plain-JS body. */
|
||||
/** The plain-JS script body (top-level await allowed; ends with `return <json-value>`). */
|
||||
script: string
|
||||
/** The workflow's identity block, as plain JSON data (shape-validated by the engine). */
|
||||
meta: WorkflowMeta
|
||||
/** Optional input exposed verbatim to the script as the `args` global. */
|
||||
args?: unknown
|
||||
/** The agent on whose behalf the run executes (parent of every child). */
|
||||
|
||||
Reference in New Issue
Block a user