0027 — A sovereign DESIGN provider: phase one runs without paid credit¶
Status: accepted · Date: 2026-08-30 (PR #120; operator-supplied evidence 51778876, the same day) · Extends: ADR-0015 · Cites: sub-doctrine 8.a
Context¶
ADR-0015 admitted qwenloop as an opt-in standby: selected only when no eligible paid engine exists, so a zero-dollar descriptor does not crowd paid engines out of rotation. Doctrine 8.a, ratified afterwards, inverts the burden: the 100% sovereign path is always the preferred way to run, and paid is the move that must be justified.
Under that rule the tree had a contradiction. EngineId.QWENLOOP was wired as a BUILD executor, but DESIGN is phase one and its only live provider was ClaudeLoopDesignProvider. A project could not be started without paid credit; the "preferred" path was the one that could not go first. And vibey doctor could not even see the engine when the feature flag was on (#117) — a preferred path you cannot inspect is not a preferred path.
Decision¶
DESIGN has a sovereign provider, chosen explicitly: --provider qwenloop on vibey work and vibey worker. QwenloopDesignProvider runs the interview batch and the spec synthesis against a local model.
- It talks to Ollama's chat API directly, not to the
qwenloopbinary.qwenloop runtakes a plan file andqwenloop promptneeds an existing run id; neither is one-shot prompt-to-JSON. Going direct also buys constrained decoding: Ollama compiles the JSON schema to a grammar and zeroes any token that would break it, so malformed output is unreachable. The paid provider has to hunt for fences and strip prose; on output shape alone the weaker model is the more reliable of the two. - Decoders are shared, not imported from the paid path.
design_json.pyholds them so the sovereign provider never depends on the paid one. - The floor is declared rather than worked around. A local model has no web access, so
research()never answers from recollection: returning a recollection with asourcefield would put a fabricated citation into a design spec, and a wrong answer that looks sourced survives review where a missing one does not. That is doctrine 10's floor, declared at the moment it is known. Because synthesis will not run until every research job succeeds, a provider that could only refuse would block the whole phase (measured on a live run), so the operator supplies the reading:<topic>.mdin the directory named byVIBEY_EVIDENCE_DIR, whose first line must besource: <where it came from>. The model summarises that; thesourceis taken from the file, never minted; missing evidence, or evidence with no provenance line, raisesSovereignResearchUnavailable. A sovereign fetcher remains an open option. doctorand the worker share one notion of "enabled" (environment first, then[features] qwenloop), so the health check and the dispatcher can never disagree about which engines exist.
Consequences¶
Good. A project can be started, interviewed and specified with no counterparty who can raise a price or refuse service — 8.a made real for phase one. Verified against the live model, and the live run caught a real defect (constraints and NFRs folded into acceptance criteria) that the prompt now forbids.
Bad. Two DESIGN providers to keep at parity; the sovereign one researches only what the operator supplies; and ADR-0015's rotation posture (qwenloop only when nothing paid is eligible) now applies to BUILD rotation but not to the explicit DESIGN choice, which this record makes explicit rather than leaving implicit.
Rule status. The rule is 8.a and is already law; this ADR is the argument for how phase one satisfies it. The narrower clause — never emit a citation the model did not fetch — is conduct and may deserve its own sub-doctrine under doctrine 10.
Alternatives rejected¶
- Shell out to the
qwenloopbinary for parity with BUILD. No one-shot JSON mode; loses constrained decoding; would need a plan file for what is a single exchange. - Let
research()answer from the model's memory. A fabricated citation in a spec is worse than a gap. - Make qwenloop the default DESIGN provider. Not yet: research depends on operator-supplied evidence and the ledger-driven acceptance path has one live validation. Explicit selection keeps the choice a decision.
- Leave DESIGN paid-only and call 8.a satisfied by BUILD. Phase one is where a project begins; a preferred path that cannot go first is not preferred.
Addendum — 2026-09-18 (#115): DECOMPOSE, a parked floor, one configurable client¶
Three things this record left open are now closed; the decision itself is unchanged, and qwenloop is still chosen explicitly (automatic selection remains open, per "Alternatives rejected").
- DECOMPOSE is sovereign too.
vibey worker --provider qwenloopused to hand BUILD's plan toScriptedWorkPlanProducer— the test fake, whose items carry no verification commands, so every verify gate after it ran nothing.QwenloopWorkPlanProducerasks the local model under a schema whoseacceptance_idsandcriteria_checkedare an enum of the spec's own criterion ids and whose items must each carry a command and a checked criterion; ordering, full criterion mapping and the skeleton rule are checked after decoding, and a plan that fails any of them is refused whole. Its decoders aredesign_json.WorkPlanDecoder, shared with the paid producer — the "decoders are shared" rule above, extended to decomposition. - The floor is declared to a human, once.
SovereignResearchUnavailablemoved todomain/errors.pyso the research handler can catch it without importing infrastructure. A refusal now parks aresearch_evidencegate on the first attempt, naming the topic, the file wanted andVIBEY_EVIDENCE_DIR; before, it was a generic failure retried six times and then parked on anattempts_exhaustedgate asking for more attempts — none of which could succeed. - One client, configured rather than hard-coded. Both providers talk through
ollama_chat.OllamaChatClient:VIBEY_OLLAMA_URL(the name vibey-gh already reads),VIBEY_OLLAMA_MODELor--ollama-model, andVIBEY_OLLAMA_TIMEOUT, defaulting to the values this record shipped with. Onlyhttp/httpsendpoints with a host are accepted.