# Sk Platform Dispatch

> Resolves the active runtime tier and provides the canonical DISPATCH contract for all pipeline step execution. Use when an orchestrator skill needs to dispatch pipeline steps — performs tier detection, loads the platform profile, and executes the Tier 2 inline loop when no subagent primitive is available.

- Skill: `gustavo-meilus/sk-platform-dispatch` (Agent Skill, multi-file: 36 files)
- Install (CLI): `npx skillmds@latest add gustavo-meilus/sk-platform-dispatch`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gustavo-meilus/sk-platform-dispatch/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: gustavo-meilus (https://skillmd.com/u/gustavo-meilus)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/gustavo-meilus/sk-platform-dispatch

---


# Platform Dispatch — Tier Detection & Tier 2 Inline Execution

<overview>
Superpipelines runs across five runtime tiers (Tier 1 Claude Code, Tier 1b OpenCode, Tier 1c Antigravity, Tier 1d Codex, Tier 2 IDE agents). Only Tier 1 has a skill-callable `Task()` primitive. Tier 1b/1c/1d use model-driven or platform-native subagent dispatch outside the skill layer. Tier 2 has no subagent primitive — the orchestrator executes every step inline using its own toolset. This skill encapsulates that branch so orchestrator skills remain tier-agnostic.
</overview>

<glossary>
  <term name="Tier">Runtime execution capability class. Tier 1 = skill-callable parallel subagents; Tier 2 = single-agent inline.</term>
  <term name="DISPATCH">The contract for executing one topology step: input schema, execution mechanism, output schema, status return.</term>
  <term name="Inline Step">A pipeline step executed by the orchestrator itself rather than by a spawned subagent.</term>
</glossary>

## Tier Detection Protocol

<protocol>
DETECT() returns a platform profile object (not a raw tier string).

Detection heuristics run in order — first match wins. **Override:** `SUPERPIPELINES_FORCE_TIER` env var, when set to a known tier id (`tier_1 | tier_1b | tier_1c | tier_1d | tier_2`), bypasses all heuristics. Use when detection picks the wrong tier in a multi-platform workspace.

1. **Tier 1 (Claude Code):** `Task` tool present AND `subagent_type` parameter accepted. Secondary: `CLAUDE_CODE` env var set OR `.claude-plugin/plugin.json` resolvable via `${CLAUDE_PLUGIN_ROOT}`. (Runtime capability is the primary signal; the secondary check is acceptable here because DETECT() runs in the live runtime where the Task primitive can be probed directly.)
2. **Tier 1b (OpenCode):** `$OPENCODE_CONFIG_DIR` env var set OR agent files using `mode: subagent` frontmatter present under the active scope root.
3. **Tier 1c (Antigravity):** `agy` binary on PATH.
4. **Tier 1d (Codex):** `codex` binary on PATH OR `.codex-plugin/plugin.json` resolvable.
5. **Tier 2 (fallback):** None of the above. Safe default — sequential inline execution always works.

After resolving `tier_id`:

```
READ(skills/sk-platform-dispatch/profiles/{tier_id}.json) → profile object
```

Return the full profile object. Caller caches it in `pipeline-state.json` as `metadata.platform_profile` and sets `metadata.runtime_tier = profile.tier`.

### Platform-specific skill-load tool names

When calling skills from within a running-a-pipeline orchestration, use the correct tool name for the current platform:

| Tier | Skill-load tool | Note |
|---|---|---|
| Tier 1 (Claude Code) | `Skill` | `Skill(superpipelines:sk-platform-dispatch)` |
| Tier 1c (Antigravity CLI, plugin installed) | `activate_skill` | `activate_skill(sk-platform-dispatch)` |
| Tier 1b (OpenCode) | `skill` (lowercase) | OC tool naming convention |
| Tier 1d (Codex) / Tier 2 | N/A — no skill tool | Use `running-a-pipeline` INLINE-DETECT() fallback |

**Antigravity CLI (Tier 1c) — installation requirement:** The superpipelines plugin must be installed in AGY's extension registry for `activate_skill` to resolve it. If superpipelines is only installed in Claude Code, `activate_skill` will fail to resolve the skill even though `skill_tool: true` in the profile. In that case, `running-a-pipeline` Phase 0.25 INLINE-DETECT() handles the fallback automatically. This is expected behavior for cross-platform handoff scenarios where not all platforms share a unified plugin registry.

### Profile capability fields

| Field | Purpose |
|---|---|
| `dynamic_subagents` | If true, user picks orchestrator tier only; per-step assignment skipped |
| `model_field_format` | `shorthand` (CC) \| `provider_prefixed` (OC) \| `toml_split` (Codex) \| `omit` (Tier 2, Antigravity) |
| `effort_field_name` | Name of the reasoning-effort key in agent files (null = platform has no effort field) |
| `effort_field_applies_to_providers` | List of provider prefixes for which effort emits (null = all) |
| `effort_emit_map` | Translation table for effort values (identity on current profiles; live Codex 0.142.5 accepts `low` verbatim and fails `minimal` when tools are exposed) |
| `subagent_env_override` | Env var that forces all subagents to one model (CC `CLAUDE_CODE_SUBAGENT_MODEL`) |
| `subagent_inherit_target` | What `inherit` resolves to natively (`session`, `primary`, `orchestrator`) |
| `provider_families` | Provider prefixes this platform accepts |
| `model_tiers_version` | ISO date stamp for drift detection |
| `model_tiers[*]` | 4-tier table: `triage`, `fast`, `medium`, `deep` — each with `model`, `effort`, optional `free_tier`, `quota_class` |
</protocol>

<invariant>
Tier detection is performed exactly once per orchestrator invocation. On resume: re-run DETECT() to get `runtime_tier`; compare to `metadata.source_tier`; if different, append to `metadata.tier_changes` and emit cross-tier resume advisory. Re-detection mid-run (outside resume) is forbidden.
</invariant>

## Model Resolution Hook

Before DISPATCH executes any step, callers MUST load `sk-model-resolver` and call `RESOLVE(agent, profile, prefs)` for each topology step. The resolved object is persisted to `pipeline-state.json metadata.resolved_models[step_id]` and consulted by DISPATCH (`native_task` model override; `native_subagent` payload; `toml_split` agent file rewrite; `omit` = no-op).

`running-a-pipeline` Phase 0.45 handles this for run-time execution. `creating-a-pipeline` Phase 4 uses resolution only for the preview table in Phase 5 approval; architect output writes `model_tier:` and never `model:`.

## DISPATCH Contract

<schema>
Inputs to DISPATCH(step, inputs, profile):
- `step.id`             — string, topology node id
- `step.agent`          — string, agent name (= CAD `name`; becomes the materialized `subagent_type`)
- `step.agent_def`      — string, path to the canonical agent def (CAD) RELATIVE to DATA_ROOT
                          (`pipelines/{P}/agents/{agent}.md`). Present for data-only pipelines.
                          Absent for legacy old-root pipelines → no materialization (BC3 fallback).
- `step.protocol_skill` — string, the `{agent-name}-protocol` skill name (LEGACY old-root pipelines only)
- `step.output_paths`   — array of absolute paths the step is expected to produce
- `inputs`              — object, key/value inputs resolved from upstream step outputs
- `profile`             — platform profile object from DETECT()

Returns:
- `{ status: "DONE" | "DONE_WITH_CONCERNS" | "NEEDS_CONTEXT" | "BLOCKED", outputs: [path...], concerns?: string, missing_context?: string, blocker?: string }`
</schema>

## Option A — Materialize-at-Runtime (CAD → native)

Data-only pipelines store each agent as a tool-neutral **canonical agent def (CAD)** under
`DATA_ROOT/pipelines/{P}/agents/{agent}.md` (frontmatter + inline protocol body). Before a
subagent-capable tier dispatches a step, the orchestrator MATERIALIZES that CAD into the
tool's native agent dir as an ephemeral file, dispatches natively, then treats the file as
disposable cache. The CAD is the single source of truth; the native file is regenerated every
run and **never read as source**. Schema + translation table: `pipeline-auditor-references/references/canonical-agent-def.md`.

MATERIALIZE is tier-neutral: it selects the native-agent dir and the translator from the
profile — never from a hardcoded tier string or path (per `DEPENDENCY_INVERSION: PROFILE_DRIVEN`).
Adding a new subagent-capable tier = adding its `extensions.native_agent_dir` + a `TRANSLATE_CAD_TO_*`
branch; no other MATERIALIZE edit.

```
MATERIALIZE(agent_def_path, resolved, profile, P, scope) → subagent_name:
  cad = READ(RESOLVE_DATA_ROOT(scope) + "/" + agent_def_path)   // tool-neutral frontmatter + inline body
  assert cad.schema_version supported            // else BLOCKED: unsupported CAD schema
  // native agent dir + file extension are per-tier facts, sourced from the profile:
  target = RESOLVE_SCOPE_ROOT(scope, profile.tier) + "/" + profile.extensions.native_agent_dir
           + "/" + P + "/" + cad.name + profile.extensions.native_agent_ext   // .md (CC/OC) | .toml (Codex)
  ensure_dir(dirname(target))
  // translator is selected by dispatch_mechanism — NOT by tier string. Each translator returns
  // { content, body_inlined }: YAML tiers return frontmatter (body appended below); the TOML tier
  // returns the complete file with the protocol embedded as an `instructions` string.
  SWITCH profile.capabilities.dispatch_mechanism:
    "native_task"     → t = { content: TRANSLATE_CAD_TO_CC(cad, resolved),          body_inlined: false }
    "native_subagent" → t = { content: TRANSLATE_CAD_TO_OC(cad, resolved, profile), body_inlined: false }
    "model_driven"    → IF profile.capabilities.model_field_format == "toml_split":  // Codex
                          t = { content: TRANSLATE_CAD_TO_CODEX(cad, resolved, profile), body_inlined: true }
                        ELSE: BLOCKED — no CAD materializer for this model_driven tier
                          (Antigravity dynamic subagents are dispatched without a materialized file)
  // Filesystem isolation degrades on tiers without a worktree primitive (reviewer isolation is
  // unaffected — it is enforced structurally in the translated agent file, not by the worktree):
  IF cad.isolation_required == true AND profile.capabilities.worktrees == false
     AND profile.extensions.isolation_unavailable_warning present:
       w = profile.extensions.isolation_unavailable_warning with "{name}" → cad.name
       surface w to the user; append w to metadata.isolation_warning (atomic)
  file = t.body_inlined ? t.content : (t.content + "\n\n" + cad.body)
  WRITE(target, file)                             // native agent = native config + inline protocol
  return cad.name                                 // subagent_type (CC) / subagent name (OC) / agent name (Codex)
```

**Runtime registration assumption (load-bearing for Option A).** `Task(subagent_type=cad.name)`
resolves the agent file MATERIALIZE just wrote under the workspace/user `.claude/agents/`
directory. Claude Code resolves agent files present on disk under the active scope root at
dispatch time, so a file written earlier in the same session is dispatchable. IF a materialized
`subagent_type` fails to resolve at `Task()` time on a given host, DISPATCH MUST return
`BLOCKED` with: *"Materialized agent '{name}' did not resolve as a subagent_type on this host —
Option A requires same-session agent-file discovery. Verify CC picks up agent files written
under `.claude/agents/superpipelines/{P}/` at dispatch time."* NEVER silently fall back to a
generic subagent (that would drop the structural isolation the CAD encodes).

`TRANSLATE_CAD_TO_CC(cad, resolved)` emits CC YAML per the canonical-agent-def §3 table:

```
name:        cad.name
description: cad.description          // required by CC agent-file discovery
model_tier:  cad.model_tier          // resolved.model is passed at the Task() call, not the file
// effort is profile-gated, never hardcoded (CC accepts low|medium|high|xhigh|max — our
// vocabulary maps identity, so no emit map is configured for tier_1):
IF resolved.effort != null AND profile.capabilities.effort_field_name != null:
  {profile.capabilities.effort_field_name}: resolved.effort   // e.g. effort: medium
maxTurns:    cad.turn_budget
// capability intent → CC enforcement primitive (structural):
IF cad.capabilities.write_files == false:
  tools:           (Read/Glob/Grep + cad.tool_hints.allow ∩ read-class)   // excludes Write/Edit/Bash
  disallowedTools: Write, Edit, Bash
  permissionMode:  plan
ELSE:
  tools:           Read, Write, Edit[, Bash if run_shell][, web tools if network] (+ tool_hints.allow)
  permissionMode:  acceptEdits
IF cad.capabilities.run_shell  == false AND write_files == true: omit Bash from tools
IF cad.isolation_required == true: isolation: worktree
skills:  cad.protocol_skills          // bundle skills, still tier-discovered; omit if empty
```

**Reviewer isolation stays STRUCTURAL.** `capabilities.write_files: false` is the single
canonical source for reviewer write-deny; MATERIALIZE emits real `permissionMode: plan` +
`disallowedTools: Write, Edit, Bash` (and `isolation: worktree` for writers) — enforcement is
structural on `native_task`, not convention. This preserves `WRITE_REVIEW_ISOLATION:
STRUCTURAL_ON_TIER1_1B_1D` under the data-only model.

### OpenCode (Tier 1b) materialization

`TRANSLATE_CAD_TO_OC(cad, resolved, profile)` emits OpenCode `mode: subagent` YAML per the
canonical-agent-def §3 table. Provider-prefixed model + provider-gated `reasoningEffort` come
from the resolved object; the effort key name and the providers it applies to are read from the
profile (never hardcoded):

```
mode:        subagent
name:        cad.name
description: cad.description
model:       resolved.model               // provider-prefixed, e.g. opencode-go/qwen3.6-plus
maxTurns:    cad.turn_budget
// effort is provider-gated — emit only for providers the profile lists:
IF resolved.effort != null
   AND provider_prefix(resolved.model) ∈ profile.capabilities.effort_field_applies_to_providers:
  {profile.capabilities.effort_field_name}: resolved.effort   // e.g. reasoningEffort: high
// capability intent → OC enforcement primitive (structural); emit only the DENY keys:
permission:                               // omit the whole block ONLY if no key denies below;
                                          // a writer (write_files:true) still emits permission
                                          // when run_shell/network deny (e.g. webfetch: deny)
  IF cad.capabilities.write_files == false: edit: deny        // reviewer write-deny (structural)
  IF cad.capabilities.run_shell   == false: bash: deny
  IF cad.capabilities.network     == false: webfetch: deny
tools:  concrete OC tool list from cad.tool_hints.allow (capability-consistent); omit if empty
// isolation_required has NO OC worktree primitive → handled by MATERIALIZE degradation guard
```

**Reviewer isolation stays STRUCTURAL on OC.** `capabilities.write_files: false` emits a real
`permission: { edit: deny }` — the writer cannot mutate tracked source. This is the
`extensions.reviewer_isolation_recipe` for tier_1b and upholds `WRITE_REVIEW_ISOLATION:
STRUCTURAL_ON_TIER1_1B_1D`. OC has no worktree primitive, so a writer's `isolation_required`
degrades to a surfaced warning (filesystem isolation only) — the write/review boundary itself
never degrades to convention on OC.

**Runtime registration assumption (load-bearing for Option A on OC).** A subagent dispatched by
`name` resolves the `mode: subagent` agent file MATERIALIZE wrote under the active scope root's
`agent/superpipelines/{P}/` dir. OpenCode discovers agent files present on disk under the active
scope root at dispatch time. IF a materialized OC subagent fails to resolve by name at dispatch,
DISPATCH MUST return `BLOCKED` with: *"Materialized OC agent '{name}' did not resolve as a
subagent on this host — Option A requires same-session agent-file discovery. Verify OpenCode
picks up agent files written under `.opencode/agent/superpipelines/{P}/` at dispatch time."*
NEVER silently fall back to a generic subagent (that would drop the structural isolation the CAD
encodes).

### Codex (Tier 1d) materialization

`TRANSLATE_CAD_TO_CODEX(cad, resolved, profile)` emits a complete Codex agent **TOML** document
per the canonical-agent-def §3 table. Unlike the YAML tiers, the protocol body is embedded as a
triple-quoted `instructions` string (TOML has no frontmatter/body split), so the translator
returns the whole file (`body_inlined: true`):

```
name        = cad.name
description = cad.description             # REQUIRED by the live parser ("must define a description")
model       = resolved.model              # e.g. "gpt-5.4"
IF resolved.effort != null
   AND (profile.capabilities.effort_field_applies_to_providers == null              # null = all providers
        OR provider_prefix(resolved.model) ∈ profile.capabilities.effort_field_applies_to_providers):
  model_reasoning_effort = resolved.effort  # mapped by sk-model-resolver via effort_emit_map (identity); emit verbatim
# capability intent → Codex sandbox primitive (host-conditional structural, see below):
sandbox_mode = cad.capabilities.write_files ? "workspace-write" : "read-only"
# cad.turn_budget has NO Codex primitive — the live parser rejects `turn_limit` as an unknown
# field (verified 2026-07); do not emit it. The turn budget degrades to prompt-level guidance.
developer_instructions = """<cad.body verbatim>"""   # `instructions` is rejected by the live parser
# read-only ALSO denies shell-exec writes + network, so a reviewer (run_shell:false,
# network:false) is fully covered by sandbox_mode alone. For a WRITER that denies network, emit a
# [sandbox_workspace_write] table — and ONLY after all top-level keys (TOML table-ordering rule):
IF cad.capabilities.write_files == true AND cad.capabilities.network == false:
  [sandbox_workspace_write]
  network_access = false
```

**Reviewer isolation on Codex is STRUCTURAL, host-conditionally.** `capabilities.write_files:
false` emits `sandbox_mode = "read-only"` per `extensions.reviewer_isolation_recipe` — but
enforcement depends on the live host's sandbox helpers. Live verification (2026-07,
`docs/agents/verification/codex-discovery-2026-07.md` Probe D) showed a read-only agent writing
to the workspace when the parent session ran `danger-full-access` on a host without sandbox
support. Runtime guard (MANDATORY): before dispatching any `write_files: false` step on a
profile that defines `extensions.isolation_unsandboxed_warning`, determine whether the current
session's sandbox is disabled (`danger-full-access`) or host sandbox helpers are unavailable;
if so, surface that warning at dispatch, append it to `metadata.isolation_warning` (atomic),
and treat the step's review verdicts as ADVISORY for this run — never report them as
structurally enforced. On sandbox-capable hosts the read-only deny is structural and
`WRITE_REVIEW_ISOLATION: STRUCTURAL_ON_TIER1_1B_1D` holds. Codex has no per-subagent worktree
primitive (`worktrees: false`); it isolates per-thread at the app level, so a writer's
`isolation_required` carries no additional materialized primitive.

**Concurrency.** Codex fans subagents out per `topology.json`, capped at
`profile.extensions.max_concurrent_subagents` (6). The orchestrator must not dispatch more than
that many parallel branch workers at once; excess steps queue.

**Runtime registration assumption (load-bearing for Option A on Codex).** A subagent named
`cad.name` resolves the TOML agent file MATERIALIZE wrote under the active scope root's
`agents/superpipelines/{P}/` dir. IF a materialized Codex agent fails to resolve by name at
dispatch, DISPATCH MUST return `BLOCKED` with: *"Materialized Codex agent '{name}' did not
resolve on this host — Option A requires same-session agent-file discovery. Verify Codex picks up
agent TOML written under `.codex/agents/superpipelines/{P}/` at dispatch time."* NEVER
silently fall back to a generic subagent (that would drop the structural isolation the CAD encodes).

## Materialized-Cache Cleanup

The materialized native agent dir is disposable cache, owned by DISPATCH:

```
CLEANUP_MATERIALIZED(P, scope):     // called by orchestrator at Phase 4 on `completed`
  profile = metadata.platform_profile          // cached in pipeline-state.json by DETECT()
  // native agent dir + scope-root tier are profile-sourced — same dir MATERIALIZE wrote to:
  delete_dir(RESOLVE_SCOPE_ROOT(scope, profile.tier) + "/" + profile.extensions.native_agent_dir
             + "/" + P + "/")                 // scoped to {P} ONLY
```

- Delete ONLY the `{P}` subdir — never the parent `{native_agent_dir}/` (other pipelines' caches).
- The signature stays 2-arg (`P`, `scope`): every architect-emitted `entry.md` calls it that way;
  the profile is read from cached run state, keeping native dirs portable across tiers.
- On `escalated`/`failed`/`blocked`: leave the dir for debugging; it is regenerated next run regardless.
- DISPATCH ALWAYS re-reads the CAD and re-materializes each run — the cache is never authoritative.

## Tier-Specific DISPATCH Behavior

Skills branch on `profile.capabilities` flags — NOT on `profile.tier` string. This ensures unknown future platforms with familiar capabilities route correctly without skill edits.

```
mechanism = profile.capabilities.dispatch_mechanism
SWITCH mechanism:
  "native_task"     → IF step.agent_def present:                       // data-only pipeline
                        name = MATERIALIZE(step.agent_def, resolved_models[step.id], profile, P, scope)
                        Task(subagent_type=name, model=resolved_models[step.id].model,
                             prompt=build_prompt(step, inputs))
                      ELSE:                                             // BC3: legacy old-root pipeline
                        Task(subagent_type=step.agent, model=resolved_models[step.id].model,
                             prompt=build_prompt(step, inputs))         // agent already registered; no materialize
  "native_subagent" → IF step.agent_def present:                       // data-only pipeline
                        name = MATERIALIZE(step.agent_def, resolved_models[step.id], profile, P, scope)
                        dispatch OC native subagent (mode: subagent) by `name`, passing
                          resolved_models[step.id].model + reasoningEffort (when set) in the payload,
                          prompt=build_prompt(step, inputs)
                        if it fails to resolve by name → BLOCKED per OC registration assumption
                      ELSE: OC native mode:subagent dispatch via step.agent file (legacy)
  "model_driven"    → IF step.agent_def present:                       // data-only pipeline
                        IF profile.capabilities.model_field_format == "toml_split":   // Codex
                          name = MATERIALIZE(step.agent_def, resolved_models[step.id], profile, P, scope)
                          dispatch Codex native subagent by `name` (model + model_reasoning_effort
                            + sandbox_mode already in the materialized TOML); Codex fans out per
                            topology.json, capped at extensions.max_concurrent_subagents
                          if it fails to resolve by name → BLOCKED per Codex registration assumption
                        ELSE:                                          // Antigravity dynamic subagents
                          // No materialized file: AGY auto-manages dynamic subagents at the
                          // orchestrator tier (dynamic_subagents:true, model_field_format:"omit").
                          surface profile.degradation_warnings        // orchestrator-tier-only model + convention isolation
                          body = READ(RESOLVE_DATA_ROOT(scope) + "/" + step.agent_def).body
                          dispatch dynamic subagent with task = build_prompt(step, inputs) + body;
                            OMIT per-step model (host orchestrator owns subagent model selection)
                          // reviewer isolation is CONVENTION here — review output is a self-check
                          //   (the surfaced degradation says so); no structural write-deny exists
                      ELSE: Emit orchestration prompt; Codex model fans out per topology.json (legacy)
  "inline"          → Tier 2 inline loop (see Tier 2 Inline Loop below)   // data-only handled: reads CAD body
  DEFAULT (unknown) → fallback to "inline" + emit:
                      "⚠️ Unknown dispatch_mechanism '{mechanism}'. Falling back to inline execution."
```

<dispatch_tiers>
| `dispatch_mechanism` | Model resolution source | Effort handling | Reviewer isolation source |
|---|---|---|---|
| `native_task` | Resolved model passed in Task() payload (overrides agent frontmatter) | Emitted to the materialized agent file via profile `effort_field_name` (`effort:`; CC accepts `low..max`) | `profile.capabilities.reviewer_isolation = structural`; agent `tools:` restricts reviewer |
| `native_subagent` | Resolved model + reasoningEffort written to dispatch payload | Only for `opencode/*` and `opencode-go/*` provider prefixes | `structural`; OC `permission: { edit: deny }` on reviewer agent |
| `model_driven` (Codex) | Resolved model + model_reasoning_effort written to TOML | `effort_emit_map` (identity) — emitted verbatim | `structural` when host sandbox helpers are active; degrades to convention + surfaced `isolation_unsandboxed_warning` on unsandboxed sessions (danger-full-access) |
| `model_driven` (Antigravity) | Orchestrator tier only; subagents auto-managed | N/A (no per-subagent control) | `convention` |
| `inline` (Tier 2) | No emission — host IDE selects model | N/A | `convention` |
</dispatch_tiers>

## Tier 2 Inline Loop

<protocol>
The orchestrator (the model running the entry skill) executes every step using its own tools. There is no subagent boundary.

For each step in `topology.json` (dependency order):

1. **Load protocol**: data-only pipeline → `Read(RESOLVE_DATA_ROOT(scope) + "/" + step.agent_def)` and execute the CAD's inline protocol body as data (no subagent boundary on Tier 2). Legacy old-root pipeline (no `agent_def`) → `Skill(step.protocol_skill)`. Either way the agent's full protocol enters the orchestrator's context.
2. **Resolve inputs**: read upstream step outputs from disk using paths recorded in `pipeline-state.json[phases][upstream].outputs`.
3. **Execute inline**: the orchestrator performs the protocol's actions using `Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`. No `Task()` call.
4. **Persist outputs**: write all output files to the paths declared in `step.output_paths`.
5. **Self-verify**: run the protocol's stated verification steps inline. Capture pass/fail.
6. **Update state**: append phase entry to `pipeline-state.json` with `status`, `outputs`, `error` via the atomic write pattern from `sk-pipeline-state`.
7. **Status check**: emit one terminal status per the contract above.
8. **Branch on status**:
   - `DONE` → proceed to next step.
   - `DONE_WITH_CONCERNS` → read concerns; proceed if observational; address inline if correctness/scope.
   - `NEEDS_CONTEXT` → re-execute the step with added context loaded from the named files. Bounded retry: maximum 2 attempts.
   - `BLOCKED` → set `state.status = "escalated"`, preserve temp dir, surface to user with the blocker text, stop the loop.

**Parallel patterns (Pattern 2 / 2b) degrade to sequential on Tier 2.** The dispatch loop processes branch workers one at a time in declared order. A merger step receives all branch outputs only after each branch finishes sequentially.

**Iterative patterns (Pattern 3) execute inline.** The orchestrator runs tester → analyzer → fixer in the same session; cycle limit (3) and architectural-escalation gate from `dispatch-protocols.md` Pattern 3 still apply.

**Spec-Driven (Pattern 5) on Tier 2:** Tasks execute sequentially. Each task's two-stage review runs inline — the orchestrator reads `spec.md`, applies the spec-reviewer protocol, then applies the quality-reviewer protocol. **Reviewer isolation is convention-only.** The orchestrator runs both writer and reviewer protocols with its own full toolset. There is no structural barrier preventing a reviewer from writing. Document this degradation in any user-facing report and treat Tier 2 reviews as advisory, not structurally enforced.
</protocol>

## Per-Tier Scope-Root Resolution

<scope_roots_per_tier>
| Tier | Scope-root fallback chain (first writable wins) |
|------|-------------------------------------------------|
| Tier 1 (CC) | `<workspace>/.claude/` → `~/.claude/` |
| Tier 1b (OC) | `<workspace>/.opencode/` → `~/.opencode/` |
| Tier 1c (Antigravity) | `<workspace>/.agents/` → `~/.antigravity/` |
| Tier 1d (Codex) | `<workspace>/.codex/` → `~/.codex/` |
| Tier 2 (Cursor/Windsurf/Cline) | `<workspace>/.superpipelines/` (universal fallback — created on demand) |
</scope_roots_per_tier>

`sk-pipeline-paths` resolves scope-root by reading `metadata.runtime_tier` from the pipeline state and walking the chain above. For Tier 2, if a pipeline was scaffolded on CC (paths reference `.claude/`), the dispatch layer rewrites `.claude/` → `.superpipelines/` at read/write time so portable artifacts continue to resolve. This rewrite is invertible: state files stamp the original scope-root string for auditability.

<invariant>
Path resolution MUST consult `metadata.runtime_tier` for any artifact write on a non-CC tier. Hardcoded `.claude/` paths in scaffolding output break `ARTIFACT_PORTABILITY: DATA_ONLY_PORTABLE_ALL_TIERS` (see ADR-0003).
</invariant>

<invariant>
PORTABILITY_REWRITE is checked at two levels: (1) creation time — auditor criterion 22, SEV-1, blocking; (2) run time — `running-a-pipeline` Phase 0.6, scans entry skill and offers Abort/Rewrite/Proceed (user-acknowledged advisory, non-blocking). Every caller that reads or writes a CC-scaffolded path on a non-CC tier MUST route the path through `sk-pipeline-paths` (which performs the rewrite) OR call PORTABILITY_REWRITE directly. Direct string concatenation with `.claude/` on a Tier 2 run is a defect. Entry skills emitted by the current architect already comply; legacy entry skills regenerated for portability MUST be re-audited against this rule.
</invariant>

## Degradation Surfacing (Profile-Driven)

Degradation warnings are owned by the profile — not hardcoded in skills. When a profile has non-empty `degradation_warnings`:

```
warnings = profile.degradation_warnings
IF warnings is non-empty:
  1. Emit each warning at run START with "⚠️" prefix (running-a-pipeline Phase 0.25)
  2. Emit each warning at run END in entry skill completion summary
  3. Write join(warnings, "\n") to pipeline-state.json as metadata.isolation_warning
```

Adding or changing a degradation message for any platform requires editing only that tier's JSON profile. No skill edits required.

## Worktree Behavior

<worktree_rules>
| Tier | Worktree |
|------|----------|
| Tier 1 | Per-subagent via `isolation: worktree` agent frontmatter |
| Tier 1b | None (OC does not expose worktree primitive) |
| Tier 1c | Unverified |
| Tier 1d | Per-thread at app level (not per-subagent) |
| Tier 2 | None — orchestrator works in the user's active workspace |
</worktree_rules>

On Tier 2, the orchestrator MUST verify the workspace is clean (no uncommitted changes) before starting a destructive step, and MUST commit between steps to enable rollback. If the workspace is dirty, surface to user and stop.

## Cross-Tier Resume Protocol

Invoked by `running-a-pipeline` Phase 0.25 when resuming an existing run:

```
new_profile = DETECT()
new_tier    = new_profile.tier
prev_tier   = metadata.runtime_tier ?? metadata.tier  // backward compat

IF new_tier != prev_tier:
  append { from: prev_tier, to: new_tier, at: iso8601_now() }
          to metadata.tier_changes (atomic write)
  metadata.runtime_tier     = new_tier
  metadata.platform_profile = new_profile
  metadata.isolation_warning = join(new_profile.degradation_warnings)
  emit: "⚠️ Cross-tier resume: scaffolded on {metadata.source_tier},
         now running on {new_tier}.
         Dispatch adapts to {new_tier} capabilities."
  emit each new_profile.degradation_warning

ELSE:
  proceed silently (no tier change, no log entry)
```

`metadata.source_tier` is NEVER updated on resume. It records where the pipeline was originally scaffolded, permanently.

## Status Protocol Reference

| Worker status | Orchestrator action (any tier) |
|---------------|--------------------------------|
| `DONE` | Proceed to next phase. |
| `DONE_WITH_CONCERNS` | Read concerns. If correctness/scope: address before review. If observational: proceed. |
| `NEEDS_CONTEXT` | Identify missing context; re-dispatch with same model + added context. Max 2 retries on Tier 2. |
| `BLOCKED` | (1) provide more context; (2) higher effort/model (Tier 1 only); (3) decompose; (4) escalate. NEVER retry same approach. |

<invariants>
- NEVER perform tier detection more than once per run; cache result in `metadata.runtime_tier`.
- NEVER call `Task()` when `profile.capabilities.task_primitive` is false — the tool is absent and the call will fail or be ignored.
- NEVER suppress degradation warnings from `profile.degradation_warnings`; surface in every user-facing summary and write to `metadata.isolation_warning`.
- Tier 2 inline execution MUST update `pipeline-state.json` after every step, not at end of run.
</invariants>

## Red Flags — STOP

- "I'll skip tier detection since I know this is Claude Code." → **STOP**. Detection is cheap; profile caching enables resume, portability validation, and cross-tier advisory from any checkpoint.
- "I'll branch on `metadata.tier == 'tier_2'` instead of reading the profile." → **STOP**. Tier string branching breaks when new platforms arrive. Always branch on `profile.capabilities` flags.
- "I'll call `Task()` from the Tier 2 inline loop." → **STOP**. Tier 2 has no `Task()` primitive; the call fails. Use inline `Skill` + own tools.
- "Reviewer ran clean on Tier 2, so the code is verified." → **STOP**. Tier 2 reviewer isolation is convention-only. Surface the degradation to the user; do not promote advisory reviews to structural guarantees.
- "I'll re-detect tier after a tool failure to see if something changed." → **STOP**. Tier is immutable per run. A tool failure is a tool failure, not a tier change.

## Reference Files

- `pipeline-runner-references/references/dispatch-protocols.md` — Tier-specific dispatch shapes.
- `sk-pipeline-state/SKILL.md` — State schema (including `metadata.source_tier` and `metadata.runtime_tier`).
- `sk-pipeline-patterns/SKILL.md` — Pattern definitions referenced by Tier 2 inline loop.
- `running-a-pipeline/SKILL.md` — Loads this skill in Phase 0.25.

