Decisions

ADR-171: Player + Pipeline + Blocks as the Unattended Execution Model

This ADR locks the design *now* while the substrate paper is fresh, but the architecture it commits to is gated on prior phases shipping evidence:

#171

ADR-171: Player + Pipeline + Blocks as the Unattended Execution Model

Status: Proposed Date: 2026-05-07 Tracker: warp#1881 (Phase 0 of EPIC warp#1838) Cross-refs: ADR-118, ADR-124, ADR-166, ADR-170 (sibling — the threat model this design satisfies)

Status note — Proposed not Accepted

This ADR locks the design now while the substrate paper is fresh, but the architecture it commits to is gated on prior phases shipping evidence:

  • Stage B (warp#1762, ADR-166) must prove the receiver/executor pattern on deploys.
  • Stage C (warp#1763) must prove the queue-driven CI pattern on one workflow.
  • Stage D (warp#1838) generalises both.

Reclassify to Accepted when Stage C4 (the prototype's "what breaks" doc) lands and the design survives. See docs/features/fleet-runtime-activation-arc.md for the full activation arc.

Context

The fleet's unattended work runs as Claude Code sessions on the operator's laptop. The naive shape for moving that work onto a server — "put claude -p in a container with the team's credentials" — is foreclosed by ADR-170's threat model. So what is the right shape?

Three primitives can build it.

The substrate paper (docs/proposals/unattended-pipeline-execution-substrate.md, shipping in this PR) names the design: player + pipelines + blocks. ADR-118 (per-block validation) and ADR-124 (pipeline tooling is block-composition) are the prior principles this ADR operationalises. ADR-166 is its smaller-scope precedent: receivers and executors are players; deploys are pipelines.

Decision

The substrate's unattended-execution model has three primitives. Workers run a player; players execute pipelines; pipelines compose blocks.

Player

A small runtime — a few hundred lines of Go — whose only job is to execute a pipeline. The player:

  • Pulls jobs from the queue (substrate-paper §3, ADR-172 for the contract).
  • Validates the contract pre-flight (fail-closed on schema skew or scope-unobtainable).
  • Requests scoped credentials from the vault per pipeline declaration.
  • Spawns each block in a sandboxed subprocess.
  • Streams structured trace events to the audit log.
  • Reports completion with the output manifest.
  • Releases credentials and dies.

The player has no compiler, no shell, no git, no gh, no claude binary baked in. It is the supervisor; capability comes from the blocks it runs.

The player is what ADR-166 calls an executor and what warp#1763 calls a runner agent. Same primitive, different vocabularies. See docs/features/fleet-runtime-activation-arc.md and the Fleet Runtime paper §3.1 for the vocabulary mapping.

Pipeline

A declarative YAML composition of blocks. Versioned by id + integer (e2e-run@v3). Content-addressed by SHA-256; the runtime rejects mismatches. Each pipeline declares its inputs (with JSON Schemas), its outputs (with JSON Schemas + retention), its scope (credentials + network egress + filesystem + privacy classification — per ADR-170), its contract (timeout, heartbeat cadence, cancellation semantics), and its steps (block invocations with input/output mappings).

A pipeline is what ADR-166 calls a playbook and what warp#1763 calls a workflow. The same vocabulary mapping.

Block

A bounded primitive. Each block is a small program (a binary, a script, a container, a Go module) with declared input schema, output schema, and scope. Blocks are independently security-reviewed. Blocks ship with regression suites (per ADR-118). The substrate paper §6.4 specifies the v0 set: gh-query, warp-add, file-template, model-call, clone-repo.

Blocks are a primitive the deploy and CI vantages don't have today (their playbooks/workflows are monolithic units). When those vantages graduate from monolithic to block-composition, they inherit the substrate's primitives directly.

The trust boundary line

This ADR draws an explicit line:

Context Full-agent claude -p (Bash, Read, Write, Edit, network) Bounded model-call block (string in, string out, broker-routed)
Operator's laptop (interactive, human present) First-class. Yes, when called from a script. Not the dominant pattern on the laptop.
CI runner / Actions cron Only via the player + bounded block model. Tools available to the agent must be those declared by a pipeline's block composition. First-class. The dominant pattern for off-laptop scheduled work.
Server-hosted worker container (unattended) No. Structurally incompatible with ADR-170's threat model. First-class. The dominant pattern for server-hosted work.

The line is trust-boundary-shaped, not autonomy-shaped. The substrate is comfortable with autonomous unattended work; what it forecloses is placing high-value credentials and full tool access in a container an attacker can reach.

Consequences

What becomes easier

  • New use cases ship as new pipelines (declarative YAML). The block library grows by accretion of small, reviewed primitives.
  • Audit + replay are first-class. Every block invocation is (block-id, input-hash, output-hash, exit-code, duration). A claude -p session in a container is a non-deterministic transcript; a pipeline run is structured data.
  • Composability. The same model-call block serves observers, e2e summarisers, feature-impl review steps. The same clone-repo block serves all of them.

What becomes harder

  • Improvisational flexibility goes away. A claude -p session can decide on the fly to read another file, run another command, branch the plan. A pipeline can only do what its block composition declares. For unattended execution against real codebases and real credentials, this rigidity is a feature.
  • Author cost. Writing a pipeline costs more than running a script. The tradeoff is that pipelines are reviewable, auditable, retryable, schedulable.

What stays out of scope

  • Full agent flexibility on the operator's laptop is preserved. The substrate doesn't constrain interactive work.
  • The existing gyrum-pipelines library (private alpha v0.0.x, four packages: pkg/block, pkg/pipeline, pkg/runner, pkg/retro) is the v0 substrate the player composes against. The substrate doesn't replace that library; it adds the runtime + queue + worker hardening + scope contract on top.

Compliance / how reviewers check

A PR that adds a pipeline or block satisfies this ADR iff:

  • The pipeline declares the full structure (inputs, outputs, scope, contract, steps) per ADR-172's contract.
  • Blocks are bounded — declared input/output schemas, declared scope (credentials + network + filesystem), regression corpus.
  • No claude -p with full tool surface in any unattended worker. Full agents only on the operator's laptop.

Cross-link

The substrate paper Part II §2.2-2.5 walks through the trust-boundary line with the per-line reasoning. ADR-172 (this PR) specifies the contract that connects pipelines to players. ADR-166 is the smaller-scope precedent. ADR-118 + ADR-124 are the prior principles.