Skip to content
AJ Stefely
← All work

Carrel

In development

Making two years of my own notes, documents and commits findable by meaning

Role

Solo, during a FORGE internship — schema, retrieval pipeline, MCP server, frontend

Timeline

2026 — present

Built with

TypeScript, Next.js 16, Postgres + pgvector, Supabase, Deno, MCP, Tailwind

Links

Private repository
Carrel's term plan: a bar chart of weekly load across twelve weeks with exam markers, above a written summary of the heaviest weeks.
The whole term as a load chart, built from six syllabus files before any of those weeks arrive. The week that will hurt is visible in September, while there is still time to move work out of it.

The problem

Capture is not the problem. The problem comes four months later, when a deadline moves or a final paper needs the reading you annotated in September — and the answer is in a notebook, a thread, a PDF, a recording, or a message to yourself. Somewhere.

Notes apps store text you have to remember you wrote. Chat assistants forget you between sessions. Neither can answer what did I say about this after the fact. It is a retrieval problem rather than a filing problem, and that distinction decided everything that followed.

What I built

Capture has to be nearer than the notes app or it does not happen, so anything goes in through whichever door is closest — a message to a bot, an upload, a whole folder, a pasted link, a lecture recording, or a git hook that fires on every commit I make on this machine without being asked. Everything is embedded on the way in.

What comes back out is four surfaces: a Brief for the day, a Plan for the week and the term, a Calendar that imports from Google and proposes rather than writes, and a Library that is both the search across everything and the place new things go in.

The week is drawn as seven day-tracks rather than a list of start times. “09:00 MGMT 275” says when a class begins and nothing about how much of the day it eats, so a fifty-minute class and an eighty-minute one printed identically and the week could not answer whether Wednesday afternoon was free. Every class and timed event is now placed where it starts and drawn as long as it runs.

There are two ways into the corpus and they are deliberately not the same. Carrel reads Postgres directly, because a UI needs rows. Every other AI assistant reads through the MCP server, which fences what it returns — text coming back out of a memory is untrusted input to whatever model asked for it, and that is the wrong shape for a screen but the right one for a model.

Carrel's Brief for a Monday: the day drawn as one timeline bar with three classes on it, above a written paragraph summarising what is late and what is left.
The day, written out. Not a list to re-read every morning — a paragraph that names the eight-day-late problem set first, then what is actually left before eight o'clock.
Carrel's week view: seven day columns with class blocks and committed work shaded together, and a list of overdue items beneath.
Class time and committed work are shaded together, because the useful question is not what is due — it is what the week actually looks like once everything you have taken on is in it.
Carrel's Library on its Capture tab: one text box with buttons for a link, files or a whole folder, above a note that recordings are transcribed and become searchable.
The nearest door in. A thought, a link, a file or a whole folder go in through the same box — and a folder read twice is recognised rather than duplicated, because capture that punishes you for repeating yourself stops happening.

What it changed

Each figure below was read out of the repository, not estimated.

0/6 → 6/6
probes the corpus could actually answer, before and after the retrieval rebuildmeasure-recall harness, six probes against one 48,320-character transcript. Re-run 2026-09-08: 0/6 → 4/6 → 6/6, unchanged
31
MCP tools exposing the corpus to any AI clientopen-brain-mcp edge function, counted across six tool modules 2026-09-08
85%
of the corpus that was silently unsearchable until a coverage check caught itat the time, 211 embedded commits against 37 rows in every other table

Decisions worth explaining

01

Retrieval is two problems, and I was only solving one

The first version embedded each document as a single vector and searched that. It fails in a way nothing reports: anything buried in the middle of a long document becomes unreachable, because one vector averages a whole transcript into a point that means nothing in particular. I built a harness to measure it rather than argue about it — six probes naming phrases that genuinely appear in a meeting transcript, each sitting 34 to 82 percent of the way in. Document-level search answered none of them. Contextual chunking took it to four. The last two were already being retrieved and then ranked tenth and eleventh, so the fix was not more chunking but a cross-encoder reranker deciding what survived. Retrieve wide for recall, rank narrowly for precision.

02

The relevance floor was set by a question the corpus cannot answer

Every real probe rewards a lower cutoff, so tuning on real probes alone walks the threshold down until the system confidently answers anything. I set it against a negative control instead — a question the corpus has no opinion on — and put the floor in the measured gap between the worst true answer and the best piece of junk. That one decision is why the search will say it does not know, which is the behaviour that makes everything above it usable.

03

The failure with no symptom

A table can be fully embedded, correctly indexed, and completely invisible, because nothing ever asks it a question. It happened twice — first to notes, then to commits — and neither time did anything error. When I finally measured it, 85 percent of the corpus was unreachable and the assistant was reporting that it had no information about my commits while being literally truthful. Both incidents were caught by a person noticing an answer looked wrong, which is not a detection strategy. It is a structural check now: the harness enumerates every table carrying vectors and fails if any one of them has no route in.

04

A model reads the syllabus; it does not get to write to the database

Uploading a syllabus produces a proposal, and the proposal writes nothing. Every course and deadline it extracts is checked field by field against the term before a button can turn it into a row, because the failure worth preventing is not a malformed date — it is a plausible one. A syllabus prints a bare month and day, the model supplies a year, and a deadline lands in a past week nobody scrolls to, with nothing erroring. A structured proposal can be re-checked; free text cannot, so only the checkable form is allowed to write.

What’s next

Making it deployable for someone other than me — a second install carrying a business domain instead of a student one, running against a customer's own credentials. That became Docket.

The launch page

I designed and built the launch page as well, including the art. The brain and the spine are point clouds generated from a mesh rather than stock illustration — 24,000 stipple points in the hero alone — and every one of those positions is computed offline by a small pipeline of geometry scripts and shipped as data, so the page draws the art without ever calculating it. Keeping it abstract was deliberate: a photoreal brain reads as uncanny, and the thing being sold is a memory rather than an organ.

The page itself is generated rather than hand-written. I edit one template and a build script writes the HTML and its three assets from it, because the deployment serves a content security policy that permits no inline style or script — an inline block is dropped silently by the browser rather than failing anything, so the build asserts that none survive. Three harnesses then check the result instead of me looking at it, and the strictest ends by stripping the page’s head back off and asserting that five of its eleven checks now fail on the broken copy. Without that control one of them passed either way, which is to say it was measuring nothing.

The launch page hero: a brain drawn as a point cloud, with capture sources labelled down the left and outputs on the right.
The hero, and the 24,000 points it is drawn from — every one of them positioned before the page loads. The labels are the argument the art is making: everything goes in through whichever door is nearest, and comes back out by meaning.

Built with

  • TypeScript·
  • Next.js 16·
  • Postgres + pgvector·
  • Supabase·
  • Deno·
  • MCP·
  • Tailwind