Agents, wizards and eight Raspberry Pis at DocJS and their Model Behavior
Shawn Storie
Drug makers' enrollment forms used to be printed, filled in by hand, and faxed back and forth. DocJS turns each form into a guided online journey: each person gets a link by text or email, fills in and e-signs their part, and the finished PDF goes where it needs to go.
Turning PDF forms into phone-friendly wizards, and turning "picture-only" forms into fillable ones.
AI agents write most of the code changes, review them, and run the tests. People decide what gets built and what ships.
The "brain". Ours is Claude: Opus is the biggest and most careful, Sonnet is faster and cheaper.
The written instructions we give a model.
A model that can take actions (read files, run programs, edit code) and keep going until the job is done.
Anthropic's agent for software work. Most agents in this talk are Claude Code.
Ordinary software: same input, same answer, every time. It can't be talked into anything.
A fillable form has real boxes you can type in. A flat one is just a picture of a form.
A proposed change to the code. It gets reviewed and tested before it's merged in.
A credit-card-sized computer that costs about $75. We have eight.
When the AI got something wrong, we added code that catches it every time, instead of just asking the AI more nicely.
Field names, positions, page numbers and legal wording come straight from the original document. The AI never retypes them.
Code only merges when an AI reviewer says yes and the automated tests pass.
Turning a PDF form into a step-by-step form that works on a phone.
469 picture-only forms made fillable in one night.
AI agents writing, reviewing and testing the code, and the house rules that help them.
Eight tiny computers running AI agents around the clock.
Running AI models on our own hardware, for free.
What we learned about instructing AI, often the hard way.
In shortSomeone uploads a form. Minutes later there's a step-by-step version that works on a phone, and a robot has already tested it.
Runs through Amazon Bedrock. The cost of every AI call is recorded.
Jobs wait their turn in the database. If a worker dies mid-job, another one picks it up.
For ~369 form templates. "Refine" lets an admin ask for changes in plain English.
Demo form with fictitious company and patient.
In shortBefore building anything, AI labels every box on the form. Then a second pass checks the first one's homework.
Split into chunks of 100 fields, 5 at a time. A 705-field form: 10 minutes, $5.77.
Upload the same PDF again and the earlier answer is reused for free.
AI only fills in blanks. It never overwrites a setting a person made, and never assigns roles on its own.
In shortEvery time the AI got something wrong, we added a specific rule to its instructions, with the reason.
In shortFour small pieces of regular code check every wizard. Each one exists because the AI once got that exact thing wrong.
Clears any pre-selected choice. The AI kept defaulting Sex to "Female", which would have been submitted silently.
Unlocks any field the AI locked, and rejects a wizard that has no real form fields at all.
Every section closed, every step present, a working Next button. Checked with simple pattern matching on purpose, because a forgiving web browser hides exactly these mistakes.
If the AI ran out of room mid-answer, that's a failure, never a page. A failed fix can never replace a good version.
| What broke | What happened | Fix |
|---|---|---|
| Answers in the wrong box | Two fields both labeled "Last Name" got merged, so pre-filled data landed in the wrong place | Use the PDF's own field names, exactly |
| 35 of 38 doctor fields locked | The AI locked them even with the right instructions. We first blamed the wrong instructions | A code check, plus a record of exactly which instructions ran |
| Page cut off midway | The AI hit its length limit; the half page was saved and the Next button was dead | "Ran out of room" now counts as a failure |
| Doctors couldn't sign | The format rule on the signature box was too short for a signature image | No format rules on signature fields |
| Anyone could edit the AI's instructions | Saving new instructions didn't require a key | A key for every change |
| Legal text shortened | A doctor's certification statement shrank to a stray "6." | Legal text copied word for word |
In shortMany drug-maker forms look like forms but have nowhere to type. People had to print, write and fax them.
First, regular code finds the blanks:
First try: 0 of 25 approved. A 42-form pilot: 4 approved (10%). Tuning the rules for one kind of form kept breaking another.
In shortEach form gets a first draft from code, corrections from one AI, and an independent check from another AI that looks at pictures of the result, like a person would.


Full_NameIn shortWe queued all 469 forms in the evening. By 9 the next morning every one had been processed, with nobody watching.
225 were brochures, drug-information sheets or FDA documents. The "is this even a form?" step caught them.
A supervisor program restarted stuck Pis, emailed progress, and paused the work before we hit our AI usage limits.


Every converted form is saved as a new version next to the original, with the checker's notes attached. Nothing goes live until a person signs off.
In shortEpic Beast is zPaper's standard tech stack: nine choices, one per letter. One right answer per choice means the AI guesses wrong less often.
In shortAn agent works best when it can read a whole file at once. Big files get read in pieces, and agents confidently edit the part they saw.
Why 1,000 exactly? It's a round number, not magic. The ratchet is what matters.
Files already over the limit were grandfathered, but they can only shrink. Checked when code is committed, when it's pushed, and again in the test gate, so there's no way around it.
The splitting itself is agent work: the refactoring skill, 4 files at a time.
In shortA ClickUp task becomes an agent's job. It writes the code and tests, other AIs review it, automated tests decide, and it merges only when both agree.
Plan, post the plan on the ticket, write tests, label every change with the ticket number, hand it in for review when done.
Every change is built and tested in the cloud, for about 3 cents a run. It fails only on problems this change caused.
The release pipeline (Manifest) pushes to dev, then QA, with health checks along the way.
In shortClaude Code reads a file called CLAUDE.md before every task. Ours is ~320 lines, and most lines exist because something once broke.
A written rule is a suggestion; a failing test is a fact. File-size limits, no patient data in logs, pinned versions: all enforced by tests now.
Gemini reviewed our code: of 53 issues, 12 were wrong and 23 overstated. Its 3 "critical" issues turned out to be 0.
In shortEight $75 computers in a rack, each running its own AI agent, day and night.
Cheap, always on, and contained: a job that goes wrong only affects one Pi, and the Pis hold no keys to our main computers. Building all of DocJS on one takes about 2 minutes.
In shortJobs go into a queue. A Pi checks one out at a time; if it doesn't check back in, the job goes back on the shelf.




The Pis have no monitors, but each gets a tile on a shared wall of screens. Its instructions say "You have a display anyway. Use it." The agent decides what to show: progress, a table, a screenshot, or a question for a person. One command, disp, puts any web page on its tile.
disp --ask flags the Pi on the control panel so a person noticesIn shortBesides Claude in the cloud, we run free, open AI models on one powerful desktop. No per-use cost, and the data never leaves the building.
One AMD "Strix Halo" desktop with 128 GB of memory holds models with 35 to 120 billion parameters:
| Model | Size | Speed* | Used for |
|---|---|---|---|
| gpt-oss-120b | 63 GB | 55 | reading web pages |
| Ornith 35B | 36 GB | 54 | code review |
| Qwen Flash-Next | 98 GB | 28 | agent work |
| Gemma 4 26B | 16 GB | 53 | quick second opinions |
* tokens per second, roughly words per second
In shortClaude Code can be pointed at a model on our desktop instead of Anthropic's cloud. It's the same agent with a different brain.
Our model server speaks the same language as Anthropic's, so it's a few settings:
ANTHROPIC_BASE_URL=http://127.0.0.1:8095 \ ANTHROPIC_MODEL=ornith \ claude -p "fix the failing tests"
Claude Code's built-in instructions are long (~15 s for a local model to read). Fine for real agent work; wasteful for 1,000 quick one-file reviews. That's the next slide.
In shortThe plan: a free local model reviews every file overnight, anything it can't back up with a quote gets thrown out, and Claude double-checks only the top 30.
Reviewing one file needs no tools, so we skip the full agent and its long instructions.
The organizer can only point to findings by number. It can't rewrite or quietly drop them.
Run it on code Gemini already reviewed and compare the results before trusting it on everything.
In shortA local model read healthcare facilities' websites and pulled out fax numbers, phone numbers, doctors and form links into a database.
A queue of websites. A worker fetches each page, and the model fills in the same fixed set of answers for every one. Same pattern as the Pi fleet.
Exact structure, exact output format, a list of edits instead of a full rewrite. A blueprint can be checked by code.
"Don't wire the Next button twice; it skips a step." The reason stops it making the same mistake a new way.
What's missing, where each line of text sits. Don't ask an AI to measure what code can measure.
"Fix ONLY the listed issues", with screenshots. Keep the fix only if things got better.
"You are the only gate." "When you cannot tell, do not approve." Say what a mistake would cost.
Placeholders for legal text, short codes instead of long answers. Less to write means fewer mistakes and lower cost.
A short file the agent reads first. Link to longer docs; don't paste them in: the agent opens a link when it needs it. Turn rules into tests when you can.
Plan, share the plan, test, label the work, hand it in for review. The same steps for every agent.
Command-line programs with a short how-to. The agent already knows how to use those.
Sort reviewer comments into must-fix and nice-to-have, and send back only the must-fixes.
Give "thinking" models room to think, and treat a cut-off answer as a failure, not an answer.
Version them, record which version produced each result, and require a key to change them.
In shortAgents need tools to reach the outside world. We give them ordinary command-line programs, and package know-how as "skills" they load only when needed.
The Model Context Protocol: a standard way to plug services into AI apps, each through its own small server that speaks the protocol.
gh · clickup · fp ship · disp · artifact put · pr-verify
A folder with a SKILL.md: a recipe card. The agent always sees a one-line description; the full instructions load only when a task matches. Our always-on rules stay short, and know-how is there when it's needed.
"Use when user mentions refactoring large files, splitting files, reducing file size…"
/refactor-large, which runs 4 splits in parallel, and with the 1,000-line ruleLet it ask what's unclear before it starts. Our triage step does this: if a ticket isn't ready, Opus posts its questions on the ticket instead of guessing.
Then read the plan. A wrong plan takes a minute to fix; wrong code takes an hour. Every DocJS agent posts its plan on the ticket first.
"Write in plain paragraphs" beats "don't use bullet points". Describe the target, not the thing to avoid.
It wasn't planning to make any. Wishing doesn't change the output; a check does. Say how the work will be tested, and test it.
Grand role-play intros add little. Spend the words on who it's for, what "done" looks like, and why.
Naming it puts it front and center. A rule of thumb, not a law: newer models follow "don't" far better, and a hard "never" is fine when you add what to do instead.
In shortGood results, and fewer made-up facts. But when it does hold a wrong fact, it doesn't doubt it: it's more certain, and does less research before answering.
The assistant confidently said every route lived in one file (they don't) and credited a team with a tool we'd built. A quick check caught both.
In shortThe scariest bugs didn't crash anything. Everything looked fine, or just quietly waited.
| What we saw | What was really happening |
|---|---|
| Every change "waiting for a person" | The reviewing AI hit a monthly spending cap and quietly stopped answering |
| Tickets never closed | A background service started without the ClickUp password |
| Work credited to the wrong person | The Pis weren't set up with a work identity, but uploads still worked |
| Tests passed that shouldn't have | Our fast runtime silently skipped part of the test setup |
| An old checker said PASS | An out-of-date copy of the test gate ran; now it always uses the latest |
| AI quota burned overnight | A "you're getting close" warning was read as "denied", so finished work was redone for 7 hours |
What this could look like for HealthScores.
In shortWe read the HealthScores repo (develop, Nov 4) the way an agent would. Some things already work well; a few make agents guess.
server/index.js alone is 3,791 lines.claude/ skills yetAdd a 1,000-line ratchet: today's 15 files may only shrink. Then let agents split the biggest ones first, starting with server/index.js (3,791 lines).
Require CI to pass before merging (one setting). Add a Husky pre-commit hook for lint. Add tsc --noEmit with today's errors as the baseline, allowing only fewer.
Before an agent edits a big component, have it write tests that capture what it does today. The app side is where tests are thin.
A handful of sample patients with their expected scores, checked in CI. Any change that moves a score shows up in review, on purpose or not.
Keep CLAUDE.md to rules with reasons, and link to everything else. The release-flow section already does this with docs/RELEASE_FLOW.md; do the same for the architecture tour.
FHIR quirks (R4 vs STU3, blood-pressure panels, unit conversions), Salesforce person accounts, and cutting a release: each a recipe card.
Start hereRequiring CI is one checkbox. Steps 1 and 2 are small, one-time jobs, and every agent session after them gets better for free.
In shortA small release service for HealthScores, like Manifest for DocJS. A version gets discharged to prod, and its notes are the discharge summary, written by Claude from the full PR descriptions.
| Today | With Discharge | |
|---|---|---|
| Who writes | GitHub's list of PR titles | Claude, from full PR descriptions |
| Team notes | One flat list, bots mixed in | Grouped, plus Test focus, Deploy & config, Heads-up |
| User notes | None | Plain language, nothing internal |
| Who hears | Nobody is notified | Posted to #healthscores-qa |
The lessonNothing was made up. Every fact was already in a PR description; Claude just read all of them.
Instructions are the second line of defense, not the first.
Names, positions and legal text come from the source.
Show it pictures of the result and tell it exactly what to look for.
The checker's complaints become the fixer's to-do list.
Which instructions, which model, and did it finish.
Small files, clear rules, simple tools.
A cheap first pass overnight; Claude checks what survives.
Track every job, so a crash costs minutes, not the night.
epicbeast.net · docs/epic-beast/conventions.md · docs/claude/ · tools/fillable/