I build AI systems and ship a dating product. These are the notes I take along the way — the architectures that held up, the ones that didn't, and what running the thing taught me that designing it never could.
- All
- Riteangle 17
- Decision systems 10
- Agent architecture 8
- Operations 8
- Context engineering 6
- Guardrails 6
- Data platform 5
- Agent evals 4
- Agentic architecture 3
- Advertising 2
- Marketing 2
All writing
Automating my job hunt outreach with an agent that finds, researches, and drafts — and leaves sending to me
A director/VP-level job search runs on outreach nobody automates safely, because the risky part is the send. This system automates finding, researching, and drafting end to end, and keeps the one irreversible step out of its code entirely. Underneath it, memory is split three ways: a table store for quantities, an embedding index for meaning, a graph for relationships. The question is no longer how to store data for a human to read, but how to organise it so an agent retrieves the right slice before a model call it pays for. The retrieval half gave the more surprising results: indexing each document's own title bought nearly what the vector index did, four employers' near-identical policies could only be separated by file path rather than by any retriever, and a negative result about rank fusion did not survive re-running it on a larger corpus.
44 min read
Forward-deployed engineering under five job titles, and the handoff test that decides if it worked
A company that embeds an engineer inside one operation is buying a specific kind of work, and almost never writes down the part that decides whether it paid off. I did that work under five different titles before I had a name for it. Here is the defect that showed up in every one of them, and the test I never once wrote into a handover document.
9 min read
Five dbt detectors sharing one output contract, and the timezone table that breaks all of them twice a year
Contact-centre rules about when you may call someone carry real penalties, and they change — so the structural question is whether adding next year's regulation means rewriting this year's checks. Five detectors that know nothing about each other, each emitting the same three fields, means a new rule is a new query rather than a schema migration. The interesting part is that the architecture is sound and the whole thing is still wrong for two months a year, because of a hardcoded timezone table nobody thought was interesting.
5 min read
Tiered evidence for a sport with almost no data, and the accuracy headline that failed a per-class check
Most sports modelling assumes abundance. Women's international T20 gives you a career of thirty appearances and matchups four deliveries deep, and the standard approach turns that into confident numbers derived from noise. So graceful degradation is the actual product: four declared tiers of evidence, adjacent leagues blended at a discount, thin matchups discarded rather than trusted, and every output labelled with which tier it rests on. It also includes the accuracy figure I stopped quoting once I checked it per class.
6 min read
Integer programming and ten thousand Monte Carlo rollouts in 512 MB, for an optimal XI in four seconds
Solvers want memory and time; a free-tier serverless function gives you 512 MB and thirty seconds, and a person is watching a spinner the whole time. This is what fits: integer programming, ten thousand Monte Carlo rollouts and two graph models, answering in about four seconds, with the language model firewalled to commentary that can be switched off without losing the answer. The ceiling turned out to force better decisions than an unconstrained environment would have.
8 min read
Application-layer regex against managed guardrails: choosing by who has to sign the rule off
A safety rule written in your own code can be read, reviewed and approved by a domain expert, and protects exactly the one route you remembered to wire it into. The same rule bought as managed infrastructure is opaque, catches paraphrase you never enumerated, and applies to every path including ones written six months from now by someone who never read your docs. Having built both, the split I would defend is to write the rules you must be able to defend, and buy the rules you must not be able to forget.
6 min read
A replay harness gating an agent's edits at zero divergence, and the assumption ontology that explains each one
Letting an agent edit a production configuration from a plain-English request is reckless unless something can answer what the change would actually do — offline, thousands of times, without touching live traffic. So a shadow reimplementation exists, and it earns the right to speak by reproducing every routing decision production has already made: 5,501 of 5,501, zero divergences, against a threshold set in advance. The same harness then makes a 7,900-line refactor reviewable, because behaviour preservation is demonstrated rather than argued.
7 min read
MILP, counterfactual regret minimisation and POMDP: choosing the solver by what you cannot observe
Reach for game theory on a problem with no opponent and you have built machinery you did not need; reach for optimisation when someone is actively choosing against you and your clean optimum will not hold still. Three decision-support systems built in sequence, where the rungs are not algorithms but what you are allowed to assume you know. The most transferable finding is that picking between equilibrium concepts is a question about who commits first, and once you have both solvers, the difference between them prices the commitment.
9 min read
What LangGraph buys over a hand-wired agent pipeline, and the one-line reducer that stops two parallel branches from clobbering each other
Say your pipeline has a threat-scoring agent and a coalition-modelling agent, and both append their findings to the same warnings list on a shared state object. Neither depends on the other, so a for-loop wastes time running them back to back — but fire them off concurrently and both read the old list, both write their own copy back, and one agent's findings silently vanish. A plain loop makes you choose between slow and wrong. A graph runtime solves exactly this, and nothing more exotic — I built two engines on it (a Game of Thrones strategy oracle and an intelligence-operations oracle), each eight to ten nodes over one shared typed state, where a one-line rule declaring that a field merges by appending rather than replacing lets two agents write the same list safely, and a gate kills a broken run before the expensive model step. If your pipeline is honestly a straight line — fetch, then compute, then summarize, each step waiting on the last — you have nothing to parallelize and nothing to merge; write the for-loop and move on.
16 min read
An LLM auditor and a deterministic one in the same pipeline, and the check only one of them can run
A pipeline produces a table of numbers and a written analysis, and somebody senior makes decisions from the summary — so something has to check that the prose actually describes the data. I built that check twice by accident, weeks apart: once as a model call, once as plain Python. The arithmetic version is obviously right for four of the five checks, and reimplementing it silently deleted the fifth, which was the only one a model can do.
8 min read
A deterministic keyword gate ahead of the LLM, and the clinician sign-off a classifier can never get
Some questions a patient asks are urgent in a way the patient does not know, and getting one wrong means telling someone to wait until morning. A model would detect those better than a keyword list by every ordinary measure — it handles paraphrase, typos and mid-sentence language switching. It is used anyway, because a list of phrases cannot be argued out of its judgement by the message it is reading, cannot fail open during an outage, and can be read line by line and signed off by a clinician.
7 min read
When to use a vector database, when a SQL table, and when a graph, and what it costs to pick wrong
For decades we modelled data so a human could read it, which is why tabular won. The first consumer now is an agent, and an agent's binding constraint is not legibility but token cost. If context were free you would hand a frontier model all 1,100 documents and ask; it is not, so the job becomes retrieving the smallest correct slice. An agent that drafts outreach kept quoting 600 calls a week from a summary someone wrote once, when the tracker said about 1,850. The fix was to split the archive by shape into three local stores, an embedding index for prose, DuckDB for spreadsheets and a graph database for relationships, with a rule deciding where each question goes before anything is searched. Eight lookups that would have cost a million tokens of reading now cost sixteen thousand. This is what each store is good at, what picking the wrong one costs, and the measured results.
29 min read
Nine attribute vectors instead of embeddings, and the pure scoring function that makes counterfactuals exact
An embedding can tell you two people are close and cannot tell you which of five hundred axes did the work, because the axes mean nothing individually. When the output is a ranking that decides who someone meets, the question that matters is not who is similar but what would change the answer. Hand-designed dimensions with a pure scoring function make that exact rather than plausible — and let sensitive attributes be excluded by a weight pinned to zero instead of by a promise.
9 min read
Agent replay with writes suppressed at the boundary, and the side-effect ledger that proves nothing escaped
To debug an agent you need the prompt it actually saw, and the only way to get that is to run it — which sends messages to real people. This harness is the production code path with the writes cut at the boundary, and every suppressed effect recorded in plain language rather than silently skipped. It is very good at showing you one turn and completely unable to tell you whether that turn was any good, which is the state most eval work is actually in.
8 min read
NULLS FIRST and a client insert default corrupted an agent's context window for nine turns running
An agent's context window is a query result, and it is the one artifact nobody audits — assembled at runtime, used once, discarded. Two separately correct, separately documented library defaults met in the middle and pinned a single old message to the top of every window, so the agent read it as the newest thing said and escalated accordingly. This is what that failure looked like, why every natural diagnosis pointed at the prompt instead, and how you test for a bug that lives in the gap between two libraries.
8 min read
Deterministic overrides after generation, and the incident behind each one that a prompt could not prevent
A prompt is an instruction, not a constraint, and a model told ten times not to do something will still do it on the turn where the sentence felt right. Worse, a refusal is a property of one model call and does not survive being wrapped in an orchestrator. So the rules that actually have to hold live in code that runs on the output — five checks, each written after something went wrong in production.
9 min read
LLM-as-judge in the send path, and the human-label calibration that gives its score an error bar
Once a system generates text faster than anyone can read it, the choice is between shipping unreviewed output and building something that reviews it — and human review does not scale to every message while sampling misses exactly the rare failures worth catching. A small model grading a large one costs a fraction of the thing it guards, which is what makes total coverage affordable rather than aspirational. The catch is that a judge nobody has scored against human labels produces a number with no error bar, and a confidently wrong judge looks exactly like a working one.
21 min read
A 48-hour hand-off that expires without deleting, and the single clock the cron and the agent both read
An agent finishes its work and hands the result to a person, which leaves a third party waiting on someone who may simply be busy. A deadline is necessary to protect them; a deadline that deletes punishes the wrong person for a scheduling accident. The resolution is that expiring and deleting are different operations, and the timeout should do the smallest thing that frees whoever is waiting.
9 min read
Data, prompt, context, loop and harness engineering: the tooling, cost and failure mode of each layer
Most failed agent projects are a data problem with an orchestration budget: a team buys a framework, builds an elaborate loop, and discovers the underlying data was six hours stale and nobody owned the definitions. These five disciplines arrived in sequence, each because the previous ran out of road, and each has a different unit of work, cost profile and characteristic failure. The last one is the only one whose purpose is to make the system smaller.
12 min read
Kafka to MCP in twenty-four months: what the agentic wave added to the data stack, and what it left untouched
The story everyone tells is that AI replaced the data stack. Two years of attending India's data and AI conferences says something more useful: Kafka, Spark, Airflow, dbt and Iceberg run unchanged from May 2024 to May 2026, four new floors were built on top of them, and the layer that actually got displaced was business intelligence. If you are deciding whether to rebuild your platform to do AI, the evidence says the work is above your serving layer, not below it.
8 min read
Enforcement was version one, fairness was version four
I built a productivity measurement system for a distributed team, with a five-step consequence ladder ending in exit. The ladder worked from the first day. The component that would have made the comparisons fair was version four, and its design was never written.
10 min read
Event-time partitioning in Iceberg, and the retrieval filter that refused its own answer
An agent reading a governed warehouse has no equivalent of a human analyst's instinct to double-check a number before repeating it, and that gap is not hypothetical. I built the reference data-and-retrieval architecture end to end on a real public feed and partitioned the lakehouse by each record's own event time rather than by the date it arrived, which is the one choice that lets a governance layer measure how unsettled a count still is. Asked an ordinary question, the retrieval pipeline's filter step used that measurement to drop two of six documents outright and label the rest with how many times each had already changed — including one drop that had been perfectly safe to cite forty-eight hours earlier.
13 min read
Governance was the first thing to stop paying for itself
I was asked to hit a cost target across three functions. The plan I came back with removed my own seat, on the grounds that it was buying governance at the price of three times the execution capacity. Here is the arithmetic and the argument, including the part I never got to verify.
8 min read
pgvector and Neo4j sitting on top of Postgres, and the multi-hop question neither alone can answer
A team building past prototype asked how to stand up a proprietary small language model, and the advice I gave back — try vector and graph retrieval instead, cheaper to build — undersold what was actually being decided. Each layer buys immunity from a different failure, not a discount on the same one. I grounded the claim in three systems I've actually run: a dating app's relational core in production, a graph demo that models that same app's match-and-handoff chain in Neo4j, and a governed lakehouse fusing pgvector with a lineage graph. Stacked in the right order, the three answer a question — why did a specific connection go quiet — that no single layer can answer alone, and the order you add them in is not interchangeable.
12 min read
Routing decisions without a model in the loop
Before riteangle I ran an allocation system with revenue attached. It had the same shape — a language model at the ingestion edge, arithmetic in the middle — and in two respects it was better than what I have now. Here is the precedent, and the thing I should port back.
9 min read
The curriculum was the incident log
I had four weeks to move one person's operational knowledge into a team's heads. So I built the syllabus backwards out of three real incidents, organised around one question. The question mattered more than any of the content.
8 min read
The same grid, three kinds of evidence
I used one instrument to assess my team, myself, and the market I wanted to move into. All three produced a capability grid that looked identical. Only one of them had evidence underneath it, and nothing in the presentation said which.
9 min read
Twenty context blocks assembled per turn, and the output schema derived from which ones arrived
Prompts built by string concatenation at the call site drift from whatever you tested, and the context an agent actually saw is thrown away the moment it replies. Here assembly is a subsystem: twenty blocks loaded in parallel, volatile facts grounded in SQL rather than recollection, and an output schema derived from which blocks arrived — so the model is never asked for a field this turn cannot fill. Nothing in it counts a token, which is the honest gap.
10 min read
A fail-closed regex and a fail-open model judge on every message, and the single corrective retry between them
An agent writing on someone's behalf produces text a real person reads and attributes to a real person, and nobody can read all of it before it goes. Sampling misses exactly the rare failures worth catching. A small model grades every outbound message against an enumerated rubric at roughly a sixth of the generator's budget, which is what makes total coverage affordable — and the uncomfortable part is that nothing has ever checked whether the grader is right.
12 min read
Cross-thread agent memory with provenance omitted at schema level, so shared recall cannot leak its source
When one person is represented by several agents, each working for a different counterparty, he answers the same question over and over — and the obvious fix, shared memory, quietly tells each agent about conversations it was never part of. The value and the hazard come from the same fact. This is a memory store where the origin of a row was never recorded in the first place, so it cannot be recovered by a query, an agent, or anyone with database access.
12 min read
Min-cost max-flow picks the match, and the confidence multiplier that keeps an unproven claim at 30% of its weight
A generative model cannot be regression-tested, cannot explain a specific past output, and cannot be told apart from its own drift — which makes it the wrong thing to put in charge of who two people meet. So the models here read evidence and write messages, and a weighted dot product plus a flow solve makes the actual call. The payoff is that any ranking can be re-derived exactly by changing one input and running it again.
12 min read
Meta ads, loop engineered
Half the button presses were never reaching Meta, and the screen that would have fixed it was greyed out. The way through was an error message that refused the right thing for the wrong reason.
9 min read
Snapchat advertising, loop engineered
A dashboard tells you what happened. A loop makes the next decision better than the last one. Here is the whole arrangement for one ad account, including the half of it that is still open.
7 min read
Designing global talent systems
The spreadsheet always shows the wage differential. The coordination, leadership, and redesign taxes are what it leaves out — and they are where the cost actually lives.
7 min read
What it takes to measure one tap
Four systems, none of which can see each other, cooperating to record that somebody pressed a button. Here is the whole arrangement and why each piece is there.
7 min read
Zero is not a measurement
I spent a day on a conversion number that read zero. Three separate things were wrong, and not one of them raised an error.
10 min read
Starting a notebook in public
Why I'm writing here, and what I intend to put in it.
1 min read