Lab

I’m driven by curiosity — systems that remember, pipelines that stream, and agents that cite their sources. Seven experiments, documented.

Investigation memory

Forensic LLM Wiki

RAG retrieves; a wiki remembers. This system compiles raw evidence into an evolving, Obsidian-compatible case wiki — hypotheses, contradictions, and open questions included. Replaying six evidence drops takes the eval score from 2/16 to 16/16 as knowledge compounds.

final_report contradictions iocs hypotheses entities timeline open_questions index

move your cursor — the case graph follows

raw sources immutable evidence schema rules · templates · lint ingest structured indexes events · entities · claims markdown wiki timeline · iocs · hypotheses query lint report mcp obsidian export

how it works

  • Evidence links live on a hash-indexed directed graph with content-hash deduplication
  • Every answer cites the maintained case state, not raw snippets
  • Lint rules flag unsupported claims and contradictions before they land
  • 13 tools exposed to agents over the Model Context Protocol

requirements it meets

  • Functional: ingest evidence drops, query with citations, draft the final report, export to Obsidian
  • Non-functional: deterministic mock mode (no API key), CI on every push, outputs validated by a Pydantic schema layer
pythonpydanticmcpobsidianpytest
source

Research memory

PaperTrail

A research-paper agent that answers cross-paper questions with page-anchored citations. Hybrid retrieval seeds candidates with embeddings and BM25 fused by reciprocal rank, then expands across papers with k-hop BFS over a knowledge graph — lifting cross-paper linkage from 0% to 19.2%.

parse pdf pymupdf · per page chunk ~400 words extract entities multi-pass llm hybrid retrieval bm25 + vector → rrf knowledge graph 2-hop traversal grounded answer verify every quote

how it works

  • Reciprocal rank fusion merges lexical and semantic candidates before the graph hop
  • Entity canonicalization via a regex/alias table keeps the graph clean
  • A fuzzy-match filter drops fabricated citations; quotes must match the source literally or nearly
  • A second LLM grades every claim for faithfulness before the answer ships

requirements it meets

  • Functional: upload PDFs or arXiv URLs, take notes, browse the graph, ask cross-paper questions
  • Non-functional: ~3 LLM calls per question, single Docker container on Hugging Face Spaces, state persisted and restorable

LINKAGE 19.2

RETRIEVAL — BM25 + VECTOR → RRF
GRAPH HOPS — 2
LLM CALLS — 3 / QUESTION

fastapireactnetworkxchromadbrank-bm25groq
source

Private by default

DocGPT

Your documents never leave your machine. Ollama for inference, Qdrant for vector search, Spring Boot on top — with enforced citations and an explicit refusal when retrieval comes up empty, instead of a made-up answer.

two-finger drag inside the screen — up leans in, down pulls back, slowly

upload · 202 chunk 1500/200 background thread embed 768-dim nomic-embed-text qdrant question retrieve, filtered userId enforced server-side grounded json answer valid citations, or refusal

how it works

  • Async ingestion returns 202 immediately; the UI polls pending → processing → ingested
  • Every Qdrant search carries a server-side userId filter, so tenants can never cross
  • The LLM must return schema-valid JSON; three failed attempts become a refusal, never a guess
  • Citation indices map back to chunks that were actually retrieved

requirements it meets

  • Functional: JWT auth, chunked large-file uploads, grounded chat, a model-benchmark harness
  • Non-functional: integration-tested with Testcontainers, four local models benchmarked across 42 prompts

REFUSAL BUILT-IN

CHUNK — 1500 / 200 OVERLAP
EMBED — 768-DIM
RETRIES — 3, THEN REFUSE

spring bootqdrantollamareactpostgresqltestcontainers
source

Semantic search

WolfieDex

Students search by intent — “where’s free food?” — while event listings speak administrative English. WolfieDex bridges the gap for Stony Brook: HyDE query expansion, vector retrieval over live SB Engaged data, and answers grounded only in what was retrieved.

sb engaged clean + summarize python worker embed question hyde expansion vector retrieval atlas + grounded llama 3 via groq

how it works

  • A background worker continuously scrapes listings and rewrites them into search-oriented summaries
  • HyDE expands vague student queries into hypothetical listings before embedding
  • MongoDB Atlas Vector Search retrieves; the answer cites only retrieved events

requirements it meets

  • Functional: natural-language questions, strongest matches plus similar events, direct answers
  • Non-functional: deployed live on Vercel with serverless APIs; grounded-only responses
next.jsmongodb atlasvector searchgroqminilmvercel
live app

Streaming analytics

MarketPulse

Batch ETL shows you yesterday; I wanted minutes. Ticks stream through Kafka into object storage, Airflow loads the warehouse, dbt refines bronze into silver into gold, and a dashboard renders it — 3k+ records/sec at 96% pipeline reliability, sub-minute from market event to chart.

finnhub 1-min polling kafka event streaming minio raw data lake airflow 24/25 runs green snowflake bronze · silver · gold dbt transformations streamlit 5 analytical views
MarketPulse dashboard — move your cursor to inspect

hover to inspect the dashboard up close

how it works

  • Medallion architecture: raw bronze, validated silver, business-ready gold
  • Warehouse cold-starts were tripping tight DAG timeouts; warmup queries stabilized runs at 96%
  • Kafka over Kinesis for cost, MinIO over S3 for local dev, dbt for tested, version-controlled SQL

requirements it meets

  • Functional: 1-minute polling across 100–600 tickers, 5 analytical views for strategy work
  • Non-functional: fault-tolerant messaging, 16x latency reduction over the batch baseline

SUB-MINUTE

EVENT → CHART — <60 S
THROUGHPUT — 3K REC/S
DAG RUNS — 24/25 GREEN

kafkaminioairflowsnowflakedbtstreamlit
source

Machine learning

Knowing When It Doesn’t Know

A classifier that can abstain. Monte-Carlo dropout estimates predictive uncertainty, a tri-gate routes each image to accept, abstain, or flag, and the out-of-distribution thresholds are calibrated against SVHN so confident nonsense gets caught before it ships.

image mc dropout ×N uncertainty estimate accept abstain flag as ood
pytorchmc dropoutood detectionsvhn
source

Reinforcement learning · sim-to-real

Teaching an AI to Play Geometry Dash

The actual retail game on Steam — not a clone. One button, one square, instant death on contact: a perfect testbed for frame-perfect timing. Agents train in a stripped-down physics simulator that runs 2,000× faster than the game, then a custom C++ mod drives the real thing — reading game state and pressing jump in real time.

simulator 2,000× real time train dqn · ppo · ga policy sim-only c++ mod state out, jump in real game steam

how it works

  • Training in the live game means waiting days at 60 fps; the fast simulator turns millions of frames into seconds
  • A controlled bake-off: PPO — the trendy default — reliably got stuck, refusing the first risky jump because failing cost it reward; DQN and a genetic algorithm cleared every level
  • Sim-to-real transfer: a policy that had only ever seen the simulator drove the genuine game and jumped real spikes

results

  • A full official level cleared start to finish, learned from a human demonstration
  • Custom C++ mod bridges policy and game: state out, button press in, every frame
  • The lesson: the best algorithm depends on the problem, not the hype
pythondqnppogenetic algorithmc++ modsim-to-real
project page source