PawDesk handles pet care bookings and asks a person when it isn't sure.

PawDesk is a hybrid workflow for a kennel's booking inbox: plain code runs the steps in a fixed order and calls AI agents only where the input is messy. Agents read each request and the photo of the vaccine card, code checks the vaccines and finds a free kennel, and another agent writes the reply.

When every check passes, PawDesk books the stay and sends the reply. When something is unclear or breaks a rule, the request waits in a review queue for someone on staff.

I built it to show full stack work and AI-driven development in one project, with these skills:

  • Python and FastAPI: the API and the workflow that runs each request
  • React and TypeScript: this dashboard, which shows each run live
  • PostgreSQL and Docker: migrations, and the full stack in Docker Compose
  • Testing and CI: pytest, Vitest, and Playwright, run by GitHub Actions
  • AI-assisted development: each change specified, reviewed, and tested before it ships

Example request. Avery Lindqvist wrote, with a photo attached:

Hi! Could Biscuit board with you from March 24 to March 27? He eats grain-free food twice a day. His vaccine card is attached.
The attached card, read by the records agent
Biscuit's vaccine card, listing Rabies, DHPP, Bordetella, and Canine influenza with the dates given and expiry dates
Pet
Biscuit, a Beagle on file
Stay
Boarding, March 24 to 27
Vaccines
Rabies, DHPP, and Bordetella cover all three nights
Kennel
A medium one is free all three nights
SentEvery check passed, so no one on staff was involved.

PawDesk booked that kennel and sent this reply:

Biscuit's boarding stay is confirmed — March 24–27, 2027

Hi Avery,

Great news — Biscuit's boarding stay at Maple Hollow Pet Lodge is confirmed for March 24–27, 2027.

A model read it Code checked it

Try a request

Pick a sample or make one up, then watch each step run with real model calls. The desk treats today as March 3, 2027, so the vaccine cards' dates line up with the stays.

Loading samples

Booking rules

Code checks each rule below; no model decides any of them. They're the demo facility's rules, and each facility sets its own vaccine lists per service and species.

Services

BoardingVisitOvernight, at least one nightKennelA free kennel of the pet's size, reserved when the reply goes outDogs needRabies, DHPP, BordetellaCats needRabies, FVRCP
DaycareVisitOne day, drop-off and pickup on the same dateKennelNoneDogs needRabies, DHPP, Bordetella, Canine influenzaCats needRabies, FVRCP
GroomingVisitOne appointment on a single dateKennelNoneDogs needRabiesCats needRabies

When a vaccine counts

The pet is on site every day from drop-off through pickup. A shot counts if the pet got it on or before drop-off and it expires on or after pickup day. Code looks at the records on file and the ones the records agent read from the card, and one covering record is enough.

Mar 23Mar 24Mar 25Mar 26Mar 27Mar 28
Dog on site
Bordetella, expires April 2Covers the stay
DHPP, expires March 26Expires during the stay
A hypothetical dog: drop-off March 24, pickup March 27. The DHPP shot runs out before pickup, so it fails even though it was valid at drop-off.

When no record covers the stay, code names the reason: no record, expired before drop-off, expires during the stay, or given after drop-off. A card with no expiry date for a required shot goes to a person.

Kennel sizes

Code sets a pet's size from its species and weight and searches only kennels of that size. Cats get cat kennels at any weight. Dogs:

Small
Under 20 lb
Medium
20 to under 50 lb
Large
50 to under 90 lb
Extra large
90 lb and up

A kennel is free when none of the stay's nights is booked. The nights run from drop-off to the night before pickup, so a stay that ends March 27 doesn't block one that starts March 27.

Dates

A request can start today or up to 365 days ahead. Boarding has to end at least one day after it starts. Daycare and grooming start and end on the same date.

When a rule isn't met

A failed rule, or anything the intake or records agent flags, takes the request off the automatic path. If the resolver can work on the problem, it suggests other dates, a larger kennel, or an updated vaccine record. Either way, someone on staff decides before a reply goes out.

Model Use

Four parts of PawDesk use a model. Each one reads something, returns a fixed set of fields, and hands them to code that checks them before they count. None of them can approve a booking.

IntakeOne model callReadsThe pet parent's message. Code scans it for instruction-like text, such as “ignore your rules”, before the model sees it.ReturnsThe pet's name, the service, the dates, any special notes, a confidence score, and anything it found unclear.What code checksMatches the pet to one on file and checks the dates. Instruction-like text, a confidence under 0.7, or anything unclear sends the request to a person.
RecordsOne model callReadsThe photo of the vaccine card.ReturnsEach vaccine with the date given and the expiry date, the pet's name as printed, whether the photo was readable, and a confidence score.What code checksRejects impossible dates and unknown vaccines, and compares the printed name with the booked pet. An unreadable card or a confidence under 0.7 sends the request to a person.
ResolverA loop of up to 6 model callsReadsOnly values code built, such as which vaccine expired. It never sees the pet parent's message, and code starts it only for problems it can work on.ReturnsOne offer: other dates, a larger kennel, or a request for an updated vaccine record. It finds them with 3 read-only tools, at most 12 calls.What code checksRuns the checks again on every option. The resolver can't book anything, and a person reviews its offer.
ReplyOne model callReadsThe checked facts and the options code verified. Like the resolver, it never sees the original message.ReturnsA subject line and the body of the reply.What code checksFlags any date the checks didn't confirm, any money amount, and words like “booked” or “confirmed” the facts don't support. A flagged draft goes to a person.

How a request moves

Code sets the order of the steps and makes each decision. The models read messy input and write text for people.

  1. 1

    Readmodel

    The intake and records agents read the message and the vaccine card at the same time.

  2. 2

    Checkcode

    Code compares each required vaccine's expiry date with the stay, then looks for a free kennel that fits the pet's size.

  3. 3

    Find optionsmodeltools

    If a check fails, the resolver suggests a fix and code checks it again. If every check passes, this step is skipped.

  4. 4

    Write the replymodel

    The reply agent drafts the answer to the pet parent from the facts code checked.

  5. 5

    Send or holdcodestaff

    Code scans the draft for promises the desk can't keep. A clean reply goes out and reserves the kennel in one database transaction. A flagged one waits in the review queue until someone on staff approves, edits, or declines it.

What the tests hold it to

On 16 labeled messages, three of them prompt-injection attempts, intake got all 16 right and sent all three attempts to a person. The evals page has that report and the others.

Working with an AI assistant

I build PawDesk one change at a time, and each change goes through the same four steps. Choices that outlast one change get a numbered decision record; there are 19 so far.

  1. An AI assistant drafts the change as an OpenSpec proposal: requirements with when/then scenarios, a design that weighs the options, and a task list.
  2. I read the proposal, push back where I disagree, and approve it before any code exists.
  3. The code goes in as small commits. Lint, type checks, and the tests pass before each one.
  4. Each test names the requirement it proves, and the build fails when a requirement has no passing test. In the latest test run, 177 of 177 requirements pass, across 1061 tests.

How it was built lists each of the 49 changes with its requirements, the tests behind them, and their latest results.

Skills across the stack

PawDesk runs from the database to the browser, built with AI tools every day and checked by tests at every step. Each row pairs a skill with the parts of PawDesk that use it.

SkillWhere PawDesk uses it
Python, FastAPIThe API, the workflow, and the checks. Pydantic validates each model response before the code uses it.
TypeScript, ReactThis dashboard, in Next.js 16 and React 19 with Tailwind CSS. React Flow draws the run graph and Recharts draws the metrics.
REST APIs and integrationsFastAPI endpoints with live run updates over server-sent events. Model calls go to DeepSeek V4.1 Flash through OpenRouter. The browser reaches the API only through a proxy with an allowlist.
PostgreSQL and data modelingPostgres 18 with SQLAlchemy and Alembic migrations. The schema models owners, pets, vaccine records, kennels, reservations, and the steps of each run.
Docker and CI/CDDocker Compose runs the full stack. A GitHub Actions workflow runs lint, type checks, the tests, the build, and a gitleaks secret scan.
Automated testingpytest against a real Postgres, Vitest for the dashboard's logic, and Playwright end-to-end runs that replay recorded model responses so they cost nothing.
Secure codingPet parents' messages and photos count as untrusted input. Code flags prompt injections, and the API key stays on the server.
AccessibilityYou can reach each step of a run by keyboard, and screen readers announce its summary. Layouts hold at phone width.

The pet parents, pets, and vaccine cards on this site are made up.