The Agent That Runs a Body
The same operations agent, pointed at one person instead of a company: a daily ledger, a wearable pull, a genotyping array and a lab panel compiled into rules — and every documented failure mode reproducing exactly.
An agent that normally runs purchasing and finance was pointed at one person's food, training and lab work. No new framework: the same memory files, executors, gates and weekly close. The first ledger ran 66 days and recorded nothing, because it had no executor. The rebuilt one surfaced an off-by-one in the vendor's date layer, a calorie metric that falls as fitness improves, and an estimation error that is within 8% for whole foods and 21-27% low for formulated products.
Why a health project belongs on a site about operations agents
Everything else on this station describes an agent that runs a business: purchasing, mail, finance, governance, documents. Since mid-August the same system has also been pointed at one person — food, training, sleep, and two static clinical datasets.
No new framework was written for it. It reuses the memory files, the scheduled jobs, the executors, the gates and the review cadence that already existed. That makes it close to a controlled experiment on the operating system rather than on the person: every failure mode this station has already documented reproduced, and two new ones appeared that only show up when the subject is a body.
What this article does not claim. At the time of writing the project has under a week of measured days. There is no outcome here and no weight curve. Reading a trend off two data points is one of the failures listed below, and the article is not going to commit it in its own opening paragraph.
The stack
| Layer | What it is | Cadence |
|---|---|---|
| Ledger | one append-only markdown file | per day |
| Executor | a session-start hook checks whether today has a row; if not, the agent asks | once per day |
| Intake | a photo dropped in a folder, or one sentence in chat | ad hoc |
| Wearable pull | scheduled job against the watch platform's API; raw JSON archived | 07:00 daily |
| History mine | separate on-demand backfill over 2,569 days | once |
| Rules layer | 8 named rules compiled from a genotyping array and a lab panel | static |
| Weekly close | script writes the week's row from measured data only, no model involved | Sunday |
| Dashboards | generated HTML, one per layer | per run |
| Write-back | completed activities appended to the calendar, tasks to the task database | ad hoc |
Nothing in that list is health software. It is the shape of any other operational subsystem: an intake, a ledger, a scheduled pull, a gate, and a weekly close.
Version one: a ledger with no executor
The first version of the ledger ran for 66 days and recorded zero entries.
The diagnosis was not discipline. There were three causes, and only the first one matters:
- Nobody asked. The ledger existed; the question did not.
- Over-design at the start — 14 daily reminders specified in a phase when not one of them was running.
- Structure had to be created ahead of use. Weekly tables were built by hand; when nobody built week two, the log ended.
Version two changed exactly three things — one executor, one question a day, append-only with no pre-built tables — and, more importantly, it removed things. The 14 planned reminders were cancelled as the compensation for adding the hook, so the net complexity of the change is negative.
The question itself, verbatim:
Project 85: what did you eat today, and did you move? (Bullet points are fine, I'll do the maths.)
The anti-rules attached to it carry as much weight as the question. Do not ask about breakfast: it is normally skipped, so an empty field is not a gap. Do not ask about weight: it arrives from the watch. Never more than once a day. Never moralise — a fast-food lunch is recorded as numbers, not as a comment. The instrument is a gauge, not a supervisor, and a gauge that editorialises stops being consulted.
The intake had to lose its shape
Version one failed because the input needed a form. Version two accepts a photograph with no accompanying text at all:
Classify as a meal if A and (B or C):
A phone photo, timestamp today or yesterday
B frame contains food, a drink, a plate, packaging, a receipt or a nutrition label
C file time falls inside a meal window
Time is a supporting signal, never the only one. Content decides.
Within two hours of the loop going live, three unprompted inputs arrived — a photo captioned only "testing whether you know what to do with this", a cancelled training session, a coffee. The evidence points one way: version one produced nothing for 66 days not because reporting was unwelcome, but because reporting had to be formatted.
Every derived row carries an explicit [estimate] marker and a declared error of ±20–30%.
Genome and blood work are a rules layer, not a report
Two static datasets sit under the project: a consumer genotyping array of roughly 700,000 markers, and a laboratory panel that is four years old.
Neither is consulted as a document. They were compiled once into 8 named rules with explicit weighting — three carrying about 80% of the expected effect, four about 15%, one about 5% — plus a scoreboard of 8 numbers and a list of anti-rules: things not to do, each with the reason attached to this person's own data rather than to general advice.
The most useful output was not a recommendation. It was this:
5 of the 8 scoreboard numbers either did not exist, or were four years old.
A scored report reads like a set of conclusions. Operationally it is a list of measurements nobody has taken. The first real job of the rules layer was to turn percentiles into an order list with prices and a booking route — and on checking, the prices in the first version came from a physicians' tariff rather than a self-payer list, understating one test by 46%. The generalised version of that trap, and of the scoring trap underneath it, is in a-percentile-is-not-a-diagnosis.
Clinical values are carried in the system as ratios rather than absolutes: one liver enzyme at 1.9× its upper reference, one lipid roughly 45% above the low-risk threshold, filtration mildly reduced. Same rule as for business figures — the ratio carries the finding, the absolute value carries identity.
The metric that inverts
Machine-reported energy expenditure across four sessions on the same stationary bike, ordered by average heart rate:
| Avg HR (bpm) | kcal/min |
|---|---|
| 121 | 9.1 |
| 127 | 10.1 |
| 133 | 11.2 |
| 136 | 11.3 |
The first three points are close to linear: +0.17 kcal/min per beat. The fourth breaks the line — the model predicted 11.7 and the device reported 11.3, 3.5% low. Two rules came out of that table.
The number is a function of heart rate, not of work. The device derives calories from heart rate. The 121 bpm session did more mechanical work than the 133 bpm one — same duration, higher resistance, higher cadence — and reported 2.1 kcal/min less. So as aerobic fitness improves, the same session reports fewer calories. That is an improvement arriving disguised as a decline, and any dashboard plotting it as a trend will read it backwards.
Plan against the lower bound. The working range is 9.1–11.3 kcal/min; the planning figure is 9.1. A budget built on the top of a measured range is a budget that does not exist.
Underneath both sits the division that reallocated the whole project: 500 kcal of exercise is 0.065 kg of fat, against a weekly aerobic ceiling of 150–180 minutes above which training in a deficit starts costing muscle. The arithmetic is unambiguous — the plate decides the weight, the bike decides the fitness. Nothing else the instrumentation produced changed as many decisions as that one number.
The date layer lied twice
The vendor's rows do not all describe the same day. Daily metrics belong to day D; sleep belongs to the night that began on D, which the vendor files under D+1; readiness is computed for the morning of D. Joined naively into one row per day, readiness sits one day out of step with sleep — and a bad score was attributed to a good night before anyone noticed.
The second lie was quieter. The scheduled pull ran at 07:00, before the watch had synced. The API returned an empty skeleton rather than an error, the job exited 0, and the day was written with sleep missing. The only tell was payload size: 78 KB instead of about 230 KB.
Both are dissected in the-readiness-that-belonged-to-yesterday, including the two-line correlation test that proved the offset.
One statistical guard from the same subsystem is worth copying directly: a per-year average is reported only if that specific metric has at least 100 measured days in that year. The first run cheerfully compared a year holding 355 measurements against a year holding 6. Mechanically coupled pairs are excluded from findings by name — where the platform computes one metric from another, their r = 0.71 is arithmetic, not insight.
Estimation error is not uniform
The agent estimates calories and protein from photographs and text. Once labels started being checked against those estimates, the error turned out to have structure:
| Item class | Error |
|---|---|
| Simple whole foods (ham, cottage cheese, yoghurt, fruit) | within ±8% |
| Formulated products (drinks, bars) | 21–27% understated |
| Salt content of one packaged meat | 23% understated |
The cause was an anchor — "a protein drink is about 150 kcal" — which silently ignored added sugar, added fibre and the milk base. The rule adopted: no estimate for a formulated product. Read the label, or mark it [estimate ±25%] and do not build a decision on it.
The subtler error was a transfer. A calibration point — protein plus two vegetables, no oil, the best ratio in the log — was carried from a cooked dinner at home to a snack eaten at a desk. The number transferred; the executor did not. Nobody cooks at a desk. Before reusing a calibration point in a new context, check that the person or process that produced it also exists there.
The ratio that changed behaviour
The measure that actually changed decisions was neither weight nor calories. It was kcal per gram of protein, computed per meal.
Across logged meals it ranged from 7.9 to 31.2; the daily average required to hit both targets is 10.9. What makes it operationally different from a calorie count is the action it implies. A calorie count implies less. A ratio implies a different carrier:
Four skewers instead of three plus fries: −400 kcal, +28 g protein, same order, same price, no sense of restriction.
And the record-holding meal was not the most virtuous-looking one. It was the one that needed no preparation — three packages opened at a desk. That matters more than the ratio itself, because it is repeatable on a bad day, and a protocol that only works on good days is measuring the wrong variable.
The weekly close refuses to guess
A script with no model in it writes the weekly review row: weight delta, steps, sessions, sleep, resting heart rate, active calories — all measured — and parses intake and adherence out of the ledger. Where the ledger is silent, it writes —. From its own header:
The script therefore never reports "adherence 0/7" when records are merely missing; it distinguishes zero from unknown (—vs0).
That distinction is the entire design. A weekly report that renders missing data as zero manufactures a failure that did not happen, and the reader stops trusting the instrument long before they stop trusting the plan.
Cost, and the kill criterion
The carried cost is about fifteen lines in a hook, one question a day, and roughly two minutes of writing. The compensation was the removal of 14 planned reminders.
The kill criterion is written down and dated: if fewer than 10 days out of 31 are logged by a fixed date, there is no third version. The agent reports the fact and proposes either an off-the-shelf app or stopping the tracking entirely. A habit that needs a third bespoke attempt is not a tooling problem.
The gate that does not cover bodies
One closing finding, and it is about this site rather than about the project.
The publishing pipeline has an anonymity gate. It counts business categories — revenue band, headcount, sector, geography — and fails the build when more than one appears in a single artifact. It has caught a real leak before, on an article that had already been marked as verified.
Until this article it had nothing at all for a body. Age, city, genotype, lab values and a training schedule appeared on no list, and they cross with the business bands in a way the gate could not see: one business band plus one clinical detail narrows a population faster than two business bands do.
The gate now carries a body category, counted alongside the business ones against the same threshold of two. That change was made before this article was published rather than after, which is the only detail about it worth repeating: a rule enforced by remembering it is a rule with no executor, and that is exactly where this article started.
See also
the-readiness-that-belonged-to-yesterday · a-percentile-is-not-a-diagnosis · a-rule-without-an-executor · empty-is-not-zero · the-anonymiser-that-passed
$ head -12 the-agent-that-runs-a-body.md
$ cite the-agent-that-runs-a-body
Citation id SV-9353 is stable. It resolves at
https://stillvalid.dev/c/SV-9353 even if this artifact moves to another section,
which a bare URL does not survive. The verification date is part of the citation on
purpose — this site says out loud when it last checked.
[The Agent That Runs a Body](https://stillvalid.dev/architecture/the-agent-that-runs-a-body) — stillvalid, SV-9353 (deep-dive, verified 2026-08-25)