A gateline run is one change (a feature, a fix, a piece of maintenance) carried from a one-page brief to a merged branch by a chain of specialized agents, with a named human approving at each phase boundary. Everything the run is, everything it has done, and everything it is waiting on lives in a single directory of files committed to git.
From conversation to artifacts
In the chatbot workflow most engineers know, the developer is the integration point: you hold the plan in your head, feed the model context piece by piece, and validate every output before it touches the repo. It works, but throughput caps at one conversation, and none of the intermediate reasoning survives the session. gateline moves the integration point into durable, typed artifacts committed to git: agents never share a conversation, they share contracts. Each role consumes specific files, produces specific files, and a human approves at phase boundaries. The pipeline's state is the repo.
Three properties fall out of that one decision. Any agent is replaceable
mid-flight: if an implementer's work is unusable, you discard its branch and
re-run the work item, because the spec and plan it worked from are unchanged
on disk. Models and runtimes are swappable independently of roles, because a
role spec says what an agent must consume, produce, and refuse to do — never
which vendor's model executes it. And the record is free: every decision,
review finding, and verification result is a file with git history, so "why
did we build it this way?" has a git log answer.
The lifecycle: four gates
At full weight a run passes four gates, each a named human's recorded decision that closes a phase. The Analyst turns the intent brief into a testable spec; at G0 the human answers "is this what we actually want built?" The Architect turns the spec into a technical plan cut into work items; at G1, "is this how we'd want it built, in safe parallel pieces?" Each work item then loops between an Implementer and a Reviewer, and a Verifier exercises the finished change end to end; at G2, "does the evidence support merging?" Finally the Ops role prepares release and rollback plans; at G3, "ship it?"
| Gate | The human's question | On the table |
|---|---|---|
| G0 — Spec | Is this what we actually want built? | intent-brief.md, spec.md |
| G1 — Plan | Is this how we'd want it built, cut into safe parallel pieces? | plan.md,
tasks/*.yaml, ADRs |
| G2 — Change | Does the evidence support merging? | diff, review report, verification report |
| G3 — Release | Ship it? | release plan, rollback plan |
Two caps keep the loop safe. Implementer ⇄ Reviewer cycles are capped at three rounds per work item; a fourth round becomes an automatic escalation to the human with both sides' artifacts. Agents arguing past three rounds are almost always stuck on an ambiguity in the spec, which is a G0 or G1 defect. The cap has an exit: resolving that escalation grants the loop one more round, and the engine asks again for each extra round. And each run carries a cost budget whose exhaustion pauses the pipeline rather than letting quality degrade silently.
Reviewer and Verifier are deliberately separate roles. Review is reading: does this code say the right thing? Verification is running: does the system do the right thing? Collapsing them recreates the rubber-stamp reviews seen in human teams.
The lifecycle has been exercised end to end. runs/dupefind/,
a completed human-orchestrated run at full weight (its state file predates
the profile field, and an absent profile: means
full), shows the shape the lifecycle leaves behind: three work
items each verified after two review rounds, a hand-appended budget ledger of
sixteen entries totaling $12.15 against a $50 limit, four gates approved by
the same named human with the first three noting
burden: confirmation in their notes, and zero escalations.
The run directory is the state
A run lives at runs/<slug>/ on its own branch,
run/<slug>, and the directory is the pipeline's
state; no state lives anywhere else. Artifacts are committed as they are
produced, so the git history is the record of what happened, in order. Once a
run merges, its directory is a historical record and is never
retro-edited.
runs/<slug>/
├── state.yaml # phase, profile, gates, budget, tasks, escalations
├── intent-brief.md # human-authored input
├── spec.md # Analyst → gate G0
├── ux-research.md # UX Researcher (design runs only)
├── design/
│ └── <candidate>/ # one Designer each (design runs only)
├── plan.md # Architect → gate G1
├── tasks/
│ └── NN-name.yaml # one work item per Implementer
├── review-NN.md # Reviewer, per task, per round
├── verification-report.md # Verifier → gate G2
├── release-plan.md # Ops → gate G3
└── retro.md # human observations after the run
At the center is state.yaml, the run's spine. It records the
phase and profile, and the budget as an append-only ledger
(one entry per model invocation, carrying timestamp, role, task, round,
adapter, model, tokens, and cost) from which totals are derived rather than
maintained, so concurrent writers and later audits survive. It also holds the
gate ledger (one entry per gate with who approved, when, notes, and an
optional burden rating of how much work the review took),
task statuses and review-round counts, and an append-only escalation list.
A human or an engine decides what happens next from this file, the artifacts
committed beside it, and the branch order in which they landed.
Two rows of the tree are not roster output. ux-research.md
and design/ come from extension positions that design runs such
as runs/fleetview-design/ prototyped inside their own records,
as DESIGN.md §4.2 allows; they are not roles, and only the eight roster roles
have contracts. retro.md is human notes and has no contract
either.
Every artifact the roster's roles hand off follows a contract from contracts/:
required sections, concision budgets, a parseable ID and heading grammar, and
readability rules for the sections a human reads. All of these are equally
bounceable. An artifact missing a required section is malformed, and the
consuming agent's first duty is to bounce it back to its producer, never to
guess. A breach of a readability rule is bounced the same way, with the rule
cited. The handoffs therefore fail loudly or not at all.
Profiles: ceremony scaled to the change
Not every change should pay for the full pipeline. A run profile declares, per run, which roles run and which gates exist. There are three profiles, they are fixed sets, and they nest:
| Profile | Roles that run | Gates | Phase sequence |
|---|---|---|---|
patch | Implementer, Reviewer | G1, G2 | plan → implement → integrate → done |
standard | + Analyst, Architect, Verifier | G0–G2 | spec → plan → implement → integrate → done |
full | all eight | G0–G3 | spec → plan → implement → integrate → release → done |
Every sequence ends at done, and every profile can also end
at closed, the second terminal phase. A named human ends a run
short of done with gateline close <slug> --as
<disposition> --reason <text>, where the disposition is
one of already-delivered, superseded,
obsolete, or abandoned. The engine never closes a
run. Closing deletes nothing, and gateline reopen undoes it in
a commit of its own.
patch is for bug fixes and small bounded changes: the human
authors the intent brief and the single work item at creation
(gateline new --task-file <path> stages it as
tasks/01-<slug>.yaml), G1 approves both together, and
there is no Verifier (wanting independent verification is itself evidence
the change is standard-weight).
standard is the workhorse for feature-sized changes that ship by
merging; it drops Ops and G3 because for most repository work the merge
is the release. full is the complete pipeline, for when
deployment is a distinct, risky act that needs a release and rollback
plan.
The guardrails matter more than the menu. Profiles are fixed sets, not
knobs: there is no per-run role or gate toggle, so if a profile doesn't fit
you pick the next heavier one. The gate ledger carries exactly the profile's
gates; a gate outside the profile is absent, never auto-approved. Upgrades
are one-way and human-decided (the human edits profile: to a
heavier value and resumes, and the missing work derives as ordinary
dispatches), while downgrading mid-run is forbidden, and an engine that
observes one escalates.
Two defaults live at different layers and are easy to
conflate. A state.yaml with no profile: field is a
full run, the heaviest profile, so every pre-profile record
keeps its meaning. The gateline new command, one layer up,
defaults to --profile standard for runs it stages. The record's
default protects history; the tool's default matches the common case.
Records are historical, so older runs show older shapes. The
orchestrator-driven runs/creation-seam/ (a standard
run) lists a G3 entry that today's contract would omit: present but
undecided, a compatibility shim for a parser written before per-profile gate
sets. It is harmless because of the rule above: an undecided or absent gate
can never masquerade as approved.
Creating a run: stage, then arm
A run is created in two steps. gateline new stages the record
(branch, intent brief, state file) in a paused, inert state
(paused_reason: staged) for human review; nothing can dispatch
against a staged run. gateline arm is what makes it
dispatchable: it starts the run at the profile's first undecided-gate phase
and ensures the run's draft pull request exists.
That pull request is generated from the run's own artifacts (the intent
brief first, then the spec once it lands), and it refreshes as the run
progresses, until a human edits the body. The generated body carries a
hidden marker comment, <!-- gateline:draft-pr -->. Once
the marker is gone, which a rewrite of the body normally removes, the text
belongs to the human, and the framework never touches the title or body
again. The PR stays a draft, carrying a do-not-merge banner, until the
run reaches done. A closed run's banner names its disposition
instead.
The gate-less exception: historian sweeps
One role runs outside the gate lifecycle. The Historian is scheduled:
committed policy in the repo-root orchestrator.yaml sets its
cadence (in this repository, a sweep every seven days with a $5 pre-flight
cost cap). A due sweep is dispatched as a mini-run,
runs/historian-<date>/ on branch
run/historian-<date>, carrying a one-entry ledger in
sweep.yaml and deliberately no state.yaml, so sweeps stay out of the gate
machinery entirely. Its approval is not a numbered gate: a human reviews the
docs-delta.md and merges the sweep branch. That merge is the
approval.
The gates are where the design concentrates its weight: the next page, how gates keep a human in charge, covers who may write them and how that hold survives automation. For field-level detail see the state.yaml reference; for command flags, the CLI reference.