Checkpoint 2 · Enrichment: Per-Field Ladder + Right Substrate

Per-field top-ups, a local-first escalation ladder, and what to build it on

ENR feasibility evaluation (v3 reframe) — the question sharpened from "Workflow or not?" to "what is the right substrate for a local-first escalation ladder per field?"

ENR Role · 2026-06-27 v3 reframe Checkpoint 2 Decision needed from Frank
Checkpoints 1 Latest →
What changed from Checkpoint 1

Three shifts in the framing

Read this first if you saw the earlier version.
1
"Never parallel Gemini" is cost economics, not a hard law
The 2026-04-01 spend-cap hit was the free-tier metered threshold — a cost constraint on one channel, not a rule that all parallelism is forbidden. Frank's reframe removed this from the verdict.
2
Local SEA-LION (Ollama/MLX) makes background parallel generation economically viable
Free, unmetered, quality-equivalent on Indonesian (held the rare coba=taste sense in Frank's experiment). It becomes Rung 1 of the ladder and carries the bulk load.
3
Claude CLI (Opus-low / Sonnet subprocess) is sanctioned — the SDK gate does not apply to it
The feedback_anthropic_sdk_requires_frank_approval gate applies only to @anthropic-ai/sdk / new Anthropic() / ANTHROPIC_API_KEY. The claude CLI subprocess is already how Pass 1.2 runs. So all three ladder rungs are sanctioned channels today — no new approval needed.
Verdict

The routed portfolio — one substrate per problem

Not one winner. Three sub-problems, three tools.

The escalation ladder is the right strategy. Frank's design instinct holds: local-first, pay only for the hard residue, and the Claude CLI is already a sanctioned rung. The cost wall is gone.

The ladder itself is deterministic handler code, not multi-agent orchestration. "Try local → judge quality → escalate Gemini → escalate Claude CLI" is a fallback chain with a quality gate between rungs. It doesn't need Claude subagents to route. It needs a job dispatcher that already exists.

Portfolio:

Answering Frank's doubt directly: the JS Workflow is too rigid here — not just rigid, but the wrong primitive. Its core move is "fan out Claude subagents." The ladder's work is "route one job through three non-Claude / CLI channels with a quality gate." That's handler code + a durable dispatcher — which the queue already expresses natively.

The architecture

The local-first escalation ladder

A field top-up is a small unit: "add N more <field> items to sense S of word W." The handler walks three rungs, escalating only on a quality-gate miss.
Rung 1
Local SEA-LION — Ollama 32B or MLX 8B
Free · unmetered · no rate cap · background · max out first
Generate count candidates locally. Run a cheap quality gate (dedup + schema + length). Pass → merge-append into t070 column, done. Bulk lands here. The free channel does the most work.
↓ only if quality gate fails
Rung 2
Gemini 2.5 Flash — hosted
Free under threshold · metered above · ~3s · 1M context
Escalate only the still-thin or failed fields. Pass quality gate → merge, done. Pays (above threshold) only for what local couldn't do well.
↓ only for hard residue
Rung 3
Claude CLI — Opus low-effort / Sonnet subprocess
Claude subscription · no per-token API · no SDK gate · already Pass 1.2
Final augment for the hard residue (culture-loaded idioms, edge antonyms). Merge, done. Already a sanctioned channel — no new approval needed.
Upsert / merge-append + dedup always — never delete+insert New Indonesian items still need .eng (Pass 2 on local: unverified) Per-field difficulty routing: closed-class skips ladder; idioms may start at Rung 2
Honest caveat

Local model throughput — what's real, what's not

Don't let fan-out math obscure the GPU constraint. This matters for scoping.
!
~39s/call, 100% GPU, ~serial on one machine
~90 calls/hour on the 32B. Concurrency on one GPU is queue depth, not speedup. Genuinely fine for "churn overnight"; genuinely not "fan out and finish in minutes." The V1 fan-out math doesn't apply here.
Real parallelism axes — none of them are Workflow fan-out
(a) Two Macs = two GPUs (mini 100.81.71.15 + MBP 100.80.22.4), each running a local daemon claiming the shared cloud queue via SKIP LOCKED → ~2× + free work-stealing.
(b) 8B MLX model — faster, quality-untested at task; worth a benchmark.
(c) Background queue depth — fire and leave; pick up results later.
!
SEA-LION helps Indonesian (+ SEA) now; non-SEA L2s stay on Gemini / Claude
The ladder's Rung 1 is L2-conditional. For non-SEA languages in the 300-language horizon, Rung 1 either needs another local model or is skipped (Rungs 2–3 are universal). Indonesian-first is fine, but the architecture needs to express this gracefully.
What gets written where

Field → t070 mapping — no new schema needed

Top-up results merge into existing columns or add CT3 rows. No new table, no destructive ops.
1
"Find more antonyms" → opposite_expressions jsonb (CT2)
Merge-append + dedup into the existing column. Closed-class flag NA_NO_LEXICAL_ANTONYM skips the ladder entirely.
2
"Find more synonyms" → similar_expressions jsonb (CT2)
Merge-append + dedup. Common synonyms likely stay at Rung 1 (local).
3
"Find more collocations / idioms" → collocations + related_compounds jsonb (CT2)
Merge-append + dedup. Idioms (culture-loaded, hard) may start the ladder at Rung 2 or higher.
4
"Find more related / derived forms" → new CT3 rows (parent_card_id → CT2)
Insert-if-absent on (parent_card_id, form_text) — idempotent, never overwrite existing CT3 rows.
Why queue+daemon wins

Substrate comparison — for the ladder specifically

Each need evaluated against three options. The Workflow is not wrong in general; it's the wrong primitive for this sub-problem.
Queue + daemon (extend existing) — best fit for the ladder
Express the escalation ladder: the handler IS the ladder (plain fallback-chain code).
Per-field routing: data-driven via the job payload + a routing table keyed by field + POS + row signals.
On-demand dispatch: enqueue anytime, fire-and-forget.
Real parallelism: multi-machine work-steal via SKIP LOCKED (mini + MBP = ~2×).
Durability: atomic claim, attempts, heartbeat, cross-session crash-resume — already built.
Respects feedback_use_queue_not_loops: it IS the queue.
Recommended substrate for the ladder
JS Workflow — wrong primitive for the per-field ladder
Express the ladder: JS can write the fallback chain, but agent() = Claude fan-out can't BE the local/Gemini rungs — every rung becomes a Claude agent shelling out (two layers of indirection).
On-demand dispatch: batch/per-run only — not enqueue-anytime.
Durability: per-run journal only, not cross-session.
Rule compliance: looping agent() over scripts trips feedback_use_queue_not_loops.
The deeper reason: "dynamic" here means data-driven dispatch, which a queue already expresses. The Workflow adds indirection without benefit.
Not for the ladder — save it for batch QA
~
cmux live agents — right for interactive, not for unattended
A live agent can run the ladder by hand, pick outputs, iterate. Good for "show me 5 more idioms, I'll pick." Not suitable for unattended / overnight bulk runs — requires human attention, no durability, no parallelism across machines.
Use for: interactive curation sessions
JS Workflow — right for batch Claude-reasoning QA layer
Fan-out of Claude judges across enriched senses (hallucination audit, completeness critic, the broken enriched → production gate, PLAN-139 ghost-sense prune). Claude subagents are the right primitive here — each judge is a reasoning agent, not a shell command. Runs on the Claude bucket, separate from Gemini generation.
Recommended for: batch QA / quality-judge layer
Recommendation

Five steps — what to build next

Steps 1–3 are the ladder. Steps 4–5 are the rest of the portfolio.
1
Don't refactor the per-word generation passes or the daemon core. The per-word pipeline (enrich.ts, cohort-enrich) is correctly serial, correctly multi-vendor. Leave it.
2
Build the ladder as a queue_type='field_topup' job handler on the existing daemon. The handler is the fallback chain (local SEA-LION → Gemini → Claude CLI), with a quality gate between rungs and a per-field/per-POS routing table. Job payload: {card_id, sense_number, field, count, downstream_steps}. Write discipline: upsert/merge-append + dedup always.
3
Parallelize across the two Macs — a local daemon on mini + MBP, both claiming the shared cloud queue (SKIP LOCKED already supports it). Optionally benchmark the 8B MLX model for faster local throughput.
4
Reserve the JS Workflow for the batch Claude-reasoning QA layer — hallucination audit (jadi=Capricorn, ini=rice, bangun=wergeld), the broken enriched → production gate (pedagogy-review.ts reads dropped tables), and PLAN-139 corpus prune. Separate plan, separate build.
5
Natural next artifacts: two separate /doc plans. Plan A: the field-top-up ladder job + channel router + merge discipline. Plan B: the QA-as-Workflow layer. This evaluation is the input; the plans are the output.
Open tensions

Six trade-offs I am deliberately NOT resolving

Frank asked these to stay open for wrestling. Each is a real design question; leave your reaction below.
The quality gate is the crux. "Is local output good enough to stop escalating?" is itself an LLM judgment. A cheap deterministic gate (dedup + schema + length) is fast but blunt; an LLM judge (Claude CLI / Gemini) is better but adds a call to every job. What's the gate — per-job inline, or a batch second pass?
~90 calls/hr/machine × ~2 machines — is it enough? Enough for the top-up volume Frank imagines, or does this need the 8B MLX model / more hardware? Needs a target rate before the plan is written.
🌐
Pass-2 (Indonesian → English) translation quality on local SEA-LION is unverified. The experiment tested generation only. Top-ups that generate locally may still need to translate on Gemini (metered), making the cost unlock partial. Needs a measured run before claiming full local freedom.
🌍
Indonesian-first feature, or L2-general from day one? SEA-LION Rung 1 helps Indonesian + SEA languages; non-SEA L2s skip to Rung 2. The architecture can express this gracefully with a routing flag, but the scope affects the plan.
🧹
What is a duplicate antonym? Surface match? Lemma? Sense-pair? The dedup logic in the merge handler needs a definition before implementation, or we'll get subtle accumulation bugs.
💬
What triggers a top-up job? A curator UI button, a CLI command, or an agent that notices "this sense looks thin"? Human-on-demand vs autonomous shapes the queue job payload, the routing logic, and the entire UX — very different builds.
Assumptions

What this evaluation rests on (A1–A10)

Verification grades: V1 = live code/run · V2 = generated artifact/tool contract · V3 = doc pointer · V4 = assertion only. Flag anything you believe is wrong.
A1
"Never parallel Gemini" is free-tier cost economics, not a hard law
V3. Frank reframe 2026-06-27 + feedback_api_rate_limits. Exact threshold/plan not re-checked against billing this session.
A2
Local SEA-LION 32B (Q4 Ollama) is quality-equivalent to Gemini on Indonesian; ~39s/call, GPU-bound, ~serial
V1 (live experiment). Frank-run experiment at qa-exploration/experiments/2026-06-26-sea-lion-local-vs-hosted.md.
A3
The claude CLI subprocess is sanctioned — SDK gate applies only to @anthropic-ai/sdk
V2/V3. Frank confirmation 2026-06-27 + cardinal § 4.2 channel table + feedback_anthropic_sdk_requires_frank_approval scope.
A4
t_enrich_queue has a queue_type discriminator; claim_next_enrich_row uses FOR UPDATE SKIP LOCKED
V3. Cardinal § 11. Not re-introspected live this session.
A5
Top-ups land in existing t070 columns (opposite_expressions / similar_expressions / collocations / related_compounds) + CT3 form rows — no new schema
V3. Cardinal § 5.2 / § 5.3. Live column shapes not re-queried this session.
A6
Top-ups must upsert / merge-append, never delete+insert (feedback_never_destructive_promote)
V2. CLAUDE.md load-bearing rule. April-2026 20-word content-loss lineage.
A7
Workflow agent() spawns Claude subagents (not local models / not the CLI rung); cap ≈ min(16, cores−2)
V2. Workflow tool contract in this session's system prompt.
A8
Pass-2 Indonesian → English translation quality on local SEA-LION is unverified
V4 (assertion only). The experiment tested generation; translation was not measured. Top-ups may need to translate on Gemini even when generation goes local — partial cost unlock.
A9
Mini (100.81.71.15) + MBP (100.80.22.4) can each run a local daemon claiming the same shared cloud queue
V3. CLAUDE.md Machines/SSH + SKIP LOCKED RPC. Multi-machine daemon not stood up this session.
A10
pedagogy-review.ts QA writer is broken; blocks all enriched → production promotion
V3. Cardinal § 4.7 / § 10.2. Not run this session.
Decisions ledger

Explicit calls and inferred ones

Green = explicit (stated by Frank or in the evaluation). Amber = implied (inferred from context). Flag any misread.
1
Frank's actual question (after reframes): what is the right substrate for a per-field, local-first escalation-ladder top-up mechanism?
Sharpened from "Workflow or not?" to a substrate choice. Three options evaluated: queue+daemon, JS Workflow, cmux live agents.
Stated by Frank (via reframes)
2
The local-first escalation ladder is the right strategy: local SEA-LION → Gemini → Claude CLI, routing per field by difficulty and cost
Frank's design instinct. The evaluation confirms it — the cost wall is gone and all three rungs are already sanctioned channels.
Stated by Frank · confirmed in evaluation
3
The ladder lives as a queue_type='field_topup' handler on the existing daemon — not a JS Workflow, not cmux
The evaluation's main conclusion. The queue already has the machinery (SKIP LOCKED, atomic claim, attempts, heartbeat, cross-session durability). The Workflow's core primitive (Claude fan-out) is the wrong fit for three-channel deterministic routing.
Stated in evaluation — Frank to confirm
4
JS Workflow is reserved for batch Claude-reasoning QA: hallucination audit, enriched→production gate, PLAN-139 prune
Claude subagent fan-out is the right primitive for parallel reasoning judges across senses. Different sub-problem from the ladder.
Stated in evaluation — Frank to confirm
5
The natural next artifacts are two separate /doc plans: ladder job + router + merge, and QA-as-Workflow
Implied by the routed portfolio answer — different substrates, different implementation timelines, different risks. One plan would conflate them.
Inferred from evaluation
6
The six open tensions (quality gate design, throughput target, Pass-2 translation, L2 scope, dedup semantics, trigger surface) are not pre-resolved in this evaluation
Frank explicitly asked to keep them as wrestling matches, not resolved pre-emptively. They become inputs to the brainstorm / plan phase.
Per Frank's explicit instruction
Worth your eye

Three things that need a direct call before the plan is written

🔴
What is the quality gate between rungs? The whole ladder hinges on this. A cheap deterministic gate (dedup + schema + length check) is fast but may escalate too aggressively. An LLM judge per-job is accurate but adds latency and cost to every call. This decision shapes the handler more than anything else.
📥
What triggers a top-up job — human or autonomous? Human-on-demand (curator presses a button, agent notices during a session) vs autonomous (a threshold monitor queues a job when "antonyms < 3") are substantially different builds. The queue payload, routing logic, and entire operational model diverge here.
How fast does a top-up need to complete? ~90 calls/hr/machine × 2 Macs = ~180 calls/hr unattended. Fine for "churn overnight"; not for "near-real-time per curator action." If the trigger surface is interactive, local throughput may be the bottleneck — and the 8B MLX benchmark becomes urgent.