Orchestration Patterns

This content was generated with the assistance of AI and should be verified against the official documentation of each tool and framework before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page is Claude-Code-specific: everything below is a property of Claude Code’s Skill and Agent tools (Skill({skill: "<name>", args: "<…​>"}) runs a named skill inline; Agent({description, subagent_type, prompt, run_in_background}) spawns a subagent in its own context window and returns a result to the caller, per Claude Code — Subagents and Claude Code — Tools reference), re-verified as current as of 2026-09-28 against a version baseline of the Claude Code CLI 2.1.283, VS Code 1.139 with the GitHub Copilot Chat extension, and the GitHub Copilot CLI 1.0.88. Copilot has a partial counterpart: a VS Code or Copilot CLI custom agent can call other custom agents as subagents (its agents frontmatter) and can offer handoffs to another agent from chat (see Subagents & Custom Agents). The pipeline below ships no Copilot files, though, so this page shows Claude Code only rather than inventing a Copilot version of it. Every excerpt below is quoted verbatim from this account’s ai-catalog repository, pinned to commit 58fb709 (58fb70954404f9d539031a02ac6d4413f03745be).

A single skill is a procedure. Orchestration is what happens when one skill’s job is composing other skills and agents rather than doing the work itself — deciding what runs, in what order, in parallel or not, and how progress survives an interrupted session. ai-catalog’s issue-to-PR pipeline is entirely built this way: seven skills and two agents, each with a narrow job, chained by Skill(…​) and Agent(…​) calls.

The pipeline: skill → skill → agent

A diagram of the ai-catalog orchestration pipeline: iru-issue itself makes all three top-level calls — iru-explore and then iru-plan via Skill(), then iru-code via Agent(); iru-code calls iru-code-one-task-group, which calls iru-<key>-code-one-task-group, which fans out one Agent() call per task to iru-isolated-skill-executor, each running iru-java-code-one-task or similar; those tasks fan back in to iru-gate-runner, which runs the group’s tests/coverage/quality skills and reports a compact summary; durable state — implementation_plan.md’s checkboxes, its Base branch: line, and .archive/ — sits outside all of this
Figure 1. The ai-catalog orchestration pipeline

Read top to bottom, the pinned files show six links in the chain, each one a Skill(…​) or Agent(…​) call made from inside the previous file’s own body:

Example 1. ai-catalog: iru-issue/SKILL.md — Step 5, "Explore and plan", and Step 6, "Choose how to proceed with implementation"

iru-issue is the entry point: given a ticket ID, it runs exploration and planning in sequence, waiting for each to finish before starting the next:

Run, in order, waiting for each to finish before starting the next:

1. `Skill({skill: "iru-explore", args: "<ticket-id>"})`
2. `Skill({skill: "iru-plan", args: "<ticket-id>"})`

This demonstrates the plainest form of composition: a Skill(…​) call made from inside another skill’s own steps, not by the user typing /iru-explore and /iru-plan themselves. Once implementation_plan.md exists, the same file’s Step 6 hands off implementation — deliberately with a fresh context, so the exploration/planning transcript above does not carry into it and bias the read of the finished plan:

Agent({
  description: "Implement ticket <ticket-id> via the code skill",
  subagent_type: "iru-isolated-skill-executor",
  prompt: "Invoke Skill({skill: \"iru-code\"}) to execute implementation_plan.md at the repository root end to
    end. Report back: which tasks completed, the files touched, the coverage and code-quality outcome, ...",
  run_in_background: false
})

This is skill → agent composition: rather than calling Skill({skill: "iru-code"}) directly and running it inline, iru-issue spawns the custom iru-isolated-skill-executor agent and tells it to invoke the skill, so `iru-code’s whole multi-step run happens in an isolated context window and reports back only a summary. See Issue for the full page.

Example 2. ai-catalog: iru-code/SKILL.md — Step 4, "Implement task groups one at a time"

iru-code reads the plan’s task groups and, for each one still pending, dispatches to iru-code-one-task-group the same way — through an agent rather than a direct Skill(…​) call, so each group’s implementation work stays out of `iru-code’s own context:

Agent({
  description: "Implement task group <N>",
  subagent_type: "iru-isolated-skill-executor",
  prompt: "Invoke Skill({skill: \"iru-code-one-task-group\", args: \"<the full text of every task and sub-task
    in this group from the plan, each with its own language/framework tag if the plan sets one, the group's
    Parallelizable verdict, and any relevant \\\"Current code state\\\" context>\"}). Report back, per task in
    the group: the files touched, tests added/updated, coverage achieved, the code-quality outcome ...",
  run_in_background: false
})

See Code for the full page.

Example 3. ai-catalog: iru-code-one-task-group/SKILL.md — Step 2, "Dispatch each keyed bucket"

iru-code-one-task-group is the dispatch-by-key layer covered below; once it has resolved a bucket’s language/framework key, it calls the matching <key>-code-one-task-group skill directly, inline, through the Skill tool rather than another Agent(…​) hop — it already runs inside the isolated agent iru-code spawned for the group:

Skill({skill: "iru-<key>-code-one-task-group", args: "<every task/sub-task in this bucket, each with its own
  text and sub-tasks, plus the group's overall Parallelizable verdict and any relevant Current code state
  context>"})

See Code One Task Group for the full page.

Example 4. ai-catalog: iru-java-code-one-task-group/SKILL.md — Step 1, "Capture one pre-change quality baseline"

The language-specific group skill (an example iru-<key>-code-one-task-group) is where each group’s own work reaches the iru-gate-runner agent (iru-code already used it once, in its Step 3, for project-wide baselines), to run a quality check in an isolated context rather than dumping raw Checkstyle/PMD/SpotBugs output into the caller:

Agent({description: "Capture pre-change quality baseline for task group", subagent_type: "iru-gate-runner",
  prompt: "Invoke Skill({skill: \"iru-java-code-quality\", args: \"<ClassName1,ClassName2,...>\"}), then report
    back only the list of issues found for these classes."})

Note the nested Skill(…​) call inside the prompt string: iru-gate-runner is told which skill to run and what to report, and invokes it itself, one layer further down. See the fan-out excerpt in the next section for how this same file spawns the group’s actual implementation work in parallel, and Gate Runner for the agent’s own page.

The full chain — iru-issue → iru-explore → iru-plan → iru-code → iru-code-one-task-group → iru-<key>-code-one-task-group → iru-gate-runner — is therefore not one mechanism repeated six times identically. Some hops are inline Skill(…​) calls that share the caller’s context (iru-issue to iru-explore and iru-plan, and iru-code-one-task-group to its <key> backend); the others go through Agent(…​) specifically where isolating the callee’s context matters (a whole implementation run, or a quality-tool report a caller should never see the raw form of).

Dispatch by key

iru-code-one-task-group and iru-plan both need to find which language/framework-specific skill applies, without ai-catalog itself hardcoding a list of every language it might ever support. Both do it the same way — a filesystem glob against the installed skill directories (-code-one-task-group here, -code-one-task in iru-plan), then string manipulation on the matching names:

Example 5. ai-catalog: iru-code-one-task-group/SKILL.md — Step 1, "Resolve each task’s language/framework key"
Find which `*-code-one-task-group` skills are actually installed in this repository:
`find .claude/skills -maxdepth 1 -type d -name "*-code-one-task-group"`. The directory name minus its `iru-`
prefix and its `-code-one-task-group` suffix is the key (e.g. `iru-java-code-one-task-group` → `java`,
`iru-typescript-code-one-task-group` → `typescript`, `iru-android-code-one-task-group` → `android`,
`iru-swift-code-one-task-group` → `swift`) — the
`iru-` prefix is only this catalog's marketplace-collision namespace, not part of the language/framework key
itself.

Partitioning tasks into per-key buckets this way means adding support for a new language is installing a new skill directory named to the same convention, not editing this orchestrator’s own logic — the generalised version of the pattern is: find <skills-dir> -name "<prefix>-*-<suffix>", strip the fixed prefix/suffix, and what remains is the key that selects the backend. See Code One Task Group for the full page, and Claude Code — Skills for how a skill’s args reach the callee.

This is the same discipline Writing Effective Skills covers under runtime detection over hardcoding, applied one layer up: instead of one orchestrator skill hardcoding "if java do X, if dotnet do Y", each backend ships its own skill named to a convention, and the orchestrator only needs to know the convention.

Fan-out / fan-in

A group marked Parallelizable: yes in implementation_plan.md is implemented by issuing every task’s Agent(…​) call in the same response, rather than one after another — this is what makes them run concurrently instead of serially:

Example 6. ai-catalog: iru-java-code-one-task-group/SKILL.md — Step 2, "Implement each task via `iru-java-code-one-task`"
- **Bucket marked `Parallelizable: yes`**: invoke `iru-java-code-one-task` for every task in the bucket concurrently
  — issue all of the `Agent` calls below together, in the same response, so they run in parallel rather than one
  after another:

Agent({
  description: "Implement <task N> via java-code-one-task",
  subagent_type: "iru-isolated-skill-executor",
  prompt: "Invoke Skill({skill: \"iru-java-code-one-task\", args: \"<the task's full text, including its exact
    implementation_plan.md checkbox line(s) for itself and its sub-tasks, its sub-tasks' own text, and any
    relevant Current code state context>\"}). Report back: the files touched, the tests added/updated, and
    whether the task stopped on a blocker instead of finishing.",
  run_in_background: false
})

See Java Code One Task Group for the full page. The fan-out happens only at this bucket level: iru-code itself runs the plan’s groups one at a time, in plan order.

This is fan-out: several agents, each in its own isolated context, each returning only a compact report (files touched, tests added, blocker or none). Fan-in is what the calling skill does with those reports — iru-java-code-one-task-group’s own next steps merge each parallel task’s result and then validate the whole bucket once (license headers, Javadoc, scoped tests, coverage, the full suite, code quality — each through its own `iru-gate-runner call), rather than re-running validation once per task. Running validation once per group instead of once per task is the reason the plan is grouped at all in the first place, and it is also why a group whose tasks genuinely depend on each other’s output is marked Parallelizable: no and run one at a time instead: fan-out only pays off when the tasks in a bucket do not read or write the same files. Claude Code documents the same two shapes generically — "Run parallel research" and "Chain subagents" — at Claude Code — Subagents, with the warning that every parallel result returns into the caller’s context, which is why each report here is kept compact.

Because iru-isolated-skill-executor declares tools: "*", each spawned task can use anything the parent could, but sees none of the parent’s own context — the isolation model Subagents & Custom Agents covers for a single subagent applies unchanged to every branch of a fan-out; what’s new here is only that several of them run side by side and their results converge back into one caller.

sequenceDiagram participant G as iru--code-one-task-group participant A1 as Agent: task 1 participant A2 as Agent: task 2 participant A3 as Agent: task N participant R as iru-gate-runner G->>A1: Agent() (same response) G->>A2: Agent() (same response) G->>A3: Agent() (same response) A1-->>G: files touched, tests, blocker? A2-->>G: files touched, tests, blocker? A3-->>G: files touched, tests, blocker? G->>R: Agent() per gate, once for the whole bucket R-->>G: compact summary only

Durable state in files, not in context

A context window does not survive a compaction, a crash, or the user closing the session — so the pipeline never treats it as the record of what has been done. Three files carry that instead:

  • implementation_plan.md’s checkboxes. Every task and sub-task is a Markdown checkbox (- [ ] Task 3. …​` / - [x] Task 3. …​); a group where every task is already [x] is treated as already done and skipped, which is what makes a re-run of /iru-code after an interruption resume rather than restart:

    ai-catalog: iru-code/SKILL.md — Step 2
    this makes the skill resumable across interrupted runs. A group where every task is already `[x]` is itself
    done; skip it entirely in Step 4.
  • The Base branch: line. iru-issue writes it into the plan’s Task summary once it resolves the branch the work forks from, specifically so a later, separate session — one with no memory of this conversation — can recover the same target when it later runs /iru-code and /iru-pr-description by hand:

    ai-catalog: iru-issue/SKILL.md — Step 5
    Base branch: <base-branch>
    This is what lets a later, separate session recover the choice: in the "review manually" path below, the user
    runs `/iru-code` and `/iru-pr-description` themselves, with no memory of this conversation, and
    `iru-pr-description` reads this line (from `implementation_plan.md`, or from its archived copy under
    `.archive/`) to target the pull request at the same base branch instead of falling back to the repository
    default.
  • .archive/. Once every task is done, iru-code’s own closing step moves the finished plan out of the way so a fresh `/iru-plan run does not collide with it, but keeps it discoverable as precedent for the next ticket:

    ai-catalog: iru-code/SKILL.md — Step 9
    Create the archive directory if it doesn't exist yet (`mkdir -p .archive`), then move the plan there, renamed
    to embed that identifier: `mv implementation_plan.md .archive/implementation_plan_<ticket_id>.md` ...

    `iru-explore’s own Step 4 reads this same directory back on the next ticket, looking for a prior plan whose task headings resemble the current request, so a related past change becomes visible context rather than something only a person would remember to go dig up.

Generalised, the rule is: anything a later session — or a later step in this session, after a compaction — needs to recover belongs in a file the pipeline already reads on its next pass, not only in a TaskUpdate call or a sentence typed into chat.

Live task lists

Alongside the plan file, iru-code also mirrors the plan’s task groups into the session’s own live task list with the TaskCreate tool, one entry per pending group, and marks each one TaskUpdate`d to completed in step with checking off its boxes in `implementation_plan.md — confirmed as documented Claude Code tools at the Claude Code Tools reference (TaskCreate, TaskUpdate, TaskList, TaskGet). That page also notes that, as of 2026-09-28, the Task tools are provided by default only on older models (Claude 3.x, Opus 4 through 4.7, Sonnet 4 through 4.6, Haiku 4.5); on newer models a session gets them only when it opts in, for example with CLAUDE_CODE_ENABLE_TODO_TOOLS=1 or by naming one in --allowedTools, so a pipeline that relies on the live list should opt in explicitly:

Example 7. ai-catalog: iru-code/SKILL.md — Step 3, "Track progress"
Mirror the plan's task groups into the session's task list (`TaskCreate`), one per pending group, so the user
can see live progress at the group level. Mark each `TaskUpdate`d as completed as you finish it, in step with
the plan-file confirmation in Step 5. This is in addition to, not a replacement for, updating
`implementation_plan.md` -- the plan file is the durable record; the task list is the live view.

The distinction the excerpt draws is the one to keep: the task list is a live view for whoever is watching the session right now, rebuilt fresh each run and gone once the session ends; `implementation_plan.md’s checkboxes are the durable record a different session, or the same session after an interruption, reads back to know what already happened. Keeping both current, but never treating the task list as a substitute for the file, is what lets a group finish cleanly whether or not anyone is watching the live list at the time.

Recovering across sessions

Put together, the pipeline’s recovery story does not depend on any one session staying alive:

  • A crash or a closed terminal mid-iru-code run leaves implementation_plan.md with some tasks [x] and others [ ] — a fresh /iru-code invocation in a new session reads the same file, skips every group that is already fully done, and resumes at the first pending one.

  • A plan reviewed manually rather than handed off automatically (iru-issue’s Step 6 "review manually" path) can sit untouched indefinitely; whenever the user is ready, `/iru-code and /iru-pr-description pick it back up from the plan file alone, including the Base branch: line neither of those skills' this invocation ever saw written.

  • Once the ticket is done and the plan archived, the next ticket’s /iru-explore run checks .archive/ for a precedent before planning starts, so institutional memory about how a similar change was structured survives past the session that made it, without anyone copying context by hand.

None of this needs a database, a queue or a persistent process — the repository’s own working tree, read back on each new invocation, is the pipeline’s only shared state.