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).
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).
The 11 canonical steps (plus two pre-steps)
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: open ⇄ snoozed (≤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).
States: open ⇄ snoozed (≤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).
snooze_count ≥ 2 (D-031). Snooze caps at 7 days.
Where the LLM is allowed to think
| Step | Done by | Why |
|---|---|---|
| 0.5 · auto-revive | SQL UPDATE | Pure state transition. |
| 0.7 · reconcile | SQL SELECT + file Edit | Pure diff + patch. |
| 1 · read drift | SQL view | Math owned by view; no LLM math (D-011). |
| 2 · aux signals | Helper script | Deterministic helper, no LLM. |
| 3 · composite | Arithmetic in command | Single weighted average; values verbatim. |
| 4 · dedup check | Comparison rule | Pure boolean predicate. |
| 5 · driving signal | argmax(weighted sub-scores) | Deterministic. |
| 6 · vault search | HTTP /search call | Indexer returns ranked sources. |
| 7 · compose body | LLM | Narrative, citation framing, counter-thesis composition. |
| 8 · write file | file write | Mechanical. |
| 9 · INSERT | SQL / HTTP POST | Mechanical. |
| 10 · patch FM | YAML edit | Mechanical. |
| 11 · return | Mechanical. |
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.
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.