# workflow

> Deterministic, crash-resumable multi-agent workflow orchestrator over harness turns.

| field | value |
|-------|-------|
| version | 0.1.6 |
| type | binary |
| license | Apache-2.0 |
| repo | https://github.com/iii-hq/workers |
| supported_targets | x86_64-apple-darwin, aarch64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii trigger compose::add worker=workflow@0.1.6
```

## dependencies

- `configuration` @ `latest`

## readme

# workflow worker

A deterministic DAG orchestrator running over the iii harness. Accepts
`workflow::start` / `workflow::tick` / `workflow::stop` triggers; persists run
state in state; fans out nodes as child harness sessions with deterministic
ids so duplicate deliveries are idempotent.

The two always-on harness hook bindings (turn-completed → wake, pre-trigger →
stamp-reply) are one-shot at startup and rely on the engine's recoverable
triggers (iii #1962, engine ≥ 0.21.8): bound before the harness is up, they
park as pending intents and activate when the harness registers the trigger
types. On older engines the binds are silently dropped. The third hook —
pre-generate → inject-guidance — follows the `inject_guidance` knob in the
`workflow` configuration entry (ON by default): turning it off shrinks
prompts (the harness's `# Granted functions` catalog still advertises the
`workflow::*` ids) and the worker binds or unbinds it hot, no restart.

---

## Getting the result back (don't poll)

`workflow::start` returns a `run_id` immediately. There are a few ways to learn
the outcome; prefer push over polling `workflow::status` in a loop.

0. **Reply into your session (for an AGENT caller).** Set `reply_to: {}` on
   `workflow::start`. When the run reaches a terminal state the worker posts the
   outcome into your session as a new message (via `harness::send`), so your next
   turn just sees it — no polling, no callback wiring:

   ```jsonc
   workflow::start {
     "definition": { /* ... */ },
     "reply_to": { "template": "Naming run done — pick the winner:" }  // template optional
   }
   ```

   You pass NO session id: a `pre_trigger` hook auto-stamps your real
   `session_id` / `model` / `provider` (an agent cannot target another session).
   Delivery is at-least-once; a deterministic `idempotency_key` (`wfreply_<run_id>`)
   makes a re-fire a no-op.

1. **Completion callback (push), for a WORKER caller.** Pass `notify` to
   `workflow::start`. When the run reaches a terminal state the worker triggers
   your function once with `{run_id, status, result, result_error}`:

   ```jsonc
   workflow::start {
     "definition": { /* ... */ },
     "input": { /* ... */ },
     "notify": { "function_id": "myworker::wf-done" }  // optional "queue", defaults to "default"
   }
   ```

   Delivery is durable (enqueued) and **at-least-once** — make the handler
   idempotent and dedup on `run_id`. This is the trigger-driven path; no polling.

2. **Global broadcast.** Every terminal run also fires `workflow::run-completed`
   (untargeted). Bind a worker to it to observe *all* runs, e.g. for audit.

`workflow::status` remains for one-off inspection, but a live tracker should use
`reply_to` (agents) or `notify` (workers) rather than a status loop.

---

## Scaling & concurrency

### Single-writer-per-run (the correctness model)

Every path that reads, mutates, and writes a run record — `workflow::tick`,
`reconcile`, the pending-timeout sweep, and `workflow::stop` — holds the
per-run `WorkflowLocks` guard before touching state.  This serialises writes
within one process and closes the read-modify-write race.

Idempotency for duplicate event deliveries is provided by deterministic ids:
the child session id (`wf_<run_id>_<node_uid>@r<attempt>`) and the
`workflow_node_result/<run_id>/<node_uid>` key are stable, so a re-delivered
tick that re-fires an already-running node is a no-op at the harness layer.

### Horizontal scaling (run sharding)

To run more than one workflow-worker instance safely, shard `workflow::tick`
events by `run_id` so that all writes for a given run land on one owning
instance.  Concretely: consistent-hash the queue partition on `run_id`, or run
a single orchestrator instance.  No new code is needed — this is a deployment
topology choice.

### No record-level CAS available

state exposes `state::get`, `state::set`, `state::delete`,
`state::list`, and `state::update` (atomic per-field ops).  None carry a
precondition or version check — `state::set` is an unconditional overwrite.
There is no `state::cas` or `if_match` parameter.

`WorkflowRunRecord` therefore carries no `version` field; pretending one
enforces optimistic concurrency would be misleading.

### Blocked prerequisite for true multi-writer-per-run

Whole-record optimistic concurrency (allowing two instances to safely race on
the same run) requires an upstream iii engine operation: a `state::set` with an
`expected`/`if_match` argument, or a dedicated `state::cas` call.  Until that
engine op exists, the correct stance is run sharding (above).

**Action:** file an engine feature request for `state::set` with `if_match` /
`state::cas`.  Do not build a fake CAS on top of the current unconditional set.

## api reference

```json
{
  "functions": [
    {
      "description": "Internal pre_generate hook: appends workflow orchestration guidance to the agent system prompt. Bound to harness::hook::pre-generate at worker startup; not called directly.",
      "metadata": {},
      "name": "workflow::inject-guidance",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "GenerateContext": {
            "properties": {
              "system_prompt": {
                "default": "",
                "description": "The system prompt assembled so far (base + any prior hook's mutation).",
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "description": "The `pre_generate` hook envelope slice we read; the generation context is nested under `generate`.",
        "properties": {
          "generate": {
            "$ref": "#/definitions/GenerateContext"
          }
        },
        "title": "PreGenerateEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "PreGenerateMutations": {
            "description": "The harness applies `system_prompt` only when the key is present (`HookRunner`'s `parse_mutations`), so `None` serializes to an empty object: the safe no-op that preserves the harness's assembled prompt.",
            "properties": {
              "system_prompt": {
                "description": "Full replacement system prompt (base + appended guidance). The harness overwrites, it does not merge.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "description": "Hook envelope returned to the harness: the mutations to apply to the generation.",
        "properties": {
          "mutations": {
            "$ref": "#/definitions/PreGenerateMutations"
          }
        },
        "required": [
          "mutations"
        ],
        "title": "PreGenerateResponse",
        "type": "object"
      }
    },
    {
      "description": "Fetch the stored JSON result of one node: `node_uid` is the node id, or '{node_id}#{i}' for a fanout item (workflow::status lists them under `node_results`). Returns {result: null} when nothing is stored.",
      "metadata": {},
      "name": "workflow::node-result",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "node_uid": {
            "description": "The node id, or `\"{node_id}#{i}\"` for a fanout item (the keys of `workflow::status`'s `node_results`); `uid` is accepted as an alias.",
            "type": "string"
          },
          "run_id": {
            "description": "The `run_id` returned by `workflow::start`.",
            "type": "string"
          }
        },
        "required": [
          "node_uid",
          "run_id"
        ],
        "title": "NodeResultRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "result": {
            "description": "The node's stored JSON result, or null if the node has not completed (or produced no stored result). Always serialized — even when absent it is an explicit `{ \"result\": null }`, so callers never special-case a missing key."
          }
        },
        "title": "NodeResultResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal: hot-reload workflow config — re-binds the cron sweep on a sweep_expression change and swaps the per-call tuning snapshot otherwise.",
      "metadata": {
        "internal": true
      },
      "name": "workflow::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Trigger payload for `<worker>::on-config-change`. Advisory only: handlers re-fetch the authoritative value and ignore it, so a direct call can never inject config.",
        "properties": {
          "id": {
            "default": null,
            "description": "Configuration id that changed.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "OnConfigChangeEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Ack returned by the internal `<worker>::on-config-change` handler.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "OnConfigChangeResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal pre_trigger hook on workflow::start: stamps the caller's session (and, for `reply_to`, model/provider/policy) into the arguments. Always continues; not called directly.",
      "metadata": {},
      "name": "workflow::stamp-reply",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "StampCall": {
            "properties": {
              "arguments": {
                "default": null
              },
              "id": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "description": "The slice of the `pre_trigger` hook envelope we need (lenient: ignores the other envelope fields the harness sends).",
        "properties": {
          "call": {
            "anyOf": [
              {
                "$ref": "#/definitions/StampCall"
              },
              {
                "type": "null"
              }
            ]
          },
          "functions": {
            "default": null,
            "description": "Caller turn's dispatch policy, captured into the stamped `reply_to` so the reply can wake the caller with its own reach."
          },
          "model": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "provider": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "StampReplyEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "StampReplyMutations": {
            "properties": {
              "arguments": {
                "description": "The call's arguments with the caller identity stamped in."
              }
            },
            "required": [
              "arguments"
            ],
            "type": "object"
          }
        },
        "description": "Hook outcome returned to the harness: always `continue`, with rewritten `workflow::start` arguments when there was a caller identity to stamp. The harness's `parse_output` treats a missing/`\"continue\"` decision and absent mutations as \"continue unchanged\", so the no-op case is the same wire the old `null` reply produced — but the response schema stays typed (the registry publish gate refuses AnyValue response schemas).",
        "properties": {
          "decision": {
            "description": "Always `\"continue\"` — this hook never denies or holds a call.",
            "type": "string"
          },
          "mutations": {
            "anyOf": [
              {
                "$ref": "#/definitions/StampReplyMutations"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "decision"
        ],
        "title": "StampReplyResponse",
        "type": "object"
      }
    },
    {
      "description": "Run a multi-agent workflow and return its run_id immediately. Declarative DAG: fan-out, barrier/join, durable, crash-resumable. To get the result set `reply_to: {}` (or `notify`) and END YOUR TURN — it arrives as a separate message when the run ends; never poll workflow::status in a loop.",
      "metadata": {},
      "name": "workflow::start",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "AgentSpec": {
            "additionalProperties": false,
            "description": "The sub-agent that runs a node (one harness session).",
            "properties": {
              "functions": {
                "description": "Child dispatch policy: an allow-list array, a single string, or `{ \"allow\": [...], \"deny\": [...] }`; omit to inherit `default_functions`."
              },
              "model": {
                "description": "Model id registered on this engine (discover them with `router::models::list`); an unregistered id is rejected at start.",
                "type": "string"
              },
              "output": {
                "description": "Output contract; any node another node reads or fans out over must be `{\"type\":\"json\",\"schema\":{...}}` declaring every field read downstream."
              },
              "provider": {
                "description": "Provider override; defaults to the routed provider for the model.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "system_prompt": {
                "description": "System prompt appended to the child's built-in identity prompt.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "model"
            ],
            "type": "object"
          },
          "FanoutSpec": {
            "additionalProperties": false,
            "description": "Per-item parallelism: expand a node into one child per element of an array.",
            "properties": {
              "over": {
                "description": "`\"node:<dep>.<path>\"` to an array the dependency's `agent.output` schema declares; one child runs per element, reading it as `\"fanout_item\"`.",
                "type": "string"
              }
            },
            "required": [
              "over"
            ],
            "type": "object"
          },
          "InputFrom": {
            "anyOf": [
              {
                "description": "`\"run_input\"` (the run's top-level input), `\"node:<id>\"` (that dependency's result), or `\"fanout_item\"` (this fanout child's array element).",
                "type": "string"
              },
              {
                "description": "Several `\"node:<id>\"` sources, each also listed in `depends_on`, joined into one object keyed by node id.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            ],
            "description": "Where a node's input comes from: one source, or an array of `\"node:<id>\"` refs gathered into one object keyed by node id."
          },
          "InputSpec": {
            "additionalProperties": false,
            "description": "What a node receives as its opening message.",
            "properties": {
              "from": {
                "allOf": [
                  {
                    "$ref": "#/definitions/InputFrom"
                  }
                ],
                "description": "Source of the input value: a single source or, for a join node, an array of `\"node:<id>\"` refs."
              },
              "template": {
                "description": "Text prepended to the JSON-serialized input as the node's opening message.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "from"
            ],
            "type": "object"
          },
          "NodeDef": {
            "additionalProperties": false,
            "description": "One agent in the DAG, plus its wiring (inputs, dependency edges, optional fan-out).",
            "properties": {
              "agent": {
                "$ref": "#/definitions/AgentSpec"
              },
              "depends_on": {
                "default": [],
                "description": "Prerequisite node ids; the node fires only once all of them are Done (the barrier / join).",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "fanout": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/FanoutSpec"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Fan out: one child agent runs per item of the referenced array."
              },
              "input": {
                "$ref": "#/definitions/InputSpec"
              }
            },
            "required": [
              "agent",
              "input"
            ],
            "type": "object"
          },
          "NotifySpec": {
            "additionalProperties": false,
            "description": "Completion callback: `function_id` is triggered once on terminal state with `{run_id, status, result, result_error}`; at-least-once, dedup on `run_id`.",
            "properties": {
              "function_id": {
                "description": "Function id to trigger on terminal state, e.g. `\"myworker::wf-done\"`.",
                "type": "string"
              },
              "queue": {
                "description": "Queue for durable delivery; defaults to `\"default\"`.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "function_id"
            ],
            "type": "object"
          },
          "OutputRef": {
            "additionalProperties": false,
            "description": "Selects the node whose result becomes the run's `result`.",
            "properties": {
              "from": {
                "description": "`\"node:<id>\"` of an existing node; for a fanout group the result is the array of its children's results, in order.",
                "type": "string"
              }
            },
            "required": [
              "from"
            ],
            "type": "object"
          },
          "ReplySpec": {
            "additionalProperties": false,
            "description": "Push the outcome into the caller's session as a message; agents send `{}` (optionally `template`) — the rest is auto-stamped from the caller's turn.",
            "properties": {
              "functions": {
                "description": "Caller turn's dispatch policy (`{allow, deny}`), auto-stamped so the reply can wake an idle caller with its original reach."
              },
              "model": {
                "description": "Model for the reply turn; auto-stamped from the caller's turn.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "provider": {
                "description": "Provider override; auto-stamped from the caller's turn.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "session_id": {
                "description": "Target session; auto-stamped from the caller's turn.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "template": {
                "description": "Text prepended to the formatted outcome.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "WorkflowDef": {
            "additionalProperties": false,
            "description": "Declarative multi-agent DAG, durable and crash-resumable: nodes are sub-agent sessions, `depends_on` the edges, `output` the returned node.",
            "properties": {
              "default_functions": {
                "description": "Dispatch policy inherited by every node that omits `agent.functions` (same shape); when unset those nodes get `[\"*\"]`, everything."
              },
              "nodes": {
                "additionalProperties": {
                  "$ref": "#/definitions/NodeDef"
                },
                "description": "Nodes keyed by node id; an id must not contain `#`, `.`, or `/`, and the `depends_on` graph must be acyclic.",
                "type": "object"
              },
              "output": {
                "allOf": [
                  {
                    "$ref": "#/definitions/OutputRef"
                  }
                ],
                "description": "Which node's result the whole run returns."
              },
              "version": {
                "default": 1,
                "description": "Schema version; only `1` is supported.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "nodes",
              "output"
            ],
            "type": "object"
          }
        },
        "properties": {
          "caller_session_id": {
            "default": null,
            "description": "Orchestrator session, stamped by the `workflow::stamp-reply` hook from the caller's turn; never trusted from the agent.",
            "type": [
              "string",
              "null"
            ]
          },
          "definition": {
            "allOf": [
              {
                "$ref": "#/definitions/WorkflowDef"
              }
            ],
            "description": "The workflow DAG to run (`workflow` is accepted as an alias key)."
          },
          "idempotency_key": {
            "default": null,
            "description": "Dedupe key; a repeated key returns the original `run_id` instead of launching a duplicate run.",
            "type": [
              "string",
              "null"
            ]
          },
          "input": {
            "default": null,
            "description": "Any JSON value; read by nodes whose `input.from` is `\"run_input\"`."
          },
          "notify": {
            "anyOf": [
              {
                "$ref": "#/definitions/NotifySpec"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Completion callback triggered once when the run reaches a terminal state, instead of polling `workflow::status`."
          },
          "reply_to": {
            "anyOf": [
              {
                "$ref": "#/definitions/ReplySpec"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Deliver the outcome into your session as a new message: send `{}` (optionally `template`), then end your turn — it arrives when the run finishes."
          }
        },
        "required": [
          "definition"
        ],
        "title": "StartRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "run_id": {
            "type": "string"
          }
        },
        "required": [
          "run_id"
        ],
        "title": "StartResponse",
        "type": "object"
      }
    },
    {
      "description": "Return a snapshot of a workflow run (status, per-node state, node_errors, node_results, result, result_error), or null if not found. Each call costs a turn: do not poll in a loop; pass `reply_to`/`notify` to workflow::start to be pushed the outcome.",
      "metadata": {},
      "name": "workflow::status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Read a single snapshot of a workflow run's status.",
        "properties": {
          "run_id": {
            "description": "The `run_id` returned by `workflow::start`.",
            "type": "string"
          }
        },
        "required": [
          "run_id"
        ],
        "title": "StatusRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "anyOf": [
          {
            "$ref": "#/definitions/StatusResponse"
          },
          {
            "type": "null"
          }
        ],
        "definitions": {
          "NodeState": {
            "enum": [
              "pending",
              "running",
              "done",
              "failed",
              "cancelled"
            ],
            "type": "string"
          },
          "RunStatus": {
            "enum": [
              "running",
              "awaiting_nodes",
              "completed",
              "failed",
              "cancelled"
            ],
            "type": "string"
          },
          "StatusResponse": {
            "properties": {
              "node_errors": {
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Errors for nodes that failed, keyed by node uid — e.g. \"no provider registered for model …\". Present so a caller can diagnose a `failed` run without digging into stored state. Omitted when no node carries an error.",
                "type": "object"
              },
              "node_results": {
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Nodes that have a stored result: node uid → result_ref (its key in state). Fetch the value with `workflow::node-result { run_id, node_uid }`. Lets a caller recover partial work from a run that failed partway (the run-level `result` is only set on a Completed run). Omitted when empty.",
                "type": "object"
              },
              "nodes": {
                "additionalProperties": {
                  "$ref": "#/definitions/NodeState"
                },
                "description": "Per-node state keyed by node uid.",
                "type": "object"
              },
              "result": true,
              "result_error": {
                "description": "Run-level failure summary (set when `status == failed`).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "run_id": {
                "type": "string"
              },
              "status": {
                "$ref": "#/definitions/RunStatus"
              }
            },
            "required": [
              "nodes",
              "run_id",
              "status"
            ],
            "type": "object"
          }
        },
        "title": "Nullable_StatusResponse"
      }
    },
    {
      "description": "Cancel a workflow run, cascading harness::stop to each live node session.",
      "metadata": {},
      "name": "workflow::stop",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "run_id": {
            "type": "string"
          }
        },
        "required": [
          "run_id"
        ],
        "title": "StopRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "stopping": {
            "type": "boolean"
          }
        },
        "required": [
          "stopping"
        ],
        "title": "StopResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal cron sweep: reconcile AwaitingNodes runs and time out nodes past their deadline. Not called directly.",
      "metadata": {},
      "name": "workflow::sweep",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Cron event payload (schedule info ignored — the sweep scans all run records).",
        "properties": {
          "scheduled_at": {
            "default": null,
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "title": "SweepEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "swept": {
            "description": "Number of runs touched (reconciled and/or timed out) this sweep.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "ok",
          "swept"
        ],
        "title": "SweepResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal durable workflow step.",
      "metadata": {},
      "name": "workflow::tick",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "run_id": {
            "type": "string"
          },
          "step": {
            "default": 0,
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "run_id"
        ],
        "title": "TickRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "skipped": {
            "type": "boolean"
          }
        },
        "required": [
          "skipped"
        ],
        "title": "TickResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal: on a harness turn-completed event, wake the owning run with a tick. Best-effort latency optimization; the cron sweep is the durable backstop. Not called directly.",
      "metadata": {},
      "name": "workflow::wake",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Harness `turn-completed` payload; only `session_id` is read.",
        "properties": {
          "session_id": {
            "default": "",
            "type": "string"
          }
        },
        "title": "WakeEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "woke": {
            "type": "boolean"
          }
        },
        "required": [
          "woke"
        ],
        "title": "WakeResponse",
        "type": "object"
      }
    }
  ],
  "triggers": []
}
```
