03 · pipeline · non-technical

How a recommendation is born

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

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

  1. 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.

  2. 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.

  3. 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: ganesh_state_snapshot.py (markdown helper). Numbers are copied verbatim (D-011) — no LLM math.

  4. 2 · Read auxiliary signals

    Fitness: body_recomp_snapshot.py (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.

  5. 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.

  6. 4 · Auto-dedup check D-012

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

  7. 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.

  8. 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.

  9. 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.

  10. 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.

  11. 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)

  12. 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.

  13. 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.

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 driving signal + drift ±0.05 within 48 hours → short-circuit. Prevents the inbox from filling with near-identical artifacts.

The snooze + auto-revive lifecycle

A rec spends its life in one of these states:

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.
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.

Where the LLM is allowed to think

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.
One creative step. Of eleven, 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.
← prev
02 · Ingestion