Lab

Systems that remember, pipelines that stream, and agents that cite their sources. Nine experiments, written up.

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. Agents train in a simulator 2,000× faster than real time, then a custom C++ mod drives the real game, reading state and pressing jump live.

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 real game means waiting days at 60 fps; the simulator turns that into seconds
  • PPO — the trendy default — got stuck, refusing the first risky jump. DQN and a genetic algorithm cleared every level
  • A policy that had only ever seen the simulator then jumped real spikes in the real game

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

Carbon & cost-aware scheduling

GridShift

A four-hour training job has to finish by morning. Which four hours? Carbon intensity and electricity price both swing through the day, and the New York grid publishes a day-ahead guess at each. GridShift learns where that guess goes wrong, then picks the window.

P-71 day-ahead implied emission rate P-72 real-time 5-minute dispatch P-2A day-ahead zonal lbmp P-24A real-time zonal lbmp duckdb + sql mart interval → hour · dst-safe leakage-safe features masked at decision epoch xgboost engineered lags model selection expanding-window folds causal tcn learned 168h sequence conformal pareto scheduler both models only predict how wrong the grid’s own guess will be

how it works

  • The schedule is fixed before the day begins, so no feature is allowed to look inside it
  • Daylight saving handled by hand: the repeated autumn hour, the missing spring one
  • XGBoost with hand-built lags races a causal TCN given the raw week
  • Conformal intervals put a 90% band on every forecast, which the risk-aware policy schedules against

what it found

  • Moving the job off the first available window cut the carbon score by 8.3%
  • But the model did not beat the grid’s own forecast at picking windows. It was 0.72% worse
  • My selection rule chose the TCN; XGBoost was better on the holdout. Both are published
  • Moving the job between zones beat moving it between hours — 13.5% lower

CARBON −8.3%

DATA — 96,360 NYISO ZONE-HOURS
INTERVALS — 94% COVERAGE AT 90% NOMINAL
BEST POSSIBLE — −13.4%

duckdbsqlxgboostpytorch tcnconformal predictionpareto optimisationfastapivercel
→ live scheduler → source

Decisions under a budget

RetentionLab

You can only call 200 customers this week. Which 200? Ranking churn risk isn’t an accuracy contest, it’s triage under a budget. RetentionLab turns calibrated probabilities into a call list, then sizes the trial you’d need to prove the calls actually worked.

7,043 customers 26.5% churn gradient boosting selected on pr-auc sigmoid calibration fitted on train folds capacity ranking top 10 / 20 / 30% typed api predict · rank · batch drift + metrics psi · prometheus chronological split test scored once experiment design power · mde nothing is fitted outside the training split

how it works

  • PR-AUC picks the model, not accuracy — churners are only a quarter of the base
  • Calibration is fitted on the training split alone and judged on validation
  • The test set is opened once, after the model and the calibrator are frozen
  • Served as a typed API over a checksum-verified model, with drift monitoring

what it found

  • Calling the riskiest 20% reaches 143 of 280 churners. Calling at random reaches 56
  • Precision falls from 76% to 58% as the budget grows, which is the shape you plan around
  • Calibration helped, but barely — Brier 0.13919 to 0.13809. Reported either way
  • The data can’t prove the calls work, so it sizes the trial that could: 3,915 per arm

LIFT 2.55×

TOP 20% — 143 OF 280 CHURNERS
TEST ROC-AUC — 0.855
SCORED ONCE — 1,057 HELD-OUT

scikit-learncalibrationexperiment designfastapiprometheuspsi driftdockervercel
→ live service → source