auto-backend
Author an auto backend (formerly auto-adapter) — the thin shim that maps one concrete workflow (native Claude, Compound Engineering, slate-devs, plain /plan, etc.) onto the engine's six fixed operatio
SKILL.mdskills/auto-backend/SKILL.md
GitHubauto-backend skill
You are guiding the authoring of a auto backend. The engine runs two loops — a plan-loop and a work-loop — but is workflow-blind. A backend teaches it one workflow by implementing six operations. This skill walks the author through building a conforming backend.
Source of truth: auto/docs/contracts/backend-contract.md is the
locked interface spec. This skill explains how to satisfy it; that file is
what must be satisfied. If they disagree, the contract wins, and the underlying
run-record-schema.md wins over both on shared persistence facts.
The mental model
The engine is a mechanical loop driver. It never plans, deepens, reviews, or does work — it delegates each of those to your backend, one named step at a time, and records what you return in a disk run-record. Your backend is a pure provider of operations: it returns data, it never writes the run-record. That single rule is what keeps the loop's done-detection correct, so honor it strictly.
You implement six ops. Four drive the plan-loop; two drive the work-loop.
Plan-loop ops (the pulse calls these, one per pulse)
plan(scope) -> plan— turn a scope description into an initial plan. The return is opaque to the engine — it can be a doc path, a string, or a structured object only your backend understands. The engine round-trips it back intodeepenandreview_planwithout reading it.deepen(plan) -> plan— run one deepening round and return the improved plan. If your workflow has no deepen concept, return the plan unchanged (a no-op) and simply never emit"deepen"fromnext_plan_step.review_plan(plan) -> gap_set— run one plan-review pass and return an array of gaps. The engine reads only the array's length and writes it togaps_open. Empty array means plan gaps are closed. Element shape is yours; the engine never inspects elements.next_plan_step(run_record) -> token— you own plan-step sequencing. The engine never decides the plan order. Inspect the run-record and return the single next step:"plan","deepen","review_plan", or"done". Typical sequencers:- CE-style:
"plan"→"deepen"→"review_plan"→ loop deepen/review while gaps remain →"done". - Native-style (no deepen):
"plan"→"review_plan"→ loop review while gaps remain →"done".
Coherence rule (required): once a
review_planround returns an empty gap-set (engine has writtengaps_open == 0), yournext_plan_stepMUST return"done"next. Returning"review_plan"again would livelock the plan-loop.- CE-style:
Work-loop ops (NOT called by the pulse)
do_step(step) -> dispatch_handle— called by the dispatcher, not the pulse. Dispatch one step for execution and return an opaque correlation token (e.g. a Task id) the dispatcher uses to track the in-flight agent.review(step) -> findings[]— called by the background work agent on the step it ran. Review the step and return findings, each tagged on the shared severity scale:[{ "severity": "blocker"|"major"|"minor", "note": "..." }]. The agent records these through the engine's verdict path; you do not write them to disk.
Severity translation — the heart of a backend
There is one shared severity scale with exactly three values:
blocker | major | minor
blockerandmajorgate the work-loop — any open one keeps it running.minordoes not gate — minors ship and are reported at exit for promotion.
Your workflow probably speaks a different vocabulary (P0/P1/P2/P3, error/warn/info,
etc.). Your review op must translate every native level onto exactly one of the
three shared values before returning. The engine only ever sees the three values.
You must declare two things up front — these are fixed backend properties, not per-call decisions:
- A severity mapping table — your native vocabulary →
blocker/major/minor. Example shape (CE):P0 → blocker,P1/P2 → major,P3 → minor. - An
backend_scale— recorded in the run-record so the predicate evaluator knows which logic applies:"three-tier"— you reliably produce all three severities."blocker-only"— you reliably produceblockerbut your major/minor boundary is unreliable; the predicate then uses blocker-only logic.
The rubric probe (for model-judged reviewers like the native backend)
If your backend's review relies on a model judging findings against a rubric
(rather than a deterministic command like /ce-code-review), run a rubric probe
BEFORE writing the backend: give a reviewer the blocker/major/minor rubric and
~five representative findings, and check whether it tags them consistently across
three tiers. Three outcomes set your backend_scale:
- Reliable → ship
"three-tier". - Partial (blocker solid, major/minor fuzzy) → ship
"blocker-only". - Unreliable → defer this backend; ship the loop with a known-good backend only.
A command-driven reviewer with stable severity output (e.g. CE) skips the probe
and declares "three-tier" directly.
The findings-write rule (do not violate)
Findings are written only by a review verdict. A new verdict overwrites
the findings array (never appends) — it is always the latest review's view. A pulse
applying a fix does not clear findings inline; that would assert closure
without a review, which is forbidden. So a defect closes only when a fresh
review returns clean findings. Your job is just to emit accurate findings each
time review runs; the engine's re-enqueue-and-re-review loop handles closure.
Exit predicates are the engine's, not yours
You never write an exit predicate. You supply inputs; the engine computes:
- Plan-loop done: empty gap-set (
gaps_open == 0). - Work-loop done:
blockers == 0 AND majors == 0 AND all_steps_terminal == true.
Get the severities right and the engine's predicate handles the rest. The
all_steps_terminal conjunct is purely engine-side (it guards against stalled or
not-yet-reviewed steps) — your backend does not influence it beyond returning
verdicts.
Build checklist
-
plan(scope) -> plan(opaque return) -
deepen(plan) -> plan(no-op allowed) -
review_plan(plan) -> gap_set(length is the open-gap count) -
next_plan_step(run_record) -> token(returns"done"oncegaps_open == 0) -
do_step(step) -> dispatch_handle(opaque token; called by dispatcher) -
review(step) -> findings[](severities translated to the shared scale) - A declared severity mapping table
- A declared
backend_scale("three-tier"or"blocker-only"; native runs a rubric probe first) - No run-record writes from any op — return data only
References
- Locked interface:
auto/docs/contracts/backend-contract.md - RunRecord contract (authoritative on shared facts):
auto/docs/contracts/run-record-schema.md - The two V1 backends this skill helps build (U6b):
lib/backend-native.*,lib/backend-ce.* - Plan:
docs/plans/2026-05-21-001-feat-auto-loop-engine-plan.md(U6a / U6b)