{"path":"research/claude-code-as-extraction-engine.md","content":"# Claude Code as the Extraction Engine: A Fourth Option for the Quota Decision\n\n**Date**: 2026-08-23 · **Status**: research, NOT a decision — the quota decision stays the founder's; this maps an option that was not on the table when the live-test blocker row was written. Cloud session: the policy and billing state below were verified first-hand today; the code facts were read from the repo; **no live extraction ran here** (that needs auth and infrastructure a cloud session does not have).\n\n## 0. Plain summary\n\nThe extraction pipeline is stalled because its LLM key sits on a blocked account at free tier — roughly twenty calls per day per model, shared across every project on the key — and the standing options were pay, open a fresh billing account, or accept brittleness. There is a fourth option requiring no payment: run extraction through Claude Code on the operator's existing Claude subscription. Verified today: that path is currently subsidized (Anthropic's announced billing change for programmatic use was paused on its effective date and has not returned), it is within Anthropic's published rules for the operator's own use, the pipeline's architecture makes it a one-function backend swap, and the model class it brings is the one this corpus already uses as its quality ceiling. The boundary that must hold: this covers **operator-run** extraction — dogfood runs, corpus building, a facilitated live session. It does not cover reopening public extraction on the operator's subscription, which Anthropic's rules prohibit.\n\nOne premise corrected up front: this does **not** meaningfully reduce the money at stake, because Deliberus never drove it — measured extraction spend is **~$5–7 all-time** (16 extractions across four months, TODO § operational threads). What it changes is *dependence*: Deliberus stops being a hostage of the shared key's fate, and the pay-or-close decision becomes a question about the key's *other* consumers.\n\n## 1. The verified ground (2026-08-23, first-hand fetches)\n\n**Billing — the programmatic-use change is still paused.** Anthropic's support article (\"Use the Claude Agent SDK with your Claude plan\"), fetched today, verbatim:\n\n> \"We're pausing the changes to Claude Agent SDK usage described below. For now, nothing has changed: Claude Agent SDK, `claude -p`, and third-party app usage still draw from your subscription's usage limits.\"\n\nA negative sweep of Jul 1 → Aug 23 (newsroom, Claude Code changelog, press) found no revival, reschedule, or cancellation; advance notice is promised before anything takes effect. The paused plan's numbers are published (never activated): $20 Pro / $100 Max 5x / $200 Max 20x per month at API rates — so even a revived split in that shape carries operator-phase extraction volume comfortably (§4 arithmetic).\n\n**Policy — the line is written down, and operator use is on the right side of it.** From code.claude.com/docs/en/legal-and-compliance, fetched in full today:\n\n> \"Advertised usage limits for Pro and Max plans assume ordinary, individual usage of Claude Code and the Agent SDK.\"\n\nBanned, verbatim: offering \"Claude.ai login into their own applications\", routing \"requests through Free, Pro, or Max plan credentials **on behalf of their users**\", and collecting/storing/intermediating credentials. The mapping here is clean in both directions:\n\n- **Operator-run extraction is individual use.** Dogfood runs, backfills, hand-ingestion by debate cluster, and a facilitated session the operator conducts are the operator's own workload on the operator's own machine and account. Scripted and server-side use is what the Agent SDK exists for; participants in a live session sign into *Deliberus* (for attribution), never into Claude, and no credential of theirs ever touches the LLM layer.\n- **Public extraction on the operator's subscription is the banned shape.** The moment arbitrary site visitors' submissions consume the operator's plan, that is routing requests through plan credentials on behalf of users. If and when public extraction reopens, it runs on a paid API key (any provider) or on the metered credit if Anthropic ships it — not on this path.\n\nThis division happens to match the product's current posture exactly: extraction is deployed behind `DELIBERUS_EXTRACTION_PAUSED` with an owner bypass. The carve-out and the kill-switch describe the same boundary.\n\n## 2. Technical fit — read from the code, not assumed\n\n**The seam exists and is one function.** All extraction-pass call sites — **12 `llm_call(...)` sites across 10 modules** (scout, focused, relationships/schemes, contested concepts, CQ generation, self-eval, implicit premises, auto-connect, correction pipeline, cross-source premises, cross-structure) — route through `llm_call(messages, response_model)` in `deliberus/extraction/client.py`, the discipline the Jul-2026 capacity-fallback work enforced. Even the PDF path (`call_genai_with_pdf`) already works schema-first: `response_model.model_json_schema()` → constrained generation → `model_validate_json`.\n\n**Claude Code headless speaks exactly that shape.** `claude -p --output-format json --json-schema <schema>` returns schema-conforming output in a `structured_output` field (current headless docs, verified today; validation behavior since CC v2.1.205), and the Agent SDK offers the same programmatically. So the backend is: render the messages, run the call with tools disallowed, validate the result with the same Pydantic model. Env-switched beside the existing `DELIBERUS_GEMINI_MODEL` override; the Gemini free-tier chain stays as fallback, so a Claude-side failure degrades to today's state rather than below it.\n\n**What stays where it is**: embeddings (local Qwen model — untouched), the deterministic instruments (staleness, stance, weighing lexicons, hinge — no LLM), and voice transcription (Claude does no audio; Gemini free tier covers the low volume, and the KB-Whisper pipeline covers the diarized modality entirely locally).\n\n**The swap discipline is already written.** TODO's 3-flash→3.5 item defines the template for any model change here, and it transfers verbatim as the acceptance gate: (a) config quirks handled per provider guidance, (b) structured-output validation re-verified against the real response models — the Apr-1 outage class, which will have a Claude-flavored sibling and needs its own regression tests before anything ships, (c) **one real-source A/B on claim quality** before adoption. Two Claude-specific items join the list: prompt-render parity (the Jinja-escaping quirk is instructor-specific and must not leak into the new path), and latency measured per pass rather than assumed (subprocess startup × ~dozens of internal calls per extraction is real; a persistent-session variant exists if it matters, but measure first).\n\n**Cost/limits arithmetic** (labeled estimate; derivation stated): the measured Gemini figure of ~$0.27–0.37 per extraction implies token volumes that price at roughly **$1–2 per extraction** at Sonnet-class API rates. On the subscription today that is not dollars at all but shared window-limit consumption — an evening of six to ten extractions plus instrument passes ≈ $10–20 API-equivalent, comfortably inside a Max evening alongside other use. Under the paused plan's credit numbers, $100–200/month ≈ 50–150 extractions/month. Free tier by comparison: one extraction consumes dozens of internal calls against a budget of ~60 degraded calls/day shared across all projects — which is why extraction is effectively down, not merely slow.\n\n**Quality is the upside, not a risk.** Run 3F established frontier-class extraction as the corpus's quality ceiling and implicit-premise recall as the pipeline's sharpest quality axis — precisely where a Flash-class model is weakest and where the free-tier fallback (flash-lite) is weaker still. A Claude-backed pipeline runs the production path at near-ceiling model quality for the first time, and makes the run-3F pipeline-comparison protocol executable: pipeline-on-Claude against the hand-executed ceiling now, with a pipeline-on-Flash arm added if the account is ever restored.\n\n## 3. What it unblocks (each currently gated on quota, none on design)\n\n1. **The live election test** — the design doc's own blocker table calls quota \"the gating founder decision\"; §4 below.\n2. **The substrate run** (`agents-as-a-consumer-class.md`) — \"needs no human session and is blocked only on quota.\"\n3. **The three-scales cross-domain experiment** (TODO § operational threads) — needs \"one authenticated extraction,\" a human-plus-quota step that becomes just a human step.\n4. **The run-3F comparison protocol** — \"BLOCKED on Gemini top-up\" since July; runnable with the arms restated as above.\n5. **Supply-side ingestion by debate cluster** (incentives-analysis §6b) — the standing rule was never short of URLs, only of calls.\n6. **Re-extraction backfills** under the Data Freshness directive, paced against shared limits.\n\n## 4. The live election test specifically\n\nThe riksdagsval is **September 13** — the design doc's \"material's own expiry date\" — and the blocked account's closure horizon falls in early September, *before* it. This option removes that cliff from the test's critical path entirely, and changes nothing in the pre-registered design:\n\n- **M-typed**: each landing-input submission runs the full 8-pass pipeline on subscription instead of against a ~60-degraded-calls/day shared ration that cannot carry even one modality of one evening.\n- **M-spoken**: the recorded baseline goes through KB-Whisper + pyannote as designed (local, no LLM at any step), and the attributed segments extract through the Claude-backed pipeline post-hoc.\n- **M-live / kartpauser**: the pipelined map-pause cadence absorbs per-extraction latency of a few minutes either way; no design change.\n- **Venue**: the dev task queue from the operator's machine against the shared data stores — the architecture's already-supported mode — which means the Claude login already present there just works, and no production-container credential plumbing exists or is needed.\n- **The simulation's \"honestest risk\" is the direct beneficiary**: shaky extraction quality on spoken-register Swedish turning ratification into an editing session is an extraction-quality risk, and model class is the largest lever on it. Prediction to score at the dry run, not assume: Claude-class extraction keeps ratification within the five-claims-per-person-per-pause budget.\n\n**What this does not fix** (the blocker table's other rows stand): the deterministic instruments are English-lexicon — run 7 measured **0 of 9 firing on Swedish against 9 of 9 on English**. *(Updated same day: whether the Swedish gap should be answered by the planned half-day lexicon at all is now a live founder question — the LLM tier alternative, with a 4/4 live Swedish demonstration, is mapped in [weighing-detection-semantic-tier.md](weighing-detection-semantic-tier.md).)* Concurrent two-user contribution still wants the two-browser dry run; the badge display question and the participant pick remain founder calls.\n\n**One self-correction, on the record**: an earlier chat suggestion in this arc proposed re-extracting the 2014 Swedish blog post as the Swedish dry-run. That collides with the open pre-fix-source orphaning decision (TODO) and is withdrawn. The dry run should use **fresh Swedish election-register material** instead — which the corpus wants anyway: it currently holds zero Swedish and zero electoral sources, and run 7's register findings predict exactly where the pipeline will strain.\n\n## 5. A side effect worth naming: cross-family checking\n\nThe threat model's instrument-independence gap says the honesty checkers run on the same model class they audit. Most instruments here are deterministic or embedding-based and unaffected — but with extraction on Claude, the one propose-only LLM judge (the terminus classifier) can stay on Gemini, giving the corpus its **first cross-model-family split between producer and checker on an LLM-made judgment**. Small, but it is the first rung of the escalation path `bengio-safety-from-honesty-and-deliberus.md` names, arriving as a side effect rather than a project.\n\n## 6. Build path (~1–2 sessions, local), and what would change this analysis\n\nBuild: the Claude backend behind `llm_call` with its regression tests (cloud-doable) → wire + verify against the live stack (local session: dev stack, one real extraction) → the Swedish dry-run A/B on fresh election material → the design doc's pre-session checklist → run 8 before Sept 13.\n\nOpen, and the founder's: whether to adopt the backend at all; whether to pay the balance anyway for the key's other consumers; the participant pick. Watch-items that would change this page: Anthropic's promised advance notice on the billing revision (the support article above is where it lands), and a headless-mode change already announced — `--bare` becoming the `-p` default in a future release, where bare mode \"never reads OAuth credentials or the system keychain,\" so any future Claude Code version bump must set subscription auth explicitly or the backend fails closed into the Gemini fallback.\n\n## Provenance and sources\n\nFetched first-hand 2026-08-23: [support.claude.com — Use the Claude Agent SDK with your Claude plan](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan) (pause + credit table) · [code.claude.com/docs/en/legal-and-compliance](https://code.claude.com/docs/en/legal-and-compliance) (the authentication rules quoted above) · [code.claude.com/docs/en/headless](https://code.claude.com/docs/en/headless) (`--json-schema`, `--bare`, per-invocation `total_cost_usd` in JSON output). Read from this repo the same day: `deliberus/extraction/client.py` (the `llm_call` seam and fallback chain), the call-site sweep across `deliberus/extraction/`, `docs/research/live-election-test-design.md`, `docs/research/dogfood-run-7-swedish-election-structured-vs-unstructured.md`, TODO § operational threads (key fingerprint settlement; all-time spend; swap-discipline template). The Jul 1→Aug 23 negative sweep was agent-gathered from primary sources (Anthropic newsroom, Claude Code changelog, press) with the two load-bearing pages re-fetched directly. Estimates are labeled as such and carry their derivations; nothing in §0–6 executed a live extraction (§7's two verification records and §7.0's A/B did, later the same day).\n\n**Cross-references**: [live-election-test-design.md](live-election-test-design.md) (the blocker row this gives a fourth option to) · [frontier-extraction-experiment.md](frontier-extraction-experiment.md) (the ceiling + comparison protocol) · [agents-as-a-consumer-class.md](agents-as-a-consumer-class.md) (the substrate run) · [incentives-analysis.md](incentives-analysis.md) §6b (supply-side ingestion) · [the-scrutiny-gap.md](the-scrutiny-gap.md) + [bengio-safety-from-honesty-and-deliberus.md](bengio-safety-from-honesty-and-deliberus.md) (instrument independence) · [weighing-detection-semantic-tier.md](weighing-detection-semantic-tier.md) (the founder question this backend reopened).\n\n---\n\n## 7. Built and live-verified the same day — the implementation record and the local checklist\n\n*Appended 2026-08-23, hours after §0–6: the founder said \"let's test if it works,\" and the cloud session turned out to have a current `claude` CLI (v2.1.241) installed — so the backend was built, mock-tested, and then exercised **live** against real calls. What follows is the verification record and the exhaustive handoff for the local session. Caveat on every live number: the cloud container bills its calls API-style, so costs below are real API-equivalents; on the operator's machine the same calls draw subscription window limits instead. The container's model roster also need not match the local one — re-verify the model alias locally.*\n\n### 7.0 The merge synthesis — two parallel builds, one file (merged the same evening)\n\nUnknown to each other, the cloud session (this §7, Fyr-pattern transfer, container-live-verified with API-style billing) and the local Mac session (subscription-live-verified) **built this backend twice in parallel**; the git merge collided add/add and was resolved deliberately, keeping each side's *verified* pieces. What the merged `claude_backend.py` took from where:\n\n- **From the cloud build**: the env interface (`DELIBERUS_LLM_BACKEND=claude` selector + STRICT mode), env hygiene (`ANTHROPIC_API_KEY` blanked, parent-session ids stripped — the nested-session inheritance was *measured*), tools disallowed **by name never wildcard**, the transient/hard failure taxonomy with backoff retries, the `result`-text JSON fallback parse, and the `usage.jsonl` telemetry (the cost instrument the project never had).\n- **From the local build**: `--setting-sources \"\"` (measured: kills the settings/memory context tax in one flag, which also makes the runner-dir `claudeMdExcludes` settings file unnecessary — with no setting sources it would not even load); `--system-prompt-file` (source texts travel role-faithful with no argv ceiling, so §7.5's oversize-folding deviation is **superseded** — role fidelity is preserved for free and the per-source prompt-cache prefix stays intact); `--no-session-persistence` (dissolving §7.7 wart 4); the production hard-refusal in `claude_enabled()` (the policy boundary as code); and two more live facts — **`--bare` skips OAuth today** (\"Not logged in\", v2.1.233), and **a wildcard tool-disallow silently nulls `structured_output`** (the mechanism is itself tool-shaped; the cloud build inferred the by-name rule from `stop_reason`, the local build measured the failure).\n- **Superseded checklist items**: §7.8 step 3's `claudeMdExcludes` verification (no longer applicable) and step 2's expectation that `ANTHROPIC_API_KEY` is `set` (measured NOT set in the local Bash shell — the strip guard stays as defense for shells where the fleet export is live). Merged-module smoke on the local machine: `smoke ok`, telemetry written, ~16K cache-creation tokens, subscription auth confirmed.\n\n**Isolation over persistence — a design stance, not an accident.** The founder's worry, verbatim: *\"I'm worried that processing many LLM calls within ONE CC session could 'pollute' the semantic understanding / let the LLM 'cheat' by having all the previously processed extraction-pipeline-promptings as earlier messages still there in the persistent session.\"* The merged backend makes that structurally impossible: every call is a fresh subprocess that dies after one answer — no `--resume`, no session persistence, no shared conversation. The economics still land because the server-side **prompt cache** is byte-prefix reuse for pricing and latency, NOT conversational memory ($0.16 cold → $0.06 warm on shared source prefixes, model blind to every other call). Warm-session pricing with cold-session isolation; the Fyr-style persistent session should NOT be adopted — isolation is worth more than the seconds.\n\n**The dry A/B — run the same evening, adoption gate's third item satisfied against today's Gemini.** The quality A/B needs no dev stack (a dry run stores nothing, so no HTTP auth and no creation-pipeline concerns; the stack matters only when results should be STORED). Source: a fresh 4,297-char Swedish rights-advocacy election op-ed (the Green Party migration piece — run 7's predicted strain register). Scout + focused through both backends:\n\n| Arm | Scout | Focused | Wall | Notes |\n|---|---|---|---|---|\n| Claude (sonnet, merged backend) | **17 arguments** | **75 claims** | 346s | claims carry author attribution (\"According to Imaan Muqbil, Green Party candidate…\") and preserve Swedish key terms in quotes ('rättssäker', 'permanenta uppehållstillstånd') |\n| Gemini (today's reality: free tier) | **4 arguments** | **20 claims** | 14s | primary model quota-blocked on EVERY call; the whole run served by fallback models (3.5-flash / flash-lite) |\n\nHonest reading: claim count alone is not quality (over-splitting is its own failure mode), but 4-vs-17 arguments at scout level on a text with ten-plus distinct argumentative moves is under-segmentation, and the sample-level attribution + terminology discipline favors the Claude arm on exactly the axes the pipeline prizes (decontextualization, attribution, the stranger test). Latency is the price: ~25x slower, fine for operator-run extraction. **Caveat**: this compares against *today's degraded* Gemini; the paid-tier comparison (and the run-3F ceiling three-way) runs only if the account is restored.\n\n### 7.1 What is in the repo now\n\n- **`deliberus/extraction/claude_backend.py`** — the backend module. Default-off; nothing changes until `DELIBERUS_LLM_BACKEND=claude` is set (the de-bake pattern: a recorded parameter whose default reproduces shipped behaviour exactly).\n- **`deliberus/extraction/client.py`** — `llm_call` gained the dispatch branch: claude backend first when the env var says so; on `ClaudeBackendError` it falls through to the untouched Gemini chain unless `DELIBERUS_CLAUDE_STRICT=1`. The swap can degrade to today's behaviour, never below it.\n- **`tests/test_claude_backend.py`** — 17 tests. 15 pass in a deps-light cloud run; 2 (the dispatch tests, which import `client.py` and therefore instructor) are `importorskip`-gated and run in the full local suite.\n\n### 7.2 Live verification record (real calls, 2026-08-23)\n\n| # | Test | Result | Load-bearing numbers |\n|---|---|---|---|\n| 1 | Trivial schema, `claude -p --json-schema`, **repo cwd** | worked — and demonstrated the isolation lesson | **159,040** cache-creation tokens (the repo's CLAUDE.md et al.), **$0.64** for \"what is 2+2\" |\n| 2 | Same call, **isolated empty cwd** | worked | **113** cache-creation tokens, **$0.0086** — the isolated-cwd pattern is a ~74× cost factor, not hygiene |\n| 3 | **`ParameterizedCQ.params: dict[str,str]`** schema (the exact field class that broke `GENAI_STRUCTURED_OUTPUTS` in Mar 2026) | **accepted and filled correctly** (`{'expert_name': 'Dr. Smith', 'field': 'labor economics'}`) | the one structural hazard on the schema inventory cleared |\n| 4 | Swedish election paragraph, Sonnet-class, extraction-shaped schema | 5/5 claims correctly typed; **4/4 weighings found with dialect + mechanism** | full transcript in [weighing-detection-semantic-tier.md](weighing-detection-semantic-tier.md) §4; ~$0.17 |\n| 5 | **The shipped module itself** (`python -m deliberus.extraction.claude_backend`) | `smoke ok: answer=4`, telemetry line written | cold-call baseline **~20.5K** cache-creation tokens = Claude Code's own system+tool context (see §7.7) |\n| 6 | Mocked suite | **15 passed, 2 skipped** (0.3s) | includes the Literal-plain-string regression (the Apr-1 class, claude-flavored) |\n\nAlso observed and now handled in code: a nested `claude` **inherits the parent session's id from the environment** (test 1 reported the harness's own session id) — `_STRIP_ENV` therefore strips `CLAUDE_CODE_SESSION_ID`/`CLAUDE_SESSION_ID` alongside `ANTHROPIC_API_KEY`; and the structured-output mechanism reports `stop_reason: \"tool_use\"` — it is itself tool-shaped, which is why the backend disallows tools by *name* and never by wildcard.\n\n### 7.3 The Fyr pattern transfer, item by item\n\n| Pattern | Fyr origin | Applied here |\n|---|---|---|\n| Isolated runner cwd + suppressed global CLAUDE.md | `claudeMdExcludes` in `~/.fyr/.claude/settings.json`; \"CC always loads `~/.claude/CLAUDE.md`\" gotcha | `runner_dir()` auto-bootstraps the dir + settings on first call; measured 159K→113 tokens |\n| Full-replacement system prompt | \"preset with `append` is wrong for non-coding assistants — need full replacement\" | `--system-prompt` always passed; the coding-agent default never reaches an extraction judgment |\n| API-key stripping | `ANTHROPIC_API_KEY: \"\"` in the child env (the fleet exports a key via zshenv — the measured leak) | `_clean_env()` blanks it (blank beats absent — Fyr's exact form) |\n| Session hygiene | stale-resume lessons (`session.json`) | stateless by design: every call is a fresh `-p`; parent-session ids stripped (new, measured) |\n| Model pin + env override | `claude-sonnet-4-6` pin, alias question | `DELIBERUS_CLAUDE_MODEL`, default `sonnet` (tracks current generation); explicit pin is one env var when reproducibility matters |\n| Cost discipline | subagents explicit-only; token-cost protection | tools disallowed; per-call telemetry to `usage.jsonl` (schema name = pass identity, models, cost, duration, cache tokens) — the cost instrument this project never had |\n| No silent degradation | logger-first fallbacks; the Apr-1 rules | every failure path raises `ClaudeBackendError` after logging; validation failures name the schema; empties are impossible by construction |\n\n### 7.4 Call-site inventory — what the backend must serve\n\nAll twelve `llm_call` sites, with schema status (sizes measured from `model_json_schema()`):\n\n| Pass | Module | Response model | Schema status |\n|---|---|---|---|\n| Scout | scout.py | `ScoutResult` | 2.2KB, clean (verified) |\n| Focused 2a | focused.py | `DecomposeResult` | flat, trivial (read) |\n| Focused 2b | focused.py | `FocusedResult` | 4.1KB, clean (verified) |\n| Relationships | cross_structure.py | `RelationshipsResult` | 1.7KB, clean (verified) |\n| Contested concepts | contested_concepts.py | `ContestedConceptsResult` | 1.5KB, clean (verified) |\n| CQ generation | cq_generation.py | `ParameterizedCQBatch` | **dict field — live-verified working** (test 3) |\n| Self-eval | self_eval.py | `SelfEvalResult` | clean; its `@computed_field` counts are excluded from the validation schema, so the model is never asked for derived numbers — consistent with the 2026-08-18 derive-in-code decision |\n| Implicit premises | implicit_premises.py | `ImplicitPremisesResult` | 4.0KB, clean (verified) |\n| Auto-connect | auto_connect.py | `PairClassification` | flat, trivial (read) |\n| Correction pipeline | correction_pipeline.py | `EdgeSchemeResult` | flat, trivial (read) |\n| Cross-source premises | cross_source_premises.py | `CrossSourcePremisesResult` | flat + one nested list (read) |\n| (dispatcher) | client.py | — | the seam itself |\n\nNo schema exceeds 5KB; argv limits are not in play for schemas. System messages are the size risk (they carry full source texts by design, for Gemini implicit caching) — handled by the >100K-chars fold-into-stdin rule.\n\n### 7.5 Invocation design — decisions made, alternatives noted\n\n- **Prompt transport**: message roles collapse to one stdin document (system parts → `--system-prompt`; oversized system content folds into stdin under a compact replacement system prompt). This is a *deliberate deviation* from the role-structured Gemini path and is a quality variable for the local A/B; `--append-system-prompt-file`-style flags exist as tuning if role fidelity turns out to matter.\n- **Structured output**: `--output-format json --json-schema` (client-side schema validation since CC v2.1.205 fails loudly, a confession channel in itself); result parsing prefers `structured_output`, falls back to JSON-parsing `result`, and otherwise raises.\n- **Retry/fallthrough**: transient = timeout, empty non-zero exit, `api_error_status` 429/503/529, overloaded/rate-limit markers → up to 2 retries with backoff, then `ClaudeBackendError(transient=True)`; hard failures (validation, missing structured output, CLI absent) raise immediately. The `client.py` catch falls through to the Gemini chain — visible in logs, never silent — unless strict mode.\n- **`DELIBERUS_GEMINI_MODEL` is ignored under the claude backend** (it names a Gemini model); `DELIBERUS_CLAUDE_MODEL` governs.\n\n### 7.6 Environment variables\n\n| Var | Default | Meaning |\n|---|---|---|\n| `DELIBERUS_LLM_BACKEND` | `gemini` | `claude` routes every `llm_call` through the backend |\n| `DELIBERUS_CLAUDE_MODEL` | `sonnet` | model/alias handed to the CLI |\n| `DELIBERUS_CLAUDE_RUNNER_DIR` | `~/.deliberus/claude-runner` | isolated cwd; auto-created with its settings |\n| `DELIBERUS_CLAUDE_TIMEOUT` | `300` | per-call seconds |\n| `DELIBERUS_CLAUDE_STRICT` | unset | `1` = raise instead of falling through to Gemini (candidate for the live session, where a silent quality downgrade is worse than a loud stop) |\n| `DELIBERUS_CLAUDE_CLI` | `claude` | binary override |\n\n### 7.7 Known warts and deferred items (each named, none hidden)\n\n1. **Jinja pre-escape leakage**: scout/focused escape `{{` → `{ {` for instructor *before* building messages, so the claude path receives the mangled form on brace-heavy sources (wiki markup, LaTeX). Marginal; the clean fix is relocating the escape into the Gemini branch. Deferred.\n2. **PDF path stays on Gemini in v1** (`call_genai_with_pdf` — native inline PDF). Claude-side PDF ingestion via the Read tool is a later design, not a swap.\n3. **Cold-call context baseline ~20.5K tokens** (CC's own system + tool context, measured on the shipped module). Whether consecutive calls hit the server-side prompt cache — creation on call 1, cheap reads after — is **unmeasured**; the telemetry field `cache_creation_input_tokens` exists precisely to answer it (checklist step 4). If calls do not cache-share, batching passes into fewer calls is the optimization lever.\n4. **Session-transcript accumulation**: every `-p` run records a session under the runner project's history — harmless, grows unbounded; periodic cleanup or an opt-out flag is a later nicety.\n5. **Concurrency**: the pipeline's ThreadPools (up to 8 workers in Pass 2) will spawn up to 8 claude subprocesses. Works, but heavy on window limits and RAM; consider capping workers on first live runs.\n6. **The `--bare` tripwire** (from the companion re-verification): a future CC release may default `-p` to bare mode, which never reads OAuth/keychain — the backend would then fail closed into the Gemini fallthrough, visibly. When upgrading the local CC, re-run the smoke test first.\n\n### 7.8 The local-session verification checklist (exhaustive, in order)\n\n1. Pull the branch; run the **full suite** — the two dispatch tests un-skip; everything else must stay green (the backend is default-off, so any other failure is environmental, not this change).\n2. Preconditions: `claude --version` (note it), logged-in subscription state, and `echo ${ANTHROPIC_API_KEY:+set}` — expected `set` (the fleet exports it), which is exactly why `_clean_env()` matters.\n3. **Module smoke**: `DELIBERUS_CLAUDE_MODEL=haiku PYTHONPATH=. uv run python -m deliberus.extraction.claude_backend` → expect `smoke ok: answer=4` and a `usage.jsonl` line. Check `cache_creation_input_tokens` is small (~20K baseline, NOT hundreds of K — if large, the global-CLAUDE.md exclusion isn't biting on the local CC version; verify the `claudeMdExcludes` key syntax against it).\n4. **Cache behaviour**: run the smoke 3–5× consecutively; record whether `cache_creation_input_tokens` collapses after call 1 (prompt-cache sharing across `-p` invocations). This decides whether per-call overhead is ~20K once or ~20K always, and therefore whether pass-batching is worth building.\n5. **Heavy-model schemas live**: one cheap call each (or one combined) for the five models the cloud run could not import (`DecomposeResult`, `SelfEvalResult`, `PairClassification`, `EdgeSchemeResult`, `ParameterizedCQBatch` — the last on the *real* class, not the reconstruction).\n6. **First real extraction**: dev stack up (`scripts/dev`), `DELIBERUS_LLM_BACKEND=claude`, submit **fresh Swedish election-register material** (not the 2014 post — the orphaning decision is open). Watch: per-pass wall clock, the SSE stream, claims stored in FalkorDB, the extraction page rendering, `usage.jsonl` totals.\n7. **The A/B** per the swap-discipline template (TODO's 3-flash→3.5 checklist): claim quality, atomicity, implicit-premise recall, weighing invitations — against the run-7 hand baseline, and optionally one Gemini free-tier run of the same source for the three-way read.\n8. **The real cost number**: sum `usage.jsonl` — replaces §2's $1–2 estimate with a measurement, and (summed monthly) feeds the credit-table arithmetic if the billing split ever revives.\n9. **Decisions** (founder, §7.9), then the election-test pre-session checklist in [live-election-test-design.md](live-election-test-design.md) with the quota row satisfied.\n\n### 7.8b Measured throughput, and the model question closed (2026-08-27)\n\n**The backend was already on Sonnet.** `DEFAULT_MODEL = \"sonnet\"`; `usage.jsonl` records `claude-sonnet-5` on every logged call. So \"would Sonnet be faster\" has no swap behind it, and neighbouring tiers do not help: **Haiku 4.5 measured slower** (121.9 s against 117.4 s on one text), and published figures put Sonnet 5 at 72 output tokens/sec against Opus 5's 55.4. Sonnet is also the right tier on *quality* for this workload — GDPval-AA v2 1,618 against Opus 4.8's 1,615, on a benchmark covering document analysis and structured output.\n\n**Scout throughput, five runs on one 628-word Swedish op-ed**: 76.7 / 98.6 / 117.4 / 121.9 / 122.2 s — a **1.6× spread on identical work**, so single-run figures from this backend carry no precision. Effort `low` was fastest and `medium` slowest, which is non-monotonic: **the effort signal is inside the noise** at one run per arm. A default-off `DELIBERUS_CLAUDE_EFFORT` knob exists so the lever is measurable; it is not a recommendation.\n\n**Per-call floor**: 8.1 s wall clock against 4.1–4.2 s of model time in telemetry, so ~3–4 s is subprocess overhead no model choice touches. **Concurrency is not blocked** — 8 calls complete in 9.3 s (7.1× effective parallelism, nothing serialises against the subscription) — but it plateaus: 16 calls take 16.6 s and 24 take 46.7 s, so doubling workers buys ~1.1×.\n\n**Comparison against the paid-Gemini tier** (11 historical extractions, full eight-pass pipeline, end-to-end from `extraction_attempts`): 9.4–15.6 words/s with a consistent ~40 claims per 1,000 words, against this backend's ~2.5–6 words/s on *two* passes with 88.5 claims per 1,000. So the trade is roughly **an order of magnitude of speed against ~2× density**, and no in-backend choice closes it. Full budget and the density discussion: [live-election-test-design.md](live-election-test-design.md) § The break budget, § Claim density.\n\n### 7.9 Decision points held for the founder\n\nAdopt the backend as the dev-mode default or keep it opt-in per run · strict vs fallthrough for the live session · model default (`sonnet`) and whether an Opus arm joins the A/B as the ceiling model · the semantic-tier question and its routing rule ([weighing-detection-semantic-tier.md](weighing-detection-semantic-tier.md) §5) · whether the half-day Swedish lexicon build still happens at all.\n"}