← All projects
In build

AgentDesk

Three AI agents on a production line, with a reviewer that is allowed to say no.

BuiltAug 2026
RoleSole developer
Live runs13
StatusRepo private

What it does

A researcher agent gathers sourced facts about a company. A drafter turns those facts into a short outreach email and an account brief. A reviewer then checks the draft against the researcher's claims and scores it out of ten. Nothing below an 8 reaches a human.

The interesting constraint is the last one. If a draft fails, it goes back for revision. If it fails again, the retry budget is spent and the work escalates to a human queue rather than being released at a lower standard. The system is built so that "not good enough" is a permitted outcome — most agent chains I've seen will always hand you something.

How it's wired

  1. ContractsEach agent is a skill definition with a strict JSON schema on its output. A stage that returns malformed data fails loudly instead of passing garbage downstream.
  2. ResearcherRead-only. Fetches and returns claims with real source URLs attached — never prose the next stage has to trust.
  3. DrafterWrites the outreach email and account brief from the researcher's claims only.
  4. ReviewerCompares the draft line by line against those claims, flags anything unsupported or generic, and scores it.
  5. GateScore of 8 or higher releases. Below that, revise. Two consecutive blocks, or an exhausted retry budget, escalates.
  6. RecordEvery attempt — brief, drafts, verdicts, outcome — is written to disk as an audit trail.

Incident log

Four of these were found in a single live debugging session, and the fifth in an instrumentation audit afterwards. The fifth is the one I'd want to talk about.

01

The researcher was inventing its sources

Symptom

Runs completed and produced claims with plausible-looking source URLs attached. The URLs did not go anywhere.

Root cause

The researcher's skill definition required a web search tool. No such tool had ever been wired into the runtime. Asked for sourced claims and given no way to fetch anything, the model produced sourced-looking claims instead.

Why it mattered

This is the failure that undermines the entire premise. The reviewer's whole job is checking the draft against the researcher's facts — if those facts are fabricated, the quality gate is validating one invention against another and confidently passing it.

Fix

Wired the search tool in properly. Later runs produce claims with source URLs that resolve, which is what makes the reviewer's verdict mean anything.

02

Schema validation would have failed every live call

Symptom

Live runs failed with a 401 that I initially chased as an authentication problem.

Root cause

Two separate faults stacked. The API key had only ever been set in a single terminal session and was never persisted, so any new process had no credentials. Underneath that, required fields in the run metadata were never being populated — meaning that even with a valid key, every live call would have failed schema validation anyway.

Why it mattered

The visible error pointed at the wrong layer. Fixing the key alone would have produced a new failure and the appearance of a second unrelated bug. Worth remembering that the first error you see is often not the first error that happened.

Fix

Key persisted at user scope; metadata populated at run construction. Also raised the token ceiling for the drafter and reviewer, which extended reasoning had been quietly consuming, and removed a print helper that was dead code.

03

The best run I ever had was not saved

Symptom

A run escalated exactly as designed — the reviewer flagged real drift from the sourced claims, the revision hit the same flag, the retry budget ran out, and the work went to a human queue instead of shipping. It was the single best demonstration the system had produced. The claim text, draft bodies, and reviewer flag detail were all gone.

Root cause

An audit found three independent causes, any one of which alone would have lost the data. The audit function only printed to the console and never wrote to disk. The serializer deliberately stripped the brief, draft, and verdict to keep the run log small. The draft and verdict variables were overwritten on each retry, so earlier attempts were already gone from memory. And the disk write itself sat inside a single except branch — so when the process died from credit exhaustion rather than a contract error, persistence was skipped entirely.

Why it mattered

Every one of those decisions was locally reasonable. Keeping the log lean is sensible. Reusing a variable is normal. Handling the error you expect is what you're taught. Together they meant the system had no durable memory of its own best behaviour, and I didn't find out until the moment I needed it.

Fix

The run record now accumulates the brief once plus per-attempt drafts and verdicts. The entry point wraps in try / except ContractError / except BaseException / finally, so persistence happens on any exit path. A new interrupted outcome distinguishes the process dying from the four real gate decisions. Verified against a fully mocked agent call, no network required.

What I didn't do

The lost run is unrecoverable without paying to re-run it, and I decided that wasn't worth it. The dashboard shows that run with an explicit "not captured" panel rather than backfilling it or quietly hiding the gap.

Known limits

Why there's no live demo link. The replay dashboard is built — vanilla JS, run data inlined to work without a server — but publishing is blocked on a hosting account issue, and the repo is private pending a decision about making it public. I'd rather say that plainly than link you to a 404. Happy to walk through it directly.

What I'd do next