In brief
- Every closed setup becomes one global memory: a DeepSeek-normalized, price-free narrative of the entry reasoning — HTF alignment, POI quality, liquidity obstacles, order flow, verdict — validated section by section before it can be embedded.
- Only the narrative goes into the vector. Symbol, side, POI type, timeframe, rank and scores live in chunk metadata and drive a deterministic layer of hard filters and rerank boosts — because embedding models are good at prose semantics and bad at numbers.
- Trade outcomes are stored in a separate per-user table and joined after retrieval — similarity is computed on what the setup looked like, never on how it ended, so a lucky win can't gravitationally pull unrelated setups toward it.
- Memory that can influence a live entry score is a prompt-injection surface: instruction-like lines are quarantined at build time, the validator rejects payload-looking documents outright, and score adjustments sit behind provenance gates and shadow mode.
- A nightly four-phase batch (backfill → normalize → rebuild → index) with explicit build statuses means a memory physically cannot reach the index until its narrative has passed normalization — there is no 'temporarily embedded the raw text' state.
The entry agent evaluates a setup, scores it, writes a few paragraphs of reasoning, and moves on. Next week a nearly identical setup shows up — same POI type, same kind of HTF alignment, same congested path to target — and the agent evaluates it from scratch, as if the first one never happened.
That's not intelligence, that's a goldfish with a scoring rubric. The whole point of keeping a structured record of every decision is that similar situations should be findable when they recur. So the platform now has a trading memory: a RAG collection where every completed setup lives as a searchable document, and where "similar" is computed the way a trader would mean it — similar market story, not similar ticker.
This post is about the design decisions that turned out to matter: what gets embedded, what deliberately doesn't, where outcomes live, and why a memory system that feeds a score-bearing agent has to be treated as a security surface.
From raw reasoning to a memory document
The raw material is the entry agent's own reasoning — the full text it produced when it accepted or rejected the setup. That text is useful but unfit for embedding as-is: it's long, it's full of exact price levels and execution details, its structure varies run to run, and it occasionally contains half-baked numeric artifacts.
So nothing goes into the index raw. A dedicated normalizer (DeepSeek, temperature 0.1) rewrites every entry reasoning into a fixed-section narrative:
Entry reasoning memory.
Entry setup summary:
Short setup on XRPUSDT at a 1H EFVG POI, scoring 59/100. The setup was
accepted despite significant weaknesses, relying primarily on exceptional
bearish alignment across all timeframes and bearish order flow data.
Market structure and momentum:
...
POI and entry trigger:
The POI is a 1H EFVG zone. The FVG component is 100% mitigated, leaving only
the order block component as valid resistance. The POI strength is critically
weak, with no clustering and severe over-testing. ...
Liquidity, FVG and obstacles:
The TP path is not clear. It is blocked by an active 1H bullish EFVG zone and
a 1D previous day low, both acting as support obstacles. ...
Market data and session context:
...
Main confluences:
- ...
Main concerns:
- ...
Agent verdict:
...
Memory note:
...
The normalizer's rules are the interesting part, because they encode what "reusable" means:
- Qualitative over numeric. "Nearby support", "limited room to target", "RSI near oversold" — not
51.77and52.09. Exact prices are execution details of one specific day; they carry zero transferable signal and actively pollute similarity. A level survives only if it's central to the verdict. - No execution instructions. Stop placement, TP levels, "tight stop" recommendations — all converted to qualitative risk context. This field is a reasoning memory, not a trade plan.
- Contradictions outrank numbers. The rejection/approval rationale and the conflicts (bullish CVD against bearish structure, over-tested POI against clean HTF alignment) are exactly what future retrieval should match on, so they're preserved more aggressively than any indicator value.
- No outcome. Anywhere. More on that below — the normalizer is explicitly forbidden from mentioning RR, PnL, or how the trade ended.
The output is validated, not trusted: required sections present, minimum length, no forbidden outcome phrases, no malformed numeric patterns (a regex catches artifacts like 0.550.578), no JSON, no markdown tables. When validation fails, the failed output plus the specific error goes back to the model as a correction turn and it gets one retry. Only text that passes becomes a memory.
What goes in the vector — and what deliberately doesn't
This was the single most consequential design decision in the whole system: the embedded document is the narrative and nothing else.
It's tempting to prepend a tidy header — Symbol: XRPUSDT, Side: SHORT, Rank score: 59 — and embed the whole thing. Tempting and wrong, for three reasons:
- Embedding models are bad at numbers. In embedding space, "score 59" and "score 61" are not usefully close, and "score 59" vs "score 91" are not usefully far. Numeric proximity is a job for arithmetic, not cosine similarity.
- Constant scaffolding inflates all-pairs similarity. Every document sharing the same labeled header lines means every document is measurably "similar" to every other one before a single word of actual content is compared. That's pure noise floor.
- The narrative already carries the categorical signal. A normalized document opens with "Short setup on XRPUSDT at a 1H EFVG POI" — instrument, direction, POI type, and timeframe are in the prose, in the form language models genuinely understand.
So the structured attributes live where structured data belongs: in the chunk metadata. Symbol, side, POI type, origin interval, rank letter, rank score, agent rank score, entry score, component scores, provenance, policy version — all attached to the indexed chunk, none of it embedded.
Search is a hybrid: the vector proposes, the metadata disposes
Retrieval runs in two stages. The vector search casts a wide net — top_k * 4 candidates on narrative similarity — and then a deterministic rerank layer applies what the vector can't:
# Hard filter: direction. A short setup's history is useless for a long.
filters["side"] = normalized_side
# Rerank boosts on top of the base similarity score:
same_poi_type +0.15
same_origin_interval +0.15
rank_score within 5 +0.10 # 10 -> +0.07, 15 -> +0.04
same_rank_letter +0.03
same_symbol +0.04 # deliberately tinyTwo of these numbers encode opinions worth spelling out. The rank-score boost is a proximity band, not an equality check — precisely the "59 is close to 61" arithmetic that the embedding can't do, done where it belongs. And the symbol boost is deliberately the smallest thing on the list: this is a global memory, and a well-normalized SOLUSDT short at an over-tested 1H zone with a congested TP path should absolutely surface when an XRPUSDT setup tells the same story. Same-ticker matching is a mild tiebreaker, not a retrieval criterion.
Outcomes live outside the embedding — by construction
The obvious design — bake "this one won, that one lost" into each memory — is a subtle trap. If outcomes are in the embedded text, similarity search starts clustering on outcome vocabulary. Every profitable memory drifts toward every other profitable memory, and retrieval quietly degrades from "setups that looked like this" to "setups that ended like this." That's look-ahead bias injected directly into your search index.
So the memory document describes the setup as it looked at decision time — full stop. Outcomes live in a separate per-user outcomes table keyed to the memory, and they're joined after retrieval:
- similarity is computed on the setup's story,
- then each retrieved memory gets its outcome stats attached — trade count, wins, losses, average RR — aggregated across every account that actually traded that setup.
One more structural choice hides in that sentence: memories are deduplicated at the setup level, one document per unique POI, with per-user results hanging off it. Ten accounts trading the same zone produce one memory with a ten-trade outcome sample — not ten near-identical documents shouting into the index.
Memory that can touch a score is a security surface
The retrieved memories feed the entry agent's evaluation. Anything that can move a score on a live trading decision deserves the same suspicion as user input, because text influencing an LLM is input — wherever it came from.
The defenses are layered and deterministic:
- Quarantine at build time. Before a narrative is embedded into a memory document, every line is scanned against instruction-pattern detectors (override attempts, tool-invocation phrasing, approval-bypass language, secret requests). A matching line is replaced with a quarantine marker — the document survives, the payload doesn't.
- Rejection at validation time. The memory-text validator independently rescans the assembled document; instruction-like payloads fail validation outright, and a failed document never reaches the index.
- Provenance in metadata. Every chunk carries its source type, source decision ID, review status, and policy version. The aggregation layer excludes any memory whose provenance doesn't check out — an un-attributed document can't contribute to a score suggestion no matter how similar it is.
- Shadow mode at the end of the chain. Even a fully validated, well-provenanced aggregate doesn't move live scores until score adjustment is explicitly enabled past shadow mode — the memory system currently reports what it would have adjusted, and that report is itself part of evaluating whether it deserves the keys.
None of these layers trusts the others. That's the point.
The nightly pipeline, and why statuses matter
Memories are built by a nightly four-phase batch — the same code path is also triggerable via API:
- Backfill scans closed positions, picks one representative per unique setup POI, and creates memory rows. A row whose agent decision hasn't been normalized yet is created as
waiting_reasoning_text— honestly labeled as not ready. - Normalize runs the DeepSeek pass for exactly those decisions that lack a normalized narrative. Already-normalized decisions are never re-billed.
- Rebuild regenerates each memory's canonical text from the normalized narrative and persists both. Anything still waiting stays waiting.
- Index embeds and upserts — but only documents that pass the validator, which structurally requires the normalizer's sections. A raw, un-normalized text cannot validate, which means it cannot be embedded. There is no "temporarily indexed the raw version" state to clean up later, because the state machine doesn't contain one.
Content hashing makes the whole thing idempotent — an unchanged document is skipped, a changed one is re-embedded, and a force flag exists for the day you change the document format and want the world rebuilt.
What the entry agent actually sees
At evaluation time, the agent gets a compact historical block, not a document dump: the top similar setups (each with its similarity score, boost reasons, setup attributes, outcome stats, and the extracted confluences/concerns), plus a deterministic aggregate across them — sample size, win/loss distribution, average RR, and the score adjustment the memory layer would suggest, gated as described above.
The division of labor mirrors the rest of the platform: everything computable is computed — similarity, boosts, outcome aggregation, provenance gating — and the model's job is interpretation: does the historical failure mode described in these memories apply to the setup in front of you, or is the thing that killed those trades absent here?
Where this fits
The memory closes a loop that the platform's architecture had been pointing at all along: the POI state machine decides when a setup is worth evaluating, the entry agent decides whether it's worth taking, and now every one of those decisions — including the rejections — becomes retrievable precedent for the next one. Rejections matter as much as wins here: "we've seen this exact trap before and declined it" is some of the highest-value context the agent can get.
It's also the rare feature that gets better entirely on its own. Every closed position deepens the outcome samples; every new setup adds a narrative to match against. The engineering job was making sure that growth compounds signal instead of noise — price-free narratives, outcome-free embeddings, deterministic metadata, quarantined text, and a pipeline that refuses to index anything half-baked.

Public pen name of LiquidMind's founder and builder. Writing first-hand engineering notes and transparent performance reviews from the system's internal ledger.
Related Posts
Stay Liquid
New posts on transparency, engineering, and the LiquidMind thesis — no noise.
Loading discussion…