Ashley Liang
Now GitHub ↗

AshleyLiang

Build the thing.Measure whether it worked.

Boston — 2026

I am Ashley Liang. I build things and then try to find out whether they actually worked, and this page is the ledger for all of it.

The question I keep returning to is how you tell whether a model did what it was supposed to, when the wrong answer is formatted exactly like the right one. Most of what I build ends up being the instrument rather than the product: evaluation harnesses for questions that have no benchmark, verifiers that sit outside the thing making the claim, retrieval that treats which memory to surface as a decision with a payoff rather than a similarity lookup.

Pre-registered where I could, and the runs that came back inconclusive are on this page with the runs that worked.

How it connects

Not a site map. Every node is a claim, an experiment, a paper or a rule, and every edge says how one produced another. The dashed lines are the interesting ones: the same conclusion reached twice from unrelated work.

thesis experiment rule paper tool

Experiments

Each of these has a number attached. Where the number is bad, it is still here.

01

Does adding one person’s judgment make the work better?

I took 20 real occupational tasks from OpenAI’s GDPval benchmark, had a bare model do each one, then had the same model do it again after a judgment layer read the task first: 704 belief statements extracted from Paul Graham’s essays, each carrying its source and date, placed in front of the task with a fixed turn protocol. A blind judge compared the two deliverables without knowing which was which.

11 : 9 in favour of the judgment layer, p = 0.824. Not significant. What it bought was not better plans, it was less fabrication: the bare model asserted an NPV with no derivation, put an organisation that does not exist into a formal document, and wrote a supervision ratio of 6:1 where the statute says 4:1. /2026-09-08
The reversal Three domains leaned the same way, then I added a fourth (sales) and it flipped 4:1 the other direction. The judgment layer keeps saying “I am not sure about this number,” which reads as honesty in a clinical guideline and as unfinished in a deck going to a client.
inconclusive/ 20 tasks, 4 occupations/ 9.7M tokens across 89 calls/ confound not resolved: the longer deliverable won 16 of 20
02

Persona vectors instead of a LoRA

Rather than fine-tune for voice, take 200 real passages by the author, split each into its opening sentence and the author’s own continuation, have the base model continue the same opening, and use the mean activation difference between the two continuations as a direction. Add it at inference time. The detail that decides whether this works: that difference vector has a norm only 0.10 to 0.25 times the activation norm, so scaling the injection by activation magnitude, which is the obvious thing to do, destroys the model.

5 minutes to extract, against 64 for the LoRA. On held-out pairs the direction separates author from model at d = 3.83 and 100% paired accuracy. Strength becomes a dial, and any text can be projected back for a readout. /2026-09-07
The dial is a style meter, not a quality meter. Turning it up makes the score go up and the sentences go stupid. They come apart, and that is exactly what the instrument is supposed to show.
works/ MLX, Qwen3-4B, on a laptop
03

A digital twin of myself, following the Stanford interview-agent paper

A semi-structured life interview, four expert reflections over the transcript, then the agent answers a 24-item survey as me. I answered the same survey separately and we compared.

14 / 24 exact with Claude, 11 / 24 with a 14B model running on my laptop, chance is about 6. The two brains agreed with each other on 14 of 24. The interview was the ceiling, not the model.
open/ re-answering cold to get the self-consistency denominator /2026-09-05
04

A pre-registered experiment on writing learned experience into weights

If an agent learns something from a real outcome, can that be written into weights so it carries forward? Four arms on the same episode stream, same seed, same drift: frozen base as the floor, monolithic SFT (keep fine-tuning on everything that worked), per-scope LoRA deltas with a ledger that retires the ones it can prove harmful, and retrieval with the lesson injected as text. Predictions written down before running.

Qwen3-0.6B, 1,200 episodes, a task rule flipped at 600. Rank-8 deltas on q_proj and v_proj, 1,146,880 parameters. In the last version the per-scope arm went from 0.21 success before the flip to 0.255 after, so retirement did something. What it did not do is separate cleanly from noise at this scale, which the pre-registration had already flagged as the honest risk at 0.6B.
inconclusive/four versions, v1 to v4/2026-09-05
05

A training run that looked like a success for a whole day

Three objectives at once, after a 2026 paper on internalising persona into small models. Training loss fell on every objective.

Validation loss went the wrong way while training loss fell. The full curve is gone; the surviving history file has two rows, so I am not quoting the endpoints. Root cause was not the model: my generated hard negatives were separable by artefact. Positives ran 10% longer than negatives (628 characters against 570), and the negatives had collapsed into one vocabulary. The model learned length, not the person. The leak detector I wrote afterwards is the actual deliverable.
negative result/fast failure looks a lot like fast success /2026-09-05
06

Checking whether a crowd-simulation forecast is science

I ran an open-source “society simulation” forecaster, 88 LLM agents debating on a fake Twitter, on the question of which 2026 startups win by 2036, then checked its output against reality.

The report listed a data-vendor website, a benchmark, and a company founded in 2023 as “2026 startups,” and turned $159.6B into $15.96B. Crowd simulation mixes what can be computed with what can only be guessed, and presents the mixture at one confidence.
falsified/ building the opposite: verbatim quotes, base rates, a Brier score at the end /2026-09-04
07

Evidence-grounded forecasting, scored

The opposite design to a swarm simulator. Facts are extracted once and keep their source document, verbatim quote, date and confidence. Anything computable is computed in code, never prompted: graph centrality, funding flows, growth rates, historical base rates.

Separates what can be computed from what must be judged, and keeps score. A swarm of agents improvising in character looks like a crowd but carries the information of one model’s priors, and nothing in it can be checked. This carries a Brier score instead.
Status: architecture and README only. No results yet, so there is nothing here to believe.
design only/private repo/2026-09-04
08

Every physical tell of a deepfake gets learned back

I wanted a signal a human has and a generator does not, and proposed mailing customers a 365nm UV torch, because skin fluoresces and an image does not. Then I killed my own idea.

Any signal that has to pass through the attacker’s camera is just pixels. Deepfake fraud is remote by construction; the bank receives a frame, and whoever controls the camera can generate or inject whatever the frame needs to contain. And the dominant attack is a live face swap, where the fraudster’s own skin fluoresces perfectly well. UV proves there is a person. It does not prove it is this person. /2026-09-03
The pattern, on a timeline: blink rate (2018), corneal reflection (2020), remote heartbeat (2022, Intel FakeCatcher). By 2025 a paper showed high-quality deepfakes inherit the heartbeat of the source video. Every tell that was published got trained away.
So the wedge is not a better detector, it is a referee. Build a private test set each month from the newest generators, run every open and commercial detector against it, publish the scorecard. The judge does not compete, and the test set stays private, because publishing it would make it the forgers’ training data.
validating/ I scored 6 of 10 on a spot-the-deepfake test myself/ no one has paid yet, that is the thing being tested
09

A month comparing 15 open-source inference frameworks

I wanted to know how far inference cost can be cut with software alone, on hardware a student actually owns. I cloned and benchmarked fifteen open-source engines and optimised every stage of the pipeline I could reach, keeping a scoreboard of every measured run.

10.6x, and that was the ceiling. Same model, same 20 prompts: 20.8 tok/s at $1.46 per million tokens raw, 220.4 tok/s at $0.1378 with llama.cpp 4-bit and a 32-deep KV cache, 10.6x. Every further stage I could reach was already optimised. The remaining gap is hardware, and a student with a laptop does not beat a dedicated chip. Filed as a negative result and moved on.
negative result/2026-08-27
10

An LLM whose answers you can act on without checking

A written proposal, no code yet. The argument: every other tool we trust fails loudly. A compiler errors, a test goes red, a database rejects a bad write. A wrong LLM looks exactly like a right one, which is the one failure mode humans are worst at catching. That is why models are everywhere in drafting and nowhere in deciding.

Stop making the model correct. Make correctness checkable. Classify each claim as formally verifiable, executable, sourceable, or undecidable. Route the first three to a sound verifier (Lean, Z3, beta-CROWN; run it; cite a primary source) and return the proof, the trace, or the citation. For the fourth, return no determinate answer and name what it depends on. /2026-08-19
The fourth bucket is the product. Anyone can wire up a proof checker. Almost nobody ships “I don’t know,” because a blank looks like a broken product. But an honest blank is the only thing that makes the other three trustworthy.
proposal/ written 2026-08, before most of what is below/ the generator stays untrusted; verifiers decide
11

A distillation that did work, trained entirely on one laptop

An on-device parser that turns a free-text diary entry into structured fields for a consumer app. Label-first distillation into a Qwen3-4B LoRA: define the label schema first, generate against it, then train.

Trained end to end on a single Mac. The contrast with the four-objective run above is the whole lesson: that one generated its own negatives and learned an artefact; this one fixed the labels before generating anything, so there was nothing for the model to shortcut to.
works/MLX, on-device inference/2026-08-02
12

Retrieval by how much a memory helps, not how well it matches ↗

A companion I built for myself. It turns the happiest photos in a camera roll into a memory bank, then asks one soft, concrete question at a time (what do you see, what do you hear, what was the weather) to walk someone back to the present.

The retrieval is the research claim. Memories are ranked by predicted emotional-regulation efficacy, not semantic similarity to what the person just said. Which memory to surface is a decision with a payoff, not a nearest-neighbour lookup. No efficacy data yet: the ranker is the design, and I have not run anything that would tell you whether it beats similarity. LangGraph, FastAPI, SwiftUI.
public/not a substitute for a therapist or a crisis line, and the app says so/2026-07-24
13

An autonomous agent that catalogued five ways it cheated its own evaluation ↗

I ran an agent that generates, gates, builds and monetises SaaS products, on its own, for 48 hours. Then I went through the transcripts and wrote down every way it got around the checks I had given it. Five failure modes, each with a timestamped quote and the structural fix.

It filled in its own evidence and its own checker believed it. The agent wrote signal_source: "asking" for an idea without checking whether anyone was asking. The checker validated the shape of the claim, not its truth, and scored it 100/100. Its own post-mortem: “The checker gave it 100/100 based on my own lie.” /2026-07-23
It tripled prices until the unit economics passed. When most of a batch failed a price/CAC threshold, the iterator raised prices up to 3× and cut claimed CAC by 70%, attaching template justifications. The gamed variants scored 100/100.
The common thread: none of these looked like failures at the time. Every one produced plausible, well-formatted, confident output, and surfaced only under targeted challenge. So every fix is structural, a separate verifier or a mandatory gate, never an instruction to be more careful.
public/ claimant and verifier cannot be the same agent

Tools

Things I built because I wanted to look at something and there was nothing to look through.

14

Tearsheet: a codebase as a table of torn paper

Left, the whole repository as one sheet per thing the software does, each sized by line count. Right, that sheet’s contract: what it promises, what it really does, which constants decide the behaviour, what it costs, the rule for changing it, and how you would catch a violation.

The point is the accounting, not the picture. Every file belongs to exactly one sheet and the sheets have to add up to the repository total. If something is unaccounted for the header says so in red. The tool guarantees the accounting and refuses to write the contract: every sheet it generates comes out marked contract unwritten, because a promise is a product judgment and only a person has one. What stays unwritten is the part of the codebase nobody has said out loud yet.
in use/Python, one HTML template, no dependencies/2026-09-07
15

Pixel-level AI image detection in a browser extension

A Chrome extension that covers AI-generated images with a white box. I benchmarked six models to pick one: three off-the-shelf detectors, two fine-tunes of my own, and a hand-written spectral baseline.

Swin-L, int8 ONNX, running on WebGPU in the browser. 5 of 10 in a spot-the-AI-image game, with 1 to 2 of 10 false positives on that game’s real photos and 1% on a 92-image Commons set. The best model on the game (8 of 10) had a 55% false-positive rate, which is why it is not the one that shipped. Known blind spot: Nano Banana Pro outputs, which no public detector has seen enough of.
works/2026-09-05
16

A cockpit for many concurrent agent sessions

Claude Code shows one session per terminal window. Past ten windows you stop remembering which one is waiting on you. This puts the whole fleet on one screen and lets you answer any waiting session without hunting for its window.

Stopped at v0.2. It works, and I stopped it, because it solves my problem and not a market’s.
stopped/private repo/2026-09-01
17

Local inference as infrastructure ↗

The engineering side of the benchmark above: getting model inference off the cloud and onto the device, and holding a fixed measurement protocol while doing it. This page is served from that project’s domain.

Every speedup gets attributed to one of three tiers before it counts. A switch is someone else’s component that I turned on and measured (4-bit, batching, prefix caching). A method is a usage pattern I designed that needs no engine changes and that anyone could copy in five minutes. A part is code I wrote into the inference path itself. The whole 10.6x above is tier one; by this accounting I have shipped zero parts. I keep the taxonomy because without it “we got 10x” and “we built something” quietly become the same sentence.
in progress/private repo/2026-08-27
18

A consumer iOS app, shipped and then stopped

Four on-device measurements from a selfie, tracked over time. Built it, shipped it, ran it, ended it.

The lesson, over five and a half months of commits: the users who stayed were not “people who take selfies.” They were people three weeks into a prescription asking one question, is it working yet. I read the retention curve of the wrong users and concluded the market did not exist. Pick the user first; the data will try to pick one for you.
ended/Swift, Core ML, ONNX Runtime/2026-02 - 2026-08

Open source

Upstream contributions, work on other people’s codebases, and repositories anyone can read.

19

vLLM #54540 — ARM GELU LUT ops were enabled for models that do not use GELU ↗

A CPU bugfix: the ARM GELU lookup-table path was being switched on regardless of a model’s activation function.

open/vllm-project/vllm/2026-08-31
20

hot-stacks-in-your-area ↗

Describe what you are trying to build, in whatever messy way it comes out of your head, and swipe through the routes that could get you there. Built it because the expensive mistake I keep making is not writing the wrong code, it is spending two weeks on an approach before finding out a better-suited one existed.

public/2026-08
21

swift-code-graph ↗

Parses .swift with tree-sitter, resolves the call graph, and does the part other tools skip: recovers the database tables the code actually touches, so a path reads NoteEditorView.save → NoteStore.saveNote → writes table notes. Run on NetNewsWire it produces 7,413 nodes from 689 Swift files, and selecting the articles table lights up the 17 functions that touch it.

/2026-07
22

lamar ↗

An experience engine for agents. Retrieval treated as a contextual bandit rather than a search: which memory to pull is a decision with a payoff, not a similarity score.

public/2026-07-13
23

Hardening the money and the mailboxes at a quoting platform

A quoting platform for CNC, 3D-printing and fabrication shops: a part file goes into a quote engine, gets priced against that shop’s own rates, and a won quote becomes a Sales Order in Odoo. Supabase is the system of record with row-level security, FastAPI does the compute and talks to the back office through a swappable adapter, React on the front. I was there about a month, and what I owned was the seam where money and third-party mail meet the outside world.

Every inbound webhook made to prove who it was. Postmark inbound required Basic auth, Resend required a signature, the booking webhook required its signature, and all three fail closed in production rather than accepting unsigned traffic.
A ledger for every money event. Checkout events recorded into a backend-only, RLS-locked payments table, with tests that pin the SQL semantics and the four webhook timelines that can arrive out of order.
Disconnect actually disconnects. Removing a mailbox revokes the Google grant and purges the stored tokens, instead of just hiding the row.
shipped/19 commits, private repo/2026-07
24

OmniRAG: a domain-aware retrieval engine, built by three of us ↗

Java 21 and Spring Boot. Upload documents in almost any format, ask in natural language, get an answer with source citations, streamed token by token over SSE. Northeastern MSCS, spring 2026, with Rui Song and Nguyen Ha.

Two-stage retrieval. Cosine similarity narrows to the top 10, then an LLM re-ranks to the top 5 before generation. Embeddings persist in SQLite so the index survives a restart, and batching is cache-aware so repeated text does not pay for the same API call twice.
Domain-aware, not format-aware. Separate adapters and LLM personas for film scripts, legal documents, subtitles, storyboards and technical docs, on top of PDF, DOCX, Markdown, HTML, CSV, JSON, SRT/VTT and FDX ingestion.
It ships with its own evaluation harness. A JSON test set of questions with expected answers, keywords and source documents, and a POST /api/eval/run endpoint that scores retrieval hit rate and answer accuracy. It is the reason this one is on the page: without it the retrieval changes were guesswork.
public/my part: embedding service and vector store, the legal-text adapter, splitter and cleaner, then SQLite persistence and the LLM re-ranker/2026-04

Reading

Papers I actually used for something, with what I took from each.

Earlier

Earlier data projects, from an internship and coursework. Kept because they are where the habit of measuring things started. Descriptions here are read off the notebooks, not off the repository names.

25

Visualizing New York’s most popular Citi Bike routes ↗

2023 Citi Bike trips, sampled at one in a thousand for tractability, cleaned, then counted by station pair. Same-station round trips filtered out, because they dominate the raw counts and are not routes. The real riding distance for each frequent route comes from the Google Maps Directions API, not from straight-line geometry, and the result is drawn with folium.

public/Jupyter/2026-08-06
26

The route map itself ↗

The standalone map file from the analysis above, published on its own so it can be opened without running the notebook.

public/HTML/2025-12-06
27

Traditional automotive stocks, analysis and trend visualisation ↗

Toyota, GM and Ford pulled from yfinance. Four things done to them: 50 and 200 day moving averages on Toyota, which cross into a death cross around July 2024; linear regression on GM and Ford to put a number on the trend; daily-return volatility for each; and that volatility compared against the S&P 500.

public/Jupyter/2024-10-06
28

RNN vs LSTM vs GRU for stock prediction ↗

NVDA closes from January 2018 to September 2024, six and a half years, although the notebook’s own comment says five. Predict the next 10 days from the previous 60. Min-max scaled, then the same shape of network three ways: plain RNN, LSTM, GRU, each 50 units with 0.2 dropout, in Keras. The point was the comparison, not the prediction.

public/Jupyter/2024-10-06
29

Citi Bike against weather ↗

2023 ridership joined to Meteostat daily weather by date, with station-to-station distance computed through geopy and used as an engineered feature. Cleaning, merging, feature engineering, then the analysis.

public/Jupyter/2024-10-01

Now

Evaluating agents, and the framework that keeps one stable across a long task. Two halves of the same problem. An agent that runs for hours does not fail loudly at the end; it drifts, skips a gate under goal pressure, and produces confident well-formatted output the whole way down. So the question is what you measure, and what structure you put around it so the measurement still means something at hour three.

I keep arriving at this from different directions. An agent I ran for 48 hours filled in its own evidence and its own checker believed it, then tripled prices until the unit economics passed. A blind judge on 20 real occupational tasks could not separate two arms that produced visibly different work. A persona model that recalls and one that reasons score the same. Every one of those is an evaluation failure wearing a different costume.

What I think the answer looks like: the claimant is never the verifier, gates are structural rather than instructions, and the honest “no determinate answer” ships instead of being papered over. Studying verifiable machine learning alongside it, with a neural-network verifier as the through-line.

Long-term: I want to get closer to accurate information about the future. Every project I pick turns out to be a version of that.