{"path":"research/dogfood-run-4-agent-surface.md","content":"# Dogfood Run 4: The Agent-Readable Surface, Consumed by an Actual Agent\n\n*Aug 12, 2026. The first time the agent-readable graph surface — built in July specifically for AI readers — was read by one. Everything below is a live observation against deliberus.com, not a code review. Five bugs found, four fixed, one narrowed and escalated.*\n\nThe extraction path needs an authenticated session, which is a human-only step, so this run exercised what is public: `llms.txt`, content negotiation, the twins, the instrument suite, and the feed. That turned out to be enough to find a four-month production outage.\n\n> **Follow-on, 2026-08-17.** This run treated agent readability as a property to verify. [agents-as-a-consumer-class.md](agents-as-a-consumer-class.md) treats agents as a **consumer class** and takes the run's cross-cutting lesson further: once a client rather than a courteous reader is on the other end, the § 5 authority risk gates shipping, because an agent reads ten thousand badges where a person reads one.\n\n---\n\n## Method\n\nThe container running this session has no LAN reach to Darwin and no credentials, which is a good approximation of the intended consumer: an outside agent with nothing but the public HTTPS surface. Every finding below is a status code or a response body from `https://deliberus.com`.\n\n## Findings\n\n### A1. All three entry points in `llms.txt` were 404 *(fixed)*\n\n`llms.txt` exists to orient an AI reader, and it opens with three links:\n\n```\n- [Claims index](https://deliberus.com/claims.json): all claims (JSON)\n- [Extractions index](https://deliberus.com/extractions.json): analyzed sources (JSON)\n- [Concepts index](https://deliberus.com/concepts.json): contested concepts (JSON)\n```\n\nAll three returned `404 {\"detail\":\"Not Found\"}`. An agent following the front door in order hit three dead links before reaching anything that worked. The singular twins (`/claim/{id}.json`, `/extraction/{id}.json`, `/concept/{term}.json`) were implemented; the plural index twins never were. The generator wrote the promise and nothing checked it, because until now nothing had followed it.\n\nFixed by stacking a second path decorator on the three existing plural handlers — no wrapper, no duplicated logic.\n\n### A2. The project's own documentation was its least machine-legible surface *(fixed)*\n\n`llms.txt` points six links at `/api/docs/{name}.md`, which returns `content-type: application/json` wrapping the markdown in `{path, content}`. The human URL `/docs/{name}.md` returns an HTML SPA shell. So a reader following markdown-looking links from a file written for machines got JSON, and the only markdown-shaped URL returned HTML.\n\nFixed with Accept negotiation on `/api/docs/{path}`: `Accept: text/markdown` now returns the raw document, the web app's default `*/*` keeps the JSON envelope, and `llms.txt` says so instead of implying otherwise.\n\n### A3. The completeness oracle called the corpus's most-examined node a copout axiom *(fixed)*\n\nThis is the red team's predicted oracle-vs-brake contradiction, confirmed live — and the confirmation was sharper than the prediction. Fetching `claim_901c11589f34`, the corpus's **first classified terminus** (`fittingness`, with a challengeable verdict-claim ground), the public markdown page said both of these, four bullets apart:\n\n- **Unsupported value premises**: 1 — asserted values with no support subgraph yet (**the copout-axiom signal**)\n- **Terminus type (confirmed)**: `fittingness` — … **survives the CQ descent**\n\nRoot cause was simpler than the red team's reading suggested and did not involve the sacredness brake at all (`weighing` was `null` on this node). The oracle's subgraph query **never selected `terminus_type`**, so it could not see a confirmed terminus even in principle. The verdict also came back `bundled` — \"nobody has looked\" — for a node walked to bedrock and typed on the record.\n\nFixed: the query now selects `terminus_type`; confirmed termini are excluded from `unsupported_value_premises` and reported in a new `classified_termini` list (lossless, not silenced); a root with a confirmed terminus gets the verdict `terminal_by_verdict` rather than `bundled`; and the markdown surface no longer prints a sorry-frontier line that contradicts the terminus line below it. Four regression tests pin all of it.\n\n**Deliberately not fixed**: braked value premises (`weighing_state`) still get flagged. That half is the live founder question about re-scoping the sacredness brake, and the founder's lean is a *differentiated voice* rather than silence. This fix touches only terminus-blindness, where a confirmed verdict is plainly not silence.\n\n### A4. Every feed mode returns 500 *(narrowed, escalated — not fixed)*\n\n`GET /api/feed` fails identically on all eight modes: `needs-help`, `bridging`, `contested`, `recent`, `strongest`, `weakest`, `divisive`, `needs-votes`. The landing page links this surface, and it is the intended front door for the friends-round dogfooding.\n\nNarrowing, without prod-log access:\n\n- All modes fail identically, so the fault is in something shared rather than in one branch.\n- The only shared component unique to the feed is `public_claim_filter()` from `deliberus/graph/maturity.py`. Grep confirms **no other module uses it** — every working endpoint bypasses it.\n- It entered the feed in one commit, `45ee3d6` (2026-04-08, \"Implement deliberation draft lifecycle scaffolding\").\n- `tests/test_feed.py` has 30 tests, and the two that touch this assert the query *string* contains `maturity_stage` against a mocked graph. The Cypher is therefore never executed against FalkorDB by any test — the project's own \"a mock hides the real contract\" lesson, exactly.\n\nBest hypothesis at roughly 70%: **the feed has been broken in production since 2026-04-08** and the mocked suite kept it green. Not proven, because Cypher cannot be executed against Darwin's FalkorDB from here, and a blind rewrite of a filter shared by eight queries on a 70% hypothesis is what the circumstantial-evidence rule forbids.\n\nThe traceback already exists — the handler calls `logger.exception` — so one command settles it:\n\n```\nssh darwin dlogs 2>&1 | grep -A25 \"Failed to get feed\" | tail -40\n```\n\n**What was fixed**: the 500 path logged and notified nothing, so a user-facing outage could persist silently for months. It now raises a `system_error` notification, which is the interrupt-worthy tier the monitoring design already specifies.\n\n### A5. `fully_exposed` at depth 0 reads as \"scrutinized\" when it means \"nothing to expose\" *(calibration finding, not fixed)*\n\nA sampled claim with no decomposition returned `verdict: fully_exposed, exposure 1.00, depth 0`. Technically consistent — an undecomposed leaf has no hidden support — but on the public surface it reads as *this claim has been fully examined*, which is the opposite of true. Since the corpus is overwhelmingly claims at depth 0, an agent or funder summarizing exposure across the graph would conclude it is far more scrutinized than it is.\n\nLeft alone deliberately: renaming a verdict is ontology vocabulary, which flows through the founder. Flagged because it interacts with the wager's accounting — an exposure metric that reads high by default is a metric that cannot embarrass us.\n\n### A6. The corpus was advertised as six documents out of 130 *(fixed)*\n\nFound the following day, from the opposite direction: an external ChatGPT thread reported that it *\"can't access Deliberus' internal research docs from the public web\"* and that search *\"only surfaced unrelated 'Deliberating with AI' papers\"*. That was our bug, not its failure, and it had two causes.\n\n`llms.txt` linked **6 of 130** documents — a curated entry point presented as if it were the inventory. An agent reading it concluded the research corpus was six files, which is why the synthesis it produced leaned on five documents and read the project as smaller than it is.\n\nAnd `sitemap.xml` listed all 130 docs at `/docs/<path>`, a route that renders client-side. Measured: that URL returns **1,957 bytes of shell containing none of the document**. So anything following the sitemap — the one artifact whose entire purpose is telling crawlers what exists — received empty pages for the whole corpus.\n\nThree fixes. `llms.txt` now enumerates every document and names the recursive machine index (`/api/docs-index`) so an agent can enumerate without scraping the file. The sitemap carries the content-bearing `/api/docs/<path>` alongside the human URL. And `/docs/<path>` now returns raw markdown under `Accept: text/markdown` at the canonical URL, keyed on Accept rather than User-Agent, matching the claim and extraction twins — the URL a human shares is now the URL an agent can read. Three regression tests pin the enumeration.\n\nThis is the same class as A1: the agent-readable surface promising more than it delivered, with nothing checking the promise. It is worth stating plainly what it cost — an outside model spent part of a months-long research thread believing the project's documented reasoning was mostly unavailable, and reasoned about a smaller Deliberus than the one that exists.\n\n## What worked, and is worth saying\n\nThe parts that were built for this consumer largely delivered. Content negotiation returns `text/markdown` and `application/json` correctly with `Vary: Accept`; the `.md` and `.json` twins both resolve; HEAD works; the reader preamble (\"a claim's presence here is not an endorsement\") is present and well-judged; the claim page's support and attack sections render with strengths and clickable neighbors; `sitemap.xml` and `robots.txt` are live.\n\nThe instruments answered on real data: the hinge score returned a baseline strength of 0.489 with per-descendant sensitivities across 11 nodes; the QBAF badge returned `GROUNDED`; disagreement preservation scored 0.909 on the assisted-dying extraction with 3 of 33 pairs flagged as flattened; the residue map reads 2 classified termini, 1 residue, fraction 0.5. And the discursive-dilemma flag **fired** on the hand-decomposed weighing claim — `dilemma: true`, premises accepted while the conclusion is rejected — which is the List-Pettit shape the instrument was built to catch, occurring in live data rather than in a test fixture.\n\n## Measured side-finding: extraction was not blocked\n\nThe TODO items saying run-3F and the three-scales experiment are blocked on a Gemini prepay top-up are stale, and this is now a measurement rather than a reading of two documents. `GET /extractions` exposes a `timestamp` per source; the newest is **2026-07-11 21:51 CEST**, which is after the July 9 credit depletion and after the swap to the postpay key. The pipeline demonstrably ran on July 11.\n\nCaveat kept honest: a successful run a month ago does not prove credit is live today. What is settled is that the *documented* blocker — \"needs a prepay top-up\" — describes a state that was already resolved.\n\n## The pattern across all five\n\nEvery finding is the same shape, and it is the shape the project already named: **a system with no confession channel reports success.** The index links 404'd with nothing watching. The oracle contradicted itself on the most carefully examined node in the graph because it never fetched the field that would have told it otherwise. The feed 500'd for four months behind thirty green tests that mocked the database. And `fully_exposed` at depth 0 is a metric that flatters by construction.\n\nThe instruments built to keep Deliberus honest are good. What this run found is that **the surfaces reporting on those instruments had no instrument of their own** — no test followed a link in `llms.txt`, no test executed the feed's Cypher, no notification fired when a public page failed. That is the next place the confession principle has to reach.\n\n---\n\n**See also**: [dogfood-run-1-friction-log.md](dogfood-run-1-friction-log.md) · [dogfood-run-2-orthogonal-experiments.md](dogfood-run-2-orthogonal-experiments.md) · [frontier-extraction-experiment.md](frontier-extraction-experiment.md) · [red-team-synthesis-2026-07.md](red-team-synthesis-2026-07.md) · [peer-review-and-the-reasoning-layer.md](peer-review-and-the-reasoning-layer.md)\n"}