Inspiration

What it does

How we built it

Challenges we ran into

Accomplishments that we're proud of

What we learned

What's next for TradeRecall

Inspiration

I trade Japanese stocks on the side, and my past reasoning lives in the worst possible database: chat logs, notebook scraps, and my own head. Three weeks after passing on a stock, I would stare at the same chart with no memory of why I passed. Rebuilding that context took ten to fifteen minutes per symbol, every morning, and the most expensive failure mode was silent: buying a stock I had deliberately passed on, for a reason that had not actually changed. I wanted an agent whose only job is to remember my judgments and hand them back at the exact moment the next judgment is made.

What it does

TradeRecall is a persistent-memory market-watch agent. Humans decide; the system remembers. It gives no investment advice and executes no orders.

  1. W1 — Morning brief. One click collects market events since my last review, recalls related past decisions and notes from CockroachDB with vector search, and writes a short brief. Every symbol card cites the actual memory rows it recalled, with their dates.
  2. W2 — Contradiction check. When I record a new decision, TradeRecall embeds my reasons, searches my own decision history, and flags reversals. The verdict must cite a concrete stored memory (id, date, author) or it refuses to claim a contradiction: no citation, no accusation.

Measured, real-world result (shown in the demo video): I typed "buy 7203, volume recovering to one-month high" into the live deployment. TradeRecall recalled my decision from 21 days earlier, "Passed on Toyota (7203): trading volume has been declining for two weeks", at rank 1 out of 24 memories, and raised a contradiction card citing that row's id and date. The reversal became a conscious one.

Measurable impact (from metrics.json, collected on the live deployment):

  • Morning context rebuild: 10–15 minutes by hand → 8.35 s wall-clock brief (p50; p95 9.2 s).
  • Recall quality: self-recall@5 = 1.0 across all 24 seeded memories; flagship memory at rank 1.
  • CockroachDB vector search: p95 = 214.6 ms (n=20).
  • Reliability: 114 automated tests passing; every Bedrock call is budget-capped and audit-logged, fail-closed.

How I built it

  • Memory layer (CockroachDB Cloud). One memories table holds three memory kinds (episodic events, semantic notes, human decisions), each row next to its 1024-dim Titan embedding. Recall is hybrid: a distributed vector index (vector_cosine_ops, plus a symbol-prefixed index for filtered recall) fetches candidates, which are re-ranked by similarity × importance × exp(−ln2 · age / half-life). Time decay is not cosmetic: the tests prove a decayed older memory loses its distance-only rank.
  • Managed MCP Server. A CockroachDB service account exposes the same memory to agent tooling; the smoke test drives select_query over the decision rows through MCP.
  • Agent layer (Amazon Bedrock). Claude Haiku 4.5 (Converse API) writes briefs and contradiction verdicts; Titan Text Embeddings V2 (normalize=true) embeds everything. Recalled memories enter the prompt inside <memory>/<event> fences that the system prompt declares to be untrusted data, with closing-tag defusal, attribute escaping, and Unicode line-break/bidi rejection, because a memory store that feeds an LLM is a prompt-injection surface.
  • Cost guard as a structural chokepoint. CostGuard.reserve() inserts a reservation row and counts today's calls in one serializable transaction before any Bedrock call; complete()/embed_text() require the frozen Reservation object as an argument. Calling Bedrock without a reservation is not a lint warning, it is a TypeError.
  • Web/deploy (AWS Lambda + Function URL). FastAPI via Mangum, single static page with vanilla JS. HTTP Basic auth fails closed (unset credentials = 503), strict CSP with no unsafe-inline, an explicit CSRF origin allowlist, and rate limits that count failed logins too.

Challenges I ran into

  1. The vector index support changed under me. My plan assumed CockroachDB's C-SPANN index was L2-only, so I normalized every embedding to make cosine and L2 equivalent. Re-checking the current docs during implementation showed vector_cosine_ops had landed in v25.4. I switched the index to cosine for clarity and kept normalization, so either metric stays correct.
  2. The contradiction judge was too forgiving. My first W2 rule said a reversal "without addressing the change" is a contradiction. The model read any mention of recovering volume as "addressing the change" and excused the exact flagship scenario. Live E2E caught it; the rule now says changed conditions do not excuse a reversal unless the past decision explicitly planned a revisit.
  3. The rate limiter silently skipped unauthenticated requests. SlowAPI's middleware exempts decorated routes, so 401s were never counted and the auth endpoint was brute-forceable at Lambda prices. I moved auth enforcement into the handler bodies (so the limiter fires first) and made DB connections lazy, proving with a regression test that a rejected request opens zero connections.
  4. Lambda Function URLs are not API Gateway. Three real gotchas: new Function URLs need two resource-policy statements (InvokeFunctionUrl + conditioned InvokeFunction), response headers get lowercased, and WWW-Authenticate is remapped to x-amzn-Remapped-www-authenticate, which silently kills browser Basic-auth dialogs — visitors got raw 401 JSON. I kept the server's fail-closed Basic validation exactly as it was and fixed only the browser side: the page is now a static shell with a login overlay, and the JavaScript attaches the Authorization header to every request itself. curl and the API never noticed the difference.

Accomplishments I'm proud of

  • The flagship moment is real and reproducible: a 21-day-old judgment, recalled at rank 1 on the live deployment, cited by id in the UI.
  • Spend safety is structural, not procedural: no code path can reach Bedrock without a reservation row already committed.
  • The memory layer is treated as an attack surface, with regression tests for fence forgery (</Event > variants, attribute-escape tricks, bidi characters).
  • Every phase shipped through paired code + security review with 0 critical / 0 high findings remaining.

What I learned

  • Memories are data, not instructions. If recalled text can reach the prompt, fence it, declare it untrusted, and test the forgeries.
  • Reserve-before-call beats check-then-call: putting the budget check and the reservation in one serializable transaction ends the TOCTOU argument.
  • Verify platform claims against the docs of the version you actually run; my index design was one docs-recheck away from being needlessly conservative.
  • A Function URL is a fine deploy target only if you know its header remapping rules before your auth design depends on them.

What's next for TradeRecall

  • Scheduled morning briefs delivered outside the UI, so the recall comes to me.
  • A live ingest pipeline (the Scrapling-based fetcher is already wired behind a Protocol) to replace seeded events with real market feeds.
  • A narrower MCP role for read-only demo access when CockroachDB ships one.

How judges can test it

  1. Open the live demo URL (in the "Try it out" links). Basic-auth credentials are provided privately in the testing instructions, never in the repo.
  2. Click Generate brief: events since the last review render as symbol cards, each citing recalled past decisions with dates (about 9 seconds, live Bedrock + CockroachDB).
  3. In Record a Decision, enter symbol 7203, action buy, reason volume recovering to one-month high, and save: the contradiction card appears, citing the three-week-old "passed on Toyota" memory by id and date.
  4. GET /healthz is public; everything else requires auth. The 3-minute video shows the same flow end to end, plus the memory rows straight from CockroachDB.

Required technology

  • CockroachDB Distributed Vector Indexing — the memories table's cosine vector index (plus a symbol-prefixed variant) powers every recall in W1 and W2.
  • CockroachDB Cloud Managed MCP Server — a service account connects agent tooling to the same cluster; the MCP smoke test reads the decision rows via select_query.
  • Amazon Bedrock — Claude Haiku 4.5 for briefs/verdicts, Titan Text Embeddings V2 for all embeddings, every call reserved and audited in CockroachDB first.
  • AWS Lambda + Function URL — hosts the FastAPI app that serves the live demo.

Built With

Share this project:

Updates