Each adapter under adapters/<name>/ carries a
manifest.json that maps the framework's runtime-neutral role
specs into a specific runner's vocabulary. Agent files are
rendered, not written: gateline render
reads role specs and manifests, then writes the runner's agent files. The
renderer lives in packages/framework/src/render.ts, and CI
enforces staleness with
node packages/framework/src/main.ts render --check, which
needs nothing installed. An adapter may
narrow a role (fewer tools, tighter permissions) but never
widen it. The role spec is the ceiling.
Honesty note: model IDs are ILLUSTRATIVE.
The registry's model IDs are marked ILLUSTRATIVE in
registry/models.yaml: each adopter must pin their
organization's approved models and re-verify pricing. The manifest model
spellings quoted below are configuration examples, not guaranteed live
bindings. The opencode adapter specifically marks its Kimi binding as
TEMPORARY (adapters/opencode/manifest.json): its comment says
to revisit the binding once Kimi K3's open weights land on 2026-07-27, a
date now past, so the binding is pending that revisit. The OpenRouter pricing entries back the token-based
metering fallback; opencode reports real cost directly today, so they are
not the live path (registry/models.yaml).
Three shipped adapters
| Adapter | Output dir | Filename template | Tools style |
|---|---|---|---|
claude-code |
.claude/agents/ |
{role}.md |
comma — capabilities rendered as a comma-separated tool list |
copilot-cli |
.github/agents/ |
{role}.agent.md |
flow-list — capabilities rendered as a YAML flow list |
opencode |
.opencode/agents/ |
{role}.md |
permission-map — capabilities rendered as allow/deny permissions |
Manifest schema — key by key (union across adapters)
All three shipped manifests carry identical top-level
non‑_comment key sets; only the headless block
differs, where opencode adds session_field and
resume_args. The renderer's loader requires
adapter, output_dir, filename and
tools_style as strings and roles as an array, and
reads tool_map and model_map at use;
model_overrides and extra_frontmatter are optional.
The renderer never reads model_vendors or
headless; the orchestrator does.
| Key | Purpose |
|---|---|
adapter | Adapter id string, e.g. "claude-code" |
output_dir | Render target directory, created if missing |
filename | Per-role filename template; {role} is substituted with the role name |
roles[] | Roles rendered for this runner. All three adapters render eight roles: analyst, architect, implementer, reviewer, verifier, ops, integrator, historian. The orchestrator role is not rendered — it is executed by the engine/human. |
tools_style | How capabilities render as tools/permissions: comma | flow-list | permission-map |
tool_map | Abstract capability (read, search, write-artifacts, edit-code, shell) → runner's tool names |
model_map | Capability profile (frontier-reasoning | balanced | fast-cheap) → runner model spelling |
model_overrides | Per-role model spelling overrides. Implements P5 pins on multi-vendor runners: the copilot-cli maps reviewer → gpt-5.4 and verifier → gemini-3-flash (against a Claude implementer); opencode maps reviewer → kimi-k2.6 and verifier → minimax-m3 (three distinct labs, three-way decorrelation). Single-vendor adapters (claude-code) leave this empty. |
model_vendors | Runner spelling → vendor name, for the orchestrator's dispatch-time avoid_vendor_of check. For opencode, vendor is the underlying model lab (not the OpenRouter routing layer they are all fetched through). |
extra_frontmatter | Extra rendered frontmatter keys, values JSON-serialised. Example: copilot-cli sets disable-model-invocation: true and user-invocable: true; opencode sets mode: all. |
headless.command[] | v1 dispatch invocation template; {prompt} and {role} are substituted |
headless.dispatch_prompt | Prompt template — {role} and {body} are substituted; defaults to {body} when absent |
headless.session_field | Optional. The field in the runner's output that names its own session for the dispatch; opencode reads sessionID. The seam records it on the ledger entry. |
headless.resume_args | Optional. Extra arguments appended on a retry of the same role, task and round, with {session} substituted, so the retry continues the harness session; opencode passes ["--session", "{session}"]. |
headless.usage_report | Metering parse spec. Three formats: json-stdout (field paths + error/result fields, used by claude-code), static-estimate (falls back to registry estimates, used by copilot-cli), ndjson-sum (line_filter + dotted field paths over streaming JSON, used by opencode). |
How gateline render consumes manifests
The renderer is part of @gateline/framework, a Node package
with no runtime dependencies: it imports node: builtins and its
own modules, nothing else. Manifests are JSON, and role frontmatter is read
by a hand-rolled flat reader. That is what lets it run before the
environment probe has fixed anything in a host repo.
renderAll lists the directories under adapters/
that contain a manifest.json, sorted by name for stable output,
then for each listed role:
- Parses the role spec's frontmatter (requiring
dispatch,capabilities, andcapability_profile). - Resolves the model: checks
model_overrides[role]first, then falls back tomodel_map[capability_profile]. - Merges capabilities into the runner's tool names via
tool_map, in order and deduplicated. - Emits the rendered file: frontmatter
(
---/name:/description:/ tools‑or‑permission /model:/ any extra frontmatter /---) first, then the header comment ("RENDERED from roles/<role>.md by gateline render - DO NOT EDIT. Edit the role spec, then run: gateline render"), then the body, then overlay splices (overlays/_all.mdandoverlays/<role>.mdare spliced in as project policy layers).
Mode --check (used by CI:
.github/workflows/render-check.yml) reads existing rendered
files and exits 1 if any are stale. A stale-render CI failure
means the manifest or role spec was edited without re-rendering. A host
repository gets the same check as
.github/workflows/gateline-render-check.yml from
gateline init.
In a host repository, gateline render resolves the core
root from <prefix>/framework-lock.json; the default
prefix is .gateline.
Permission model: narrow, never widen
An adapter may narrow a role (fewer tools, tighter permissions)
but never widen it. The role spec is the ceiling. This principle
is rendered as a property in the opencode adapter: the
permission-map tools style generates a deny-by-default map
(permission:, then "*": deny, then one
<key>: allow per mapped tool) where only the role's
declared capabilities are explicitly allowed. The
function renderPermissionMap in render.ts
implements this.
P5 decorrelation — per-adapter gradient
| Adapter | P5 decorrelation status |
|---|---|
claude-code |
Single-vendor harness — P5 decorrelation is only partially honored. The interim fallback is a lineage split: the reviewer reviews the implementer's work using a different model within the same vendor family (fable reviews sonnet). Satisfiable via a second adapter. |
copilot-cli |
P5 decorrelation is fully honored via model_overrides: reviewer → gpt-5.4 (OpenAI) and verifier → gemini-3-flash (Google), against a Claude (Anthropic) implementer. |
opencode |
Three-way lab decorrelation: implementer uses DeepSeek, reviewer uses Moonshot (Kimi), verifier uses MiniMax — distinct companies with independent training pipelines and RLHF. All three routed through OpenRouter; vendor is the underlying lab, not the routing layer. |
Dispatch-time enforcement: with more than one adapter configured, the
orchestrator's dispatch seam refuses to bind Reviewer or Verifier
to the Implementer's vendor (VendorPinError), so the
avoid_vendor_of registry pins are enforced, not conventional. With a single adapter (the default gateline up
with one --adapter) the pin is unsatisfiable and stays
advisory: the router dispatches on the implementer's vendor and logs a P5
advisory once per role. Later --adapter flags are how those
pins are satisfied.
Renderer constraints
- No runtime dependencies:
node:builtins only. A dependency that forces an install step inrender-check.ymlis the bug, not the workflow - Node 24 or later
- JSON manifests, not YAML
- Overlay splicing:
overlays/_all.mdis spliced into every agent;overlays/<role>.mdinto that one role's agent. The framework repository itself has nooverlays/; they exist in host repositories, wheregateline initwrites the stubs andvalidaterequires them present and non-empty - Stubs that are only HTML comments splice nothing
- A rendering that encounters an unparseable manifest or missing
frontmatter raises
FrameworkErrorand exits 1 (no guesswork)