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.
- 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.
- 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
memoriestable 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 bysimilarity × 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_queryover 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 frozenReservationobject as an argument. Calling Bedrock without a reservation is not a lint warning, it is aTypeError. - 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
- 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_opshad landed in v25.4. I switched the index to cosine for clarity and kept normalization, so either metric stays correct. - 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.
- 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.
- Lambda Function URLs are not API Gateway. Three real gotchas: new Function URLs need two resource-policy statements (
InvokeFunctionUrl+ conditionedInvokeFunction), response headers get lowercased, andWWW-Authenticateis remapped tox-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
- 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.
- 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).
- In Record a Decision, enter symbol
7203, actionbuy, reasonvolume recovering to one-month high, and save: the contradiction card appears, citing the three-week-old "passed on Toyota" memory by id and date. GET /healthzis 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
memoriestable'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
- amazon-bedrock
- aws-lambda
- cockroachdb
- fastapi
- javascript
- mcp
- psycopg
- pytest
- python
Log in or sign up for Devpost to join the conversation.