No role in gateline knows which model executes it. Roles are contracts over files, models are bindings resolved in a single registry, and runtimes attach through thin, generated adapters. That separation is enforced in the repository, and it is what lets an adopter change vendors, harnesses, or both without rewriting the pipeline.
The principles, and the two that do the work
Six principles govern the design: artifacts over conversation (P1); roles
are contracts, models are bindings (P2); a runtime-neutral core with thin
adapters (P3); a human at phase gates (P4); decorrelation where it counts
(P5); and extension by adding roles, not tuning knobs (P6). Two of them do
the neutrality work. P2 rules out hardcoding a claude-* or
gpt-* identifier in any role definition, and the rule is
enforced as a repository invariant: no vendor or model name may appear in
roles/ or contracts/. Model IDs live only in the
registry.
P5 is the technical argument. Models from the same family share blind spots: an error the implementer's model reliably makes is often an error that same model reliably fails to catch. Binding the Reviewer and Verifier to a different lineage than the Implementer decorrelates those failure modes cheaply. The design's name for the ruled-out alternative is the same model grading its own homework.
A closed role roster
The software development lifecycle distills to eight operations, and the roster maps them to eight roles. The Orchestrator decomposes intent and routes work without ever writing content. The Analyst turns intent into a testable spec. The Architect produces the technical plan, work breakdown, and decision records. The Implementer executes one work item on a branch. The Reviewer reads the diff adversarially against spec and plan. The Verifier independently exercises the behavior end to end. Ops prepares the release and rollback plans. The Historian periodically reconciles the surrounding prose (docs, changelog, tracker) with the run record. Reviewer and Verifier are separate on purpose: review is reading, verification is running, and collapsing them recreates rubber-stamp reviews.
The roles/ directory holds one more spec: the Integrator, the
role that adopts the framework into a host repository, with its own gate
(GI). It is not a ninth SDLC role: the pipeline roster is eight roles plus the
Integrator. The set is closed: adding a role takes an explicit maintainer
decision recorded in an issue, because P6 makes roles the only extension
surface. The Historian is the worked example: when doc drift became painful,
the role cost one spec, one contract, and one registry binding, with no
change to any other role or contract.
DESIGN.md §4.2 draws the line finer. Implementer, Reviewer, Verifier, and the Orchestrator are evidence-bearing positions: the record's claims are about them, so they are as closed as the gates. The other roles are closed by governance. A host repository may add roles of its own, and the rule for them is open table, closed gates: an adopter role may put more evidence on an existing gate's table, and it may never mint a gate.
Each role spec carries its mission, operating instructions, definition of done, and explicit escalation triggers, with frontmatter declaring its capabilities and a capability profile, a tier of model ability, not a model. One role is never rendered to a runner at all: the Orchestrator is executed by a human in v0 or by the engine in v1, so no adapter ships an agent file for it.
One file owns the models
registry/models.yaml is the only place vendor and model IDs
exist. Roles bind to capability profiles instead of models:
frontier-reasoning for where judgment concentrates
(architecture, review, orchestration, cost-insensitive because these roles'
errors are the expensive ones), balanced for the workhorse
tiers, and fast-cheap for high-volume, low-stakes work. A
bindings map attaches each role to a profile, and the Reviewer and Verifier
entries carry avoid_vendor_of: implementer, the P5 pin written
as data, so a human dispatching by hand or the engine dispatching
automatically can enforce it.
Resolution runs role → capability profile → registry profile → concrete vendor/model ID, and then to the dispatching adapter's own spelling of that ID. That spelling is what lands in the ledger and what the pricing lookup keys on, so a dispatch is priced and recorded as what actually ran. Changing vendors is an edit to the registry, then re-spelling the binding in each adapter's manifest and re-rendering; no role spec or contract ever changes. The same file holds the pricing map used to meter dispatches and the static per-role estimates behind the pre-flight budget check.
Two honesty notes. The shipped model IDs are marked ILLUSTRATIVE: pin your organization's approved models and re-verify pricing when you do. And the OpenRouter pricing entries are a wired fallback off the live path: the opencode adapter reports real per-dispatch cost directly, and the entries key the token-based fallback if that report ever stops carrying cost.
Adapters are rendered, not written
Three adapters ship. claude-code renders
.claude/agents/ subagents. copilot-cli renders
.github/agents/ custom agents. opencode renders
.opencode/agents/ agents against its any-provider catalog.
gateline render, from the Node package
@gateline/framework, generates each agent file from the role
spec (the body verbatim, because agents start cold and the spec doubles as
their instructions) plus the adapter's manifest.json, which
supplies the frontmatter shape, the capability-to-tool map, and the runner's
model spellings. That package is the only reader of role specs and
manifests, and it takes no runtime dependencies on purpose, so a host
repository's render check (node packages/framework/src/main.ts render
--check) runs with nothing installed. Rendered files carry a
do-not-edit header, and CI fails any pull request whose renders are stale:
drift between a role spec and what a runner executes is structurally
impossible.
An adapter may narrow a role (fewer tools, tighter permissions) but never
widen it; the role spec is the ceiling. In the opencode adapter the
rule is a rendered property: each agent carries a deny-by-default permission
map, so a harness capability the role never granted stays denied. The v1
engine dispatches through the same manifests: a headless section
gives the invocation template and the usage-report parsing spec, so nothing
above the dispatch seam knows which harness ran. Supporting a new runner
costs one manifest. The roles are never restated per runner.
The decorrelation gradient, stated honestly
gateline supports cross-vendor pinning by design, and it is exercised where the harness allows. The three adapters sit at different points on that gradient, and the framework's own documentation says so.
| Adapter | Model surface | P5 decorrelation |
|---|---|---|
claude-code | One vendor's models | Partially honored. The interim fallback is a lineage
split: the reviewer runs a different model lineage
(fable) than the implementer
(sonnet). |
copilot-cli | Anthropic, OpenAI, and Google models natively | Fully honored. The manifest binds the
reviewer and verifier to different vendors than the implementer
(illustrative IDs: gpt-5.4 and
gemini-3-flash against a claude-sonnet-5
implementer). Its usage report is static-estimate: until
per-invocation usage is verified live, metering records the registry's
per-dispatch estimate with token counts null. |
opencode | Any provider in its Models.dev catalog | Three-way in its proof-of-concept bindings. Implementer, reviewer, and verifier land on three distinct labs (DeepSeek, Moonshot, MiniMax), where "vendor" means the underlying lab, not the OpenRouter routing layer. |
Where more than one vendor is live, enforcement is mechanical: the dispatch seam refuses to bind the Reviewer or Verifier to the Implementer's vendor. The run records show the gradient as well: in the human-orchestrated dupefind run, every implementer ledger entry is one model lineage and every reviewer entry another, under the single vendor that harness hosts.
The neutrality claim, whole: the judgment encoded in roles and contracts is portable; model preference is a registry edit; runner preference is one manifest. Each piece is checkable in the repository, and none of it requires trusting a vendor's roadmap — including the framework's own.
For manifest schemas and the headless sections, see the adapter manifests reference; for the handoff formats the roles exchange, the contract grammar reference. For the packages that implement the engine, server, CLI, and UI, see the API documentation.