Share
Examples

Example: Fan-Out / Join Research Line

A four-Station Assembly Line that demonstrates the v1.1 Fan-Out / Join extension — one Task fans out into parallel research children and joins their results back together.


What It Does

Takes a research question from the Task Card and runs four Stations:

  1. SCOPE — Claude breaks the question into 3–5 research topics and fans out one Child Task per topic
  2. RESEARCH — the children each research one topic in parallel
  3. SYNTHESIZE — the Join Station: waits for every child to settle, then merges their findings
  4. DELIVER — writes the final report
                    ┌────────────┐
        ┌──────────►│ RESEARCH a │──────────┐
        │ fan-out   └────────────┘  settle  │
┌───────┴──┐        ┌────────────┐          ▼
│  SCOPE   │───────►│ RESEARCH b │───► ┌────────────┐     ┌─────────┐
│ (parent) │ parent └────────────┘     │ SYNTHESIZE │────►│ DELIVER │
└──────────┘ moves, ┌────────────┐     │   (join)   │     └─────────┘
             held   │ RESEARCH c │───► └────────────┘
                    └────────────┘

The parent Task is the join: it moves to SYNTHESIZE after SCOPE succeeds, but its Job there is held until every child has Settled.


Assembly Line Definition

{
  "id": "research-line",
  "name": "Research Line",
  "description": "Parallel research with fan-out/join",
  "version": "1.1.0",
  "owner": "your-username",
  "project": "your-project",
  "stations": [
    {
      "id": "scope",
      "name": "SCOPE",
      "trigger": {
        "type": "dispatch_worker",
        "labels": ["claude-code"],
        "createAssemblyLineRepo": true,
        "promptTemplate": "Research question: {{task.title}}\n\n{{task.description}}\n\nClone the Assembly Line Repository ($ASSEMBLY_LINE_REPO_URL) and write your scoping notes to reports/scope.md. Break the question into 3-5 independent research topics, then fan them out as instructed below. Commit, push, and conclude with success.",
        "fanOut": {
          "childColumnId": "research",
          "joinColumnId": "synthesize",
          "maxChildren": 5,
          "instructions": "One child per topic. Put the topic and the exact questions to answer in the child's description."
        }
      }
    },
    {
      "id": "research",
      "name": "RESEARCH",
      "trigger": {
        "type": "dispatch_worker",
        "labels": ["claude-code"],
        "promptTemplate": "You are researching one topic of a larger question.\n\nTopic: {{task.title}}\n\n{{task.description}}\n\nClone the Assembly Line Repository ($ASSEMBLY_LINE_REPO_URL) and write your findings to reports/children/<your-task-title-slug>/findings.md. Commit, push, and conclude with success."
      }
    },
    {
      "id": "synthesize",
      "name": "SYNTHESIZE",
      "trigger": {
        "type": "dispatch_worker",
        "labels": ["claude-code"],
        "promptTemplate": "Merge the parallel research results into one coherent analysis.\n\nOriginal question: {{task.title}}\n\nChild results ({{children.count}} children):\n{{children.results}}\n\nClone the Assembly Line Repository ($ASSEMBLY_LINE_REPO_URL). Each child's full findings are under reports/children/. Read them all, resolve contradictions, and write reports/synthesis.md. Commit, push, and conclude with success."
      }
    },
    {
      "id": "deliver",
      "name": "DELIVER",
      "trigger": {
        "type": "dispatch_worker",
        "labels": ["claude-code"],
        "promptTemplate": "Turn reports/synthesis.md in the Assembly Line Repository into a polished final report at reports/final-report.md. Commit, push, and conclude with success."
      }
    }
  ],
  "transitionRules": [
    { "id": "r1", "fromStationId": "scope",      "toStationId": "synthesize", "condition": "success" },
    { "id": "r2", "fromStationId": "research",   "toStationId": "synthesize", "condition": "success" },
    { "id": "r3", "fromStationId": "synthesize", "toStationId": "deliver",    "condition": "success" }
  ]
}

Three rules do all the work:

  • r1 moves the parent from SCOPE to the Join Station. Its dispatch there is held while children are unsettled.
  • r2 is the natural child rule: each child that finishes RESEARCH moves to SYNTHESIZE — and because SYNTHESIZE is its parent's Join Station, arrival settles the child instead of dispatching it. Children never run the join.
  • r3 carries the parent onward after the join Job succeeds.

Note the explicit rules: the reference implementation only auto-advances via explicit Transition Rules (see the divergence note in 03-assembly-lines.md). A child RESEARCH failure with no matching rule simply rests in place — which settles it with conclusion failure, so a failed child never deadlocks the join.


Task Card

Title:       How should a small team adopt agentic coding?

Description: Compare tooling, workflow changes, and security posture.
             Audience: a 5-person product team already on GitHub.

What Happens (Step by Step)

StepWhoWhat
1UserSubmits Task Card — Task T queued at SCOPE
2RunnerClaims the Job; SCOPE Agent writes reports/scope.md, picks 3 topics
3Agent (SCOPE)Calls POST .../tasks/T/fan-out with columnId: "research", 3 task cards, joinColumnId: "synthesize"
4ServerCreates children C1 C2 C3 at RESEARCH (each sharing T's Assembly Line Repository), sets T.blockedBy = [C1, C2, C3], dispatches the children
5Agent (SCOPE)Concludes success → rule r1 moves T to SYNTHESIZE
6ServerDispatch of T at SYNTHESIZE finds unsettled blockers → held (join_waiting)
7RunnersC1 C2 C3 run in parallel (parallelism = number of registered Runners)
8ServerEach child concludes success → rule r2 moves it to SYNTHESIZE → arrival settles it (no Job runs)
9ServerLast child settles → Join Release (at-most-once): T.childResults aggregated, held dispatch fires
10Agent (SYNTHESIZE)Prompt contains {{children.count}} = 3 and {{children.results}}; reads reports/children/*/findings.md, writes reports/synthesis.md, concludes success
11ServerRule r3 moves T to DELIVER; final report written
12ServerNo rule out of DELIVER → T rests (Settled success)

ALP Concepts in This Example

ConceptIn the Research Line
Fan-OutSCOPE's Agent spawns 3 children via the fan-out API
Parent-as-joinT itself waits at SYNTHESIZE — no separate join task
Child TaskC1 C2 C3 — ordinary Task Cards with parentTask = T
Join StationSYNTHESIZE — children settle on arrival, only the parent runs it
SettledWhat the join waits on; a failed child settles too
Join ReleaseAt-most-once aggregation of childResults + release of the held dispatch
Shared repositoryChildren inherit T's Assembly Line Repository, write under reports/children/<slug>/
{{children.results}}Per-child title/conclusion/summary injected into the join prompt
Declarative fan-outtrigger.fanOut makes the Server inject the exact API call into SCOPE's prompt

Things to Try

  1. Kill a child's Runner mid-run — the child's Job is swept as cancelled, which does not settle it. The join waits; retry the child, or force-release: POST .../tasks/T/release-join (unsettled children appear in childResults as unresolved).
  2. Retry a failed child before the join releases — the dispatch clears its Settled state and the join re-arms for it.
  3. Call fan-out twice from SCOPE — the second call returns the existing children with "reused": true instead of duplicating them.
  4. Drag the held parent out of SYNTHESIZE — a manual move of a held parent is an implicit force release.