Summary
openwop sub-workflows (core.subWorkflow / RFC 0007 dispatch) already spawn isolated, tenant-scoped child runs with inputMapping / outputMapping and depth/cycle caps. What they lack is _verification before merge_: the parent applies outputMapping directly on the child's terminal, with no checksum and no approval gate. This RFC adds an optional outputAttestation block on core.subWorkflow: when checksum: true the host computes and surfaces a content hash of the child's harvested outputs on the existing core.workflowChain.event { phase: 'output.harvested' } (RFC 0037) via an additive optional attestation field — not a new event — so the parent can verify integrity; when requireApproval: true the host MUST suspend via an approval interrupt (RFC 0051) _before_ merging, so a human or policy gate accepts/rejects/edits the sub-agent's artifact. Both are additive and opt-in — existing sub-workflows are unchanged.
Motivation
The feature set's sub-agent acceptance criterion is explicit: "each sub-agent has isolated permissions, and parent can verify checksums and approvals before merging." The isolation half exists (the apps/workflow-engine dispatcher inherits tenantId to prevent cross-tenant escalation). The verification half does not: subWorkflowDispatcher.ts copies child outputs into parent variables the instant the child reaches completed, with no integrity check and no gate. For autonomous fan-out — a supervisor dispatching N workers whose outputs are then merged — blind merge means a single compromised or hallucinated child artifact silently enters the parent's state.
The spec is the right place because "what exactly was merged, and was it approved" is an auditability + safety guarantee for multi-agent orchestration, and the checksum must be computed the same way across hosts for a parent to verify a child that ran on a _different_ host (RFC 0040 cross-host causation).
Proposal
§A — core.subWorkflow config extension (node-packs.md, additive)
{
"workflowId": "<child>",
"inputMapping": { "...": "..." },
"outputMapping": { "...": "..." },
+ "outputAttestation": {
+ "checksum": true,
+ "algorithm": "sha256",
+ "requireApproval": false,
+ "principalScope": ["report:write"]
+ }
}
All fields optional; an absent outputAttestation is exactly today's behavior.
§B — checksum (normative, when checksum: true)
After the child reaches a terminal status and its outputs are harvested but before outputMapping is applied, the host MUST:
- Compute a canonical hash (default
sha256) over the child's harvested output object, using the canonical-JSON serialization already defined for replay (spec/v1/replay rules), so the hash is host-independent. - Surface it as an additive optional
attestation { checksum, algorithm }object on the existingcore.workflowChain.event { phase: 'output.harvested' }(RFC 0037 — the event already fires at exactly this transition, carryingharvestedKeys[]), AND on the sub-workflow node'snode.completeddata (outputs.attestation.checksum). No new event type: theoutput.harvestedphase _is_ the merge point. - Apply
outputMappingas today. The checksum is _advisory for verification_ — the parent (or a downstream node) MAY compare it against an expected value and fail the parent if it diverges; the host does not itself reject on checksum (that is policy, expressed as a parent node).
§C — approval gate (normative, when requireApproval: true)
When requireApproval: true, the host MUST, after harvest and before outputMapping, suspend the parent via an approval interrupt (RFC 0051) carrying the child's outputs as the artifact (actions: ['accept', 'reject', 'edit', 'ask']). The merge proceeds only on accept or edit-accept:
accept→ applyoutputMappingwith the child's outputs unchanged.edit-accept→ applyoutputMappingwith the approver'seditedArtifactData.reject→ do not merge; surface per the node'sonChildFailurepolicy (fail-parentorabsorb).
This MUST fail-closed: if the run terminates or the interrupt expires without an accept/edit-accept, the outputs MUST NOT be merged. (Proposed protocol-tier SECURITY invariant subrun-merge-approval-fail-closed, landing with its conformance test at implementation.)
§D — permission narrowing (optional)
principalScope (when present) narrows the child run's effective scopes to the named RFC 0049 scopes — a child dispatched to "write a report" can be denied "delete data" even though the parent principal holds it. Reaffirms and tightens the existing tenant-inheritance isolation.
Positive example. Supervisor dispatches a research worker with { checksum: true, requireApproval: true }. Child completes → core.workflowChain.event { phase: 'output.harvested', attestation: { checksum: 'sha256:ab…' } } → parent suspends with an approval interrupt → operator accept → outputMapping merges. Audit log shows the checksum and the approver. Negative example. Same, operator never responds and the run is cancelled → outputs are not merged (fail-closed); parent surfaces the child as unmerged.
Compatibility
Additive. outputAttestation is an optional config block; absent ⇒ identical to today's blind merge. The attestation field is an additive optional property on the existing core.workflowChain.event output.harvested phase (RFC 0037; its required array is unchanged, consumers ignore the field). The approval gate reuses the existing RFC 0051 interrupt machinery — no new interrupt kind. No existing core.subWorkflow field, the outputMapping contract, or any MUST changes for workflows that don't opt in. No conformance pass invalidated.
Conformance
subrun-attestation-shape.test.ts—outputAttestationconfig validates. (Always runs wherecore.subWorkflowis supported.)subrun-checksum-stable.test.ts— a child's checksum is byte-stable for identical outputs and host-independent (matches the canonical-JSON recipe). (Gated onagents.subRunAttestation.)subrun-approval-gate.test.ts—requireApproval: truesuspends before merge;acceptmerges,rejectdoes not. (Gated.)subrun-approval-fail-closed.test.ts— a parent that terminates without approval does not merge the child outputs. (Gated; backs the invariant.)
The three gated scenarios drive the sub-run attestation seam POST /v1/host/sample/subrun/attest (request { childOutputs, outputAttestation: { checksum?, algorithm?, requireApproval?, principalScope? }, approvalAction? } → { attestation, harvestedEvent, merged, mergedValues? }), specified in host-sample-test-seams.md §"Open seams". A host advertising capabilities.agents.subRunAttestation: true wires it to light them up; they soft-skip on 404 until then.
Alternatives considered
- Tell authors to insert a separate
core.interruptnode after the sub-workflow. Rejected — that gates _after_ the merge already happened (the child outputs are in parent variables before the manual interrupt runs), so it can't prevent a bad artifact from entering parent state; the gate must be _intrinsic_ to the merge step. - Always checksum + always require approval. Rejected — most sub-workflows are trusted internal fan-out where a mandatory gate would break automation; opt-in keeps the common case zero-friction.
- A bespoke "merge approval" interrupt kind. Rejected — RFC 0051's
approvalkind withedit/rejectalready models exactly this; a new kind would duplicate it.
Unresolved questions
- Checksum canonicalization source. Confirm the canonical-JSON recipe reused here is the same one RFC 0041 pins for replay cache keys, so a cross-host child's checksum verifies. Resolve before Active.
- Partial fan-out approval. When a supervisor dispatches N children with
requireApproval, is it N interrupts or one batched approval? Proposed: one interrupt per child for v1; batching is a later optimization. Decide before Active. principalScopevs. RFC 0049. Is scope narrowing expressed here or purely in RFC 0049's RBAC surface? Proposed: this references RFC 0049 scopes, doesn't define new ones. Confirm.
Phase-0 resolution (architect ruling, 2026-05-25)
Per docs/autonomous-agent-runtime-plan.md §8 — Unresolved questions resolved (wire shape pinned; Active-ready pending schema + prose):
- Checksum canonicalization → reuse the RFC 8785 JCS recipe pinned in
replay.md §"LLM cache-key recipe" §B(+ the no-JCS fallback) then SHA-256 — no new recipe (ruling B). Guarantees cross-host verification. - Partial fan-out approval → one
approvalinterrupt per child for v1; batching is a later optional optimization. principalScopevs RFC 0049 → references RFC 0049 scopes only; defines no new scopes. The additiveattestationfield oncore.workflowChain.eventis confirmed non-breaking (ruling A;additionalProperties:false→ declared inproperties).
Implementation notes (non-normative)
apps/workflow-engine:subWorkflowDispatcher.ts(outputMappingat the harvest step) is the single insertion point — compute checksum + populate theoutput.harvestedevent'sattestationfield; ifrequireApproval, route through the existing suspend/interrupt path before the mapping copy. Effort: small–medium.
Acceptance criteria
- [x]
node-packs.mdcore.subWorkflowoutputAttestationsection. - [x]
agents.subRunAttestationcapability + additive optionalattestationobject oncoreWorkflowChainEvent(run-event-payloads schema). No new event type. - [x] Conformance: shape always-on; checksum/approval/fail-closed capability-gated — all four
subrun-*.test.tsscenarios live + green against the in-memory host. - [x]
subrun-merge-approval-fail-closedinvariant + public test landed inSECURITY/invariants.yaml(protocol-tier), withsubrun-approval-fail-closed.test.tsas its public test. - [x] CHANGELOG entry under
[1.1.4 — unreleased]. - [x] Milestone 2 — reference-host enforcement (
Active → Accepted). The in-memory reference host (examples/hosts/in-memory) advertisescapabilities.agents.subRunAttestation: trueand implements thePOST /v1/host/sample/subrun/attestseam (host-sample-test-seams.md): §B surfaces a byte-stableattestation { checksum: <JCS+SHA-256 of childOutputs>, algorithm: 'sha256' }(key-order-invariant via the host'sstableStringify) on the harvested-output shape; §C merges the child outputs only onapprovalActionaccept/edit-acceptand fails CLOSED (merged: false, nomergedValues) onreject/absent/expired whenrequireApproval: true. Reuses RFC 0051approval+ RFC 0049 scopes — no new interrupt kind, event type, or error code.
References
RFCS/0007-dispatch.md— sub-workflow / dispatch this extends.RFCS/0051-approval-deployment-gate-primitive.md— the approval interrupt reused for the merge gate.RFCS/0049— the scopesprincipalScopenarrows to.spec/v1/interrupt.md—approvalkind shape + resume actions.