Skip to content
MCP Chats
Documentation menu

Specification

Profile: parallel

Fork and join: several steps ready at once, continuing when all have finished.

Version parallel-1, draft, 6 October 2026. Extends the specification. Written against the 23 questions an independent review raised for the major-incident process, and revised after the third review; Appendix A maps each question to the sentence that answers it.

A server that implements this profile lists parallel-1 in describe.profiles. A process uses it by having a step with the parallel: key. No other declaration exists. A server without the profile refuses such a process at publication.

1. The step

yaml
steps:
  - id: activate
    agent: Open the incident bridge and page the three leads. Record the bridge reference.
    output: { bridge: string }

  - id: respond
    parallel: [operations, security, communications]
    next: close_review

  - id: operations
    person: operations-lead
    task: Restore service. Attach the recovery evidence.
    output: { recoverySummary: string }
    evidence: [link]
    due: 1h
    next: join

  - id: security
    person: security-lead
    task: Contain the security impact and record remaining exposure.
    output: { containmentSummary: string }
    evidence: [link]
    due: 1h
    next: join

  - id: communications
    person: communications-lead
    task: Publish the customer notice and keep it updated.
    output: { communicationsSummary: string }
    evidence: [link]
    due: 1h
    next: join

  - id: close_review
    person: inputs.commander
    approve: Confirm all three leads finished with evidence.
    on_reject: respond

  - id: done
    finish: stabilized

parallel: is a kind key like agent: or wait:. Its value is a list of two or more step ids, the branch entries. The step takes next and nothing else. It does no work of its own.

2. Branches

A branch is its entry step plus every step reachable from the entry following next and on_timeout edges, stopping at join. join is a reserved word for next and on_timeout inside a branch; it means "this branch is finished". A process that uses this profile cannot have a step with id join.

Publication rules, in addition to the core's:

  • Every branch has at least one step. Every step in a branch names next or join explicitly; the following-step default does not apply inside a branch. Every step in a branch reaches join.
  • No step is in two branches, and no step outside the branches routes into one. A branch step never routes to a step outside its branch, to a finish, or to the parallel step. join is allowed only inside a branch.
  • on_reject inside a branch targets a step in the same branch.
  • on_reject on a step after the parallel step may target the parallel step, which runs every branch again, or any step before it. It may not target a step inside a branch.
  • join edges and parallel: edges are not cycles for the core's acyclicity rule. Everything else is.
  • A branch cannot contain a parallel: step. Nesting is not in version 1.
  • Reachability: a branch entry is reachable through its parallel step.

3. Running

Fork. When the parallel step becomes ready, the server marks it waiting and makes every branch entry ready in the same write. Readiness is simultaneous; whether workers act at once is up to them. Each branch step then has its own claim, work item, due clock from this moment, escalation, on_reject, wait and events, as any step does. What the profile changes is listed in §4.

Join. When the last branch reaches join, the parallel step becomes done in the same write and the server makes its next ready. All branches must join; there is no threshold in version 1. A branch that reaches join early simply waits for the others; its outputs are already in run data.

Run data. Branch steps record their outputs under their own ids, as any step does. The parallel step records nothing in run data; its progress is its state in steps[].

Run state. The core rule applies unchanged: active while any step is ready or claimed, waiting when every unfinished step waits on a person, timer, event or escalation.

Ending the run from a branch. A failed decision, an approval rejected with no on_reject, or a wait_until on a missing value ends the run as the core says, and the core's terminal cleanup then cancels every unfinished step in every branch and stops their tokens. A branch cannot end the run with a finish, because branches cannot contain one.

Rework. on_reject inside a branch moves that branch's later completions to history and touches no sibling. on_reject from after the join to the parallel step applies the core rule to the whole region: every branch step's current completion moves to history and its state becomes null, the rejecting approval keeps its note current, the parallel step returns to waiting, and every branch entry is made ready again in the same write with a fresh due, so the returned view shows the entries ready or waiting and the other branch steps null. There is no retry of one failed branch in version 1: a failed branch has ended the run.

Snapshots. The core takes a snapshot at read time and refuses a decision when run data changed since. Inside a fork, every sibling completion changes run data, so a decision prepared before one is refused as stale and costs one more read; while siblings keep completing it can be refused more than once. The person sees what the sibling did before deciding, which is the point of the check.

4. Run view

The parallel step appears in steps[] with state waiting from fork to join and done after, with kind: "parallel". Branch steps appear as any other. get_work lists every ready agent branch step and every open branch work item. What this profile changes against the core: a new kind key; join as a reserved next value inside branches; several steps ready and several work items open at once; parallel: and join edges exempt from the acyclicity rule; and rework from after the join rewinding a whole region. Nothing else in the tool contracts changes.

Appendix A. The review's 23 questions

#QuestionAnswer
1What is the value of the key?A list of branch entry step ids (§1).
2Which step kinds may carry it?None; parallel: is its own kind (§1).
3When does the fork happen?When the parallel step becomes ready (§3). The step records nothing in run data, so there is no timestamp to reconcile on rework.
4Does the step itself do work?No (§1).
5Are branches activated atomically?Yes, in one write (§3).
6Simultaneous readiness or execution?Readiness (§3).
7How is the join identified?next: join on the last step of each branch; the parallel step's own next is the continuation (§2, §3).
8Does the step participate in the join?It is the join (§3).
9Threshold?All branches, version 1 (§3).
10Can a branch reach a finish early?No; refused at publication (§2).
11Reachability through branches?Entries are reachable through the parallel step (§2).
12Are these edges cycles?join and parallel: edges are exempt; nothing else is (§2).
13Two branches sharing a step?Refused (§2).
14Failure, rejection, escalation, timeout in a branch?Per step as in core; only run-ending events affect siblings (§3).
15Sibling cancellation on run end?Core terminal cleanup, at once (§3).
16Retry one failed branch?Not in version 1 (§3).
17Which sibling results become history on rework?Inside a branch, only that branch; from after the join, all (§3).
18Do sibling completions stale open snapshots?Yes, each one; the person reads again, possibly more than once (§3).
19How does a person get a fresh snapshot?Every get_run returns a current one (core §3.4).
20Run state with mixed agent and human branches?Core rule unchanged (§3).
21Nesting?Not in version 1 (§2).
22How is the profile declared?By using the key; detected at publication (preamble).
23How is the semantics version identified?parallel-1 in describe.profiles (preamble).