03 · pipeline · non-technical

How a recommendation is born

When you invoke /rx-finance (or fitness, or learning), two pre-steps (0.5 and 0.7) reconcile yesterday's state, then eleven numbered steps fire in a fixed order. The middle steps compose the new rec. The last steps persist it. Every step is deterministic — the LLM does narrative, not arithmetic.

RECONCILE (pre-steps 0.5 + 0.7, Phase W) → MEASURE (steps 1–3, drift composite) → DEDUP CHECK (step 4, D-012) → COMPOSE (steps 5–7, + counter-thesis) → PERSIST (steps 8–11, markdown + DB + return).

If the dedup check finds no change, the flow short-circuits from DEDUP CHECK straight to PERSIST, skipping COMPOSE (D-012).

Hover any stage to highlight its inputs and outputs. The dotted line is the dedup short-circuit per decision D-012.

The 11 canonical steps (plus two pre-steps)

if no-change → skip COMPOSE (D-012) RECONCILE pre-steps 0.5 + 0.7 0.5 · auto-revive 0.7 · reconcile MEASURE steps 1–3 1 · read drift 2 · aux signals 3 · composite DEDUP step 4 4 · dedup check COMPOSE steps 5–7 5 · driving signal 6 · vault evidence 7 · compose · LLM PERSIST steps 8–11 8 · write md 9 · INSERT 10 · patch FM 11 · return
The five stages, expanded into the two reconcile pre-steps and the eleven numbered steps. Step 7 (crimson) is the only one that uses the LLM; the dashed gold branch is the D-012 dedup short-circuit that skips COMPOSE when nothing has changed.

Step-by-step detail — expand any step.

0.5 · Auto-revive snoozed recs D-032

Update the rec store: any row with snoozed_until ≤ NOW() flips back to status=open. Lazy — runs only when you invoke a rx command, not on a cron.

0.7 · Phase W reconciler D-040

Query the rec store for recent rows. For each, check the on-disk markdown frontmatter. If the DB says status=acted but the file still says open, patch the file. Idempotent — re-running is a no-op once synced.

1 · Read drift signals from canonical source

Fitness: SELECT * FROM v_drift_signals (7-component SQL view). Finance: ~6 SQL queries against hypotheses/opportunities/drift_alerts/positions. Learning: the learning-state helper (markdown helper). Numbers are copied verbatim (D-011) — no LLM math.

2 · Read auxiliary signals

Fitness: the body-composition helper (10% second layer). Finance: watchlist context, position concentration. Learning: bundle helper output. Each door has one or two extras the SQL view doesn't cover.

3 · Compose composite drift

Weighted average. Fitness uses two layers: composite = 0.90 × view.drift + 0.10 × body_recomp_sub (D-022). Finance and learning are flat single-layer composites.

4 · Auto-dedup check D-012

If a prior open rec exists for the same goal_ref, was created < 48 hours ago, and the drift score is within ±0.05 — and no acute signal has newly fired — skip writing a new rec. Instead, write a short "no-change confirmation" artifact and stop. Prevents inbox bloat from same-state re-runs.

The match key is goal_ref, not the driving signal. Acute signals always break dedup and force a fresh rec: rising hypothesis_invalidation, any new drift_alerts, hypothesis_expiry crossing 0→non-zero, stale_opportunities rising by >2, the trades table going newly non-empty, fitness gap_breach flipping state, or a fresh fitness load_drop.

5 · Pick the driving signal

Of the components that fed into the composite, the one with the highest weighted contribution becomes the driving signal. The rec body addresses this signal specifically — not the whole drift surface.

6 · Pull evidence from the vault

Call the vault indexer's /search endpoint with a query keyed off the driving signal. Top-K results (typically 3–5) become source citations. See Deep tech for retrieval mechanics.

7 · Compose the rec artifact D-008

YAML frontmatter (id, created_at, drift_score, drift_breakdown, confidence + breakdown, status=open, signals_fired, source_refs, prior_open_recs).
Body in fixed sections: TL;DR, What I'm seeing, Recommendation, Why, Counter-thesis (mandatory), Sources.

8 · Write markdown to disk

File path <Door>/rx/rx-YYYY-MM-DD-NN.md, where NN is the next sequence number for that date.

9 · INSERT into rec store

Door-specific:
· Fitness/nutrition → Supabase recommendations
· Finance → TradingV postgres recommendations via POST /v1/rx/recs with X-RX-Ingest-Token
· Learning → skipped (markdown is the only store)

10 · Patch frontmatter with store ID

After insert, the returned id is written back into the markdown's frontmatter (verocity_rx_id for fitness, tradingv_rec_id for finance). Learning skips this step.

11 · Return TL;DR to chat

Operator gets the artifact path + the one-line TL;DR. The full rec is then read either in the editor (markdown) or in the UI surface (Lovable / FastAPI panel).

Three load-bearing rules

D-008 · counter-thesis mandatory

Every rec must contain the strongest argument against taking the action, with explicit reject/accept conditions. A rec without one is malformed.

How it's made and checked: the counter-thesis is generated in the same step-7 LLM pass, prompted to argue against the recommendation using the same vault evidence. Validation is structural, not semantic — the section must exist with explicit accept/reject conditions, or the artifact is rejected as malformed. No second model grades its quality; the operator does, at read time.

D-011 · numbers verbatim

All numeric fields — drift score, sub-scores, confidence — are copied from the canonical SQL view or helper JSON. The LLM never arithmetics on signals.

D-012 · auto-dedup <48h

Same goal_ref + drift ±0.05 within 48 hours, no acute signal → short-circuit. Prevents the inbox from filling with near-identical artifacts.

Why one threshold for all doors, when finance moves slowly and fitness decides daily: dedup guards against re-invoking the same command in the same state, and invocation cadence is operator-driven in every door — domain signal cadence doesn't change what counts as "the same state twice". Per-door thresholds are a deliberate non-feature until the global one demonstrably misfires.

The snooze + auto-revive lifecycle Lazy, not cron. Auto-revive does not run on a schedule. It runs at step 0.5 of the next /rx-<door> invocation. If you never run the command, snoozed recs sit forever. Decision D-032 made this deliberate to keep the system operator-driven and predictable.

A rec spends its life in one of these states:

States: opensnoozed (≤7 days), then terminal at acted or dismissed. The snoozed → open return is the lazy auto-revive (D-032). When snooze_count ≥ 2, a forced-decision flag is raised (D-031).

Hover a state to see its transitions. Solid = forward transition · dashed = auto-revive (lazy, D-032) · dotted = forced-decision flag at snooze_count ≥ 2 (D-031). Snooze caps at 7 days.

Where the LLM is allowed to think

One creative step. Of the thirteen steps in the flow — two reconcile pre-steps plus eleven numbered — only step 7 uses LLM judgement. Everything else is deterministic SQL, file IO, or arithmetic on values copied verbatim. This makes the system auditable: if a rec is wrong, the bug is either in the data (steps 1–2), in the math (step 3), in the dedup rule (step 4), in the vault search (step 6), or in the narrative (step 7) — and each is inspectable in isolation.
StepDone byWhy
0.5 · auto-reviveSQL UPDATEPure state transition.
0.7 · reconcileSQL SELECT + file EditPure diff + patch.
1 · read driftSQL viewMath owned by view; no LLM math (D-011).
2 · aux signalsHelper scriptDeterministic helper, no LLM.
3 · compositeArithmetic in commandSingle weighted average; values verbatim.
4 · dedup checkComparison rulePure boolean predicate.
5 · driving signalargmax(weighted sub-scores)Deterministic.
6 · vault searchHTTP /search callIndexer returns ranked sources.
7 · compose bodyLLMNarrative, citation framing, counter-thesis composition.
8 · write filefile writeMechanical.
9 · INSERTSQL / HTTP POSTMechanical.
10 · patch FMYAML editMechanical.
11 · returnprintMechanical.

The enrichment lane — the cost seam

The eleven steps above are the always-on fast path: cheap, deterministic, and run inside the app on every invocation. Deeper analysis — wide multi-hop retrieval, a contradiction pass, an external counter — is far more expensive, so it is deliberately not in that path. It runs as a separate on-demand enrichment lane in a Claude Code session (no API key, subscription-billed), and POSTs its results back to the app so the rec card can show them. This split along the cost seam is what keeps the always-on app cheap while still allowing deep work when it's worth it.

Always-on app TradingV FastAPI + vault_indexer · cheap · deterministic · no LLM · fast-path retrieval (beam 5 · prune 0.50 · stop 0.65) · citation verification (deterministic substring) · attribution split + P&L-per-rec · drift detectors (MAPE · preference · embedding) · retrieval_log (eligible vs surfaced) never gains a per-rec LLM call On-demand · Claude Code LLM judgment + web · subscription-billed · NO API key · deep retrieval (beam 12 · prune 0.30 · k 50) · contradiction / staleness pass · external disconfirmation (WebSearch) · curate + govern the candidate set · retrieval itself = pure Python (no billing) opt-in; runs where the budget doesn't apply affordance "run /rx-deep-retrieve" write-back POST /v1/rx/deep (X-RX-Ingest-Token)
The cost seam. The always-on app (left) stays cheap; expensive LLM/web work (right) is opt-in, runs with no API key, and writes results back through the token-authed endpoint. Same pattern per door — finance → Postgres, fitness/nutrition → Supabase, learning → a markdown sidecar.

Two app-side checks also attach at the edges of the eleven steps without being new steps: citation verification runs at ingest (annotating each source_ref as the rec persists), and attribution + P&L are read-time analytics over the trade↔rec links. The deep / contradiction / external-counter enrichments are the out-of-band lane above — they land in a separate rx_deep_results store, not inline in the rec.

← prev
02 · Ingestion