Skip to main content
Care DeskConcept

Viewing as

About the builder

Jess Morgan

Patient support

How it works

What runs where, what the AI can and cannot see, where a person decides, and the commands for day-to-day running.

Fictional demo dataUnofficial concept

In short

Care Desk drafts replies to routine patient messages from the patient's own records and a written support policy, and a person sends every one. Before any AI is involved, personal details are swapped for placeholders and plain safety rules read the original message. Anything clinical, a crisis or a report of a death stops there: no AI reply, the patient's orders go on hold and a clinician takes it. Routine messages are sorted by a fast Claude model and answered by a stronger one using only numbered sources, and every date, amount and order number in a draft is checked against those sources before an agent sees it.

  • Safety rules run before any AI
  • A person sends every reply
  • Every fact traced to a source

How a message moves

Seven steps, always in this order. The same pipeline runs ahead of time on the demo messages and live in the Try it box.

Patient messageEmail or chatDetails removedCode, no AISafety rulesCode, no AISorted by typeClaude, fast modelSources foundRecords and policyReply draftedClaude, strong modelFacts checkedCode, no AIA person sendsAgent reviews firstClinicianqueueNo AI replyOrderson holdStopRiskAn agentwrites thereplyNo draft isofferedUnsureDeclinesFails
Steps marked Code, no AI are plain, testable rules. Either the rules or the sorter can escalate, but a draft needs both to agree the message is routine. An agent can still escalate any draft by hand.

Where data lives

A static site and one Worker. There is no database of patient data in this demo, and nothing typed into the live box is stored.

Visitor's browserShows the pages. Keeps decisionsin this browser only.Static filesPages and demoresults. Allfictional.Live boxRemoves details,runs the rules,then calls Claude.Holds the key.One Cloudflare Workerpages anddemo datamessage in,trail backClaude APISees placeholdersand numberedsources only.Settings storeLimits, on/off,counts. Never amessage.placeholdersonlycounts
Every patient, order and message in the demo is fictional. A visitor's sends, edits and escalations are kept in their own browser, so each visitor starts with the same queue and can reset it from Help.

What the AI never sees

Names, email addresses, phone numbers, addresses, dates of birth and health numbers are replaced in code before the first AI step. Here are three of the demo's fictional messages, before and after.

Example message from

What the agent sees

The message as it arrived

What the AI sees

Placeholders, before any AI step

Loading the example message
Removed before the AI saw it
Kept, because the reply needs them

The same code runs on every message, in the desk and in the live box. It over-removes on purpose: hiding a doctor's name costs nothing, leaking a patient's would.

Open it on the desk
  • The patient's records reach the drafter as numbered sources (plan, orders, charges and appointments) with no patient name, contact details or date of birth in them.
  • Drafts open with [FIRST_NAME] and sign off with [AGENT_NAME]. The desk fills both in on screen, so the AI never sees or invents a name.

Where a person decides

Care Desk only acts on its own to protect a patient: it holds orders and withdraws drafts. It never sends anything.

  • Every reply that is sent

    A draft waits for an agent. Send, Edit and Escalate are the only ways forward, and Undo is there straight after sending.

    The desk: the Send button, or Ctrl+Enter

    Open the desk
  • Every clinical item

    Clinical questions, side effects, serious reactions, crisis language and bereavements never get an AI reply. A clinician reads why it was escalated, the words that triggered it and the patient's context, then replies personally.

    The clinician queue

    Open the clinician queue
  • Every hold release

    A safety stop puts the patient's orders on hold straight away. Only a clinician can resume them, and resuming needs a written note, kept with the message.

    The clinician queue: orders on hold

    See a hold

When something fails

Every failure lands on a person, never on the patient. The examples open demo messages in that state.

What happens when each part of Care Desk fails, and who acts next
WhenWhat Care Desk doesWho acts next
The AI is unavailable

Details are still removed and the safety rules still run, because both are plain code. Nothing is drafted: every message goes to a person. The live box says it is resting and shows the saved examples.

Next:Agents reply as they do today
The sorter is not sure

Below 75% confidence there is no draft. The message shows as Write the reply, with the sorter's best guess shown.

Next:An agent writes the reply
The fact check fails

Any date, amount, order or tracking number that is not in the sources blocks the draft, and so does any dosing figure. Send is never offered, and the trail names the fact that failed.

Next:An agent writes the reply
A safety rule fires

The trail stops at the safety rules. No AI reply, the patient's orders go on hold, and the message joins the clinician queue, urgent first.

Next:A clinician
The drafter declines

When the sources do not hold the answer, the drafter says so instead of guessing. No draft; the reason is shown on the trail.

Next:An agent writes the reply
A death is reported later

Every unsent draft to that patient is withdrawn at once and their orders are held. Nothing is sent to them unless the clinician finds the report was about someone else.

Next:A clinician, then the family

Agent guide

docs/AGENT-GUIDE.md

One page for anyone working the Care Desk queue. Plain words, no training needed.

The one habit

Read, then send. Care Desk drafts the routine replies. You read each draft, check it against the patient's records beside it, and you decide. Nothing goes to a patient until you press Send.

Your queue

Messages arrive from Australia, New Zealand and the UK, by email and chat. The most serious come first, and within each group the longest waiting come first. Every row shows the type, the risk (an icon and a word, never colour alone), how long it has waited and the country.

The filters above the queue group the rows: Write the reply, Ready to send and Clinician and urgent. All shows everything still open, and Done shows what you have sent or escalated. Rows you cannot act on yet sit at the bottom of the queue, under Waiting on a clinician for this patient.

What the row saysFilterWhat it meansWhat you do
Ready to sendFilterReady to sendWhat it meansA drafted reply passed every checkWhat you doRead it, then send, edit or escalate
Write the replyFilterWrite the replyWhat it meansNo draft: the patient asked for a person, it is a complaint or privacy request, the AI was unsure, or a check failedWhat you doWrite the reply yourself
Held: clinician firstFilterWrite the replyWhat it meansA draft exists, but this patient reported something serious in another messageWhat you doAsk the on-call clinician before anything is sent
WithdrawnFilterAllWhat it meansSomeone has told us this patient may have diedWhat you doSend nothing. The clinician handling that message decides what happens next
Clinician or UrgentFilterClinician and urgentWhat it meansA safety rule stopped the message: no AI reply, and a clinician answersWhat you doNothing to write. Urgent ones also have the patient's orders on hold

Working a message

  1. Open it. The message is in the middle, with the patient's orders, charges and appointments. The check trail is on the right.
  2. Read the draft. Every fact in it has a number, like [1], pointing to the order, charge or policy it came from. The fact check has already matched every date, amount and order number to those sources.
  3. Decide.

    • Send when it is right. You can undo straight after.
    • Edit when it needs your touch. Change anything you like; the send button works the same way.
    • Escalate when anything feels clinical, even if the rules did not catch it. It goes to the clinician queue with your note.
    • Pass to a person when a colleague owns the conversation.

The draft greets the patient by first name and signs off with your name. The AI never saw either: the desk fills them in for you.

Keyboard

KeyAction
J and KActionNext and previous message
EActionEdit the draft
Ctrl + Enter (Cmd + Enter on a Mac)ActionSend
XActionEscalate to a clinician
?ActionShow every shortcut

Always

  • A patient asks for a person: they get a person. These never get an AI draft.
  • Anything about symptoms, side effects, doses, other medicines, pregnancy or driving: escalate. Do not answer it, even in part.
  • Crisis words or a risk to someone's life: the message is already urgent and with a clinician. If you are ever unsure, escalate and tell the on-call clinician straight away.
  • A death reported by family or a carer: everything to that patient is withdrawn and their orders are on hold. A clinician replies to the family.

Never

  • Send a draft you have not read.
  • Promise delivery dates, refunds or prices that are not in the patient's records or the policy.
  • Give medical advice, dosing figures or product recommendations.
  • Paste a patient's details into any other tool.

When something looks wrong

Open See what the AI saw on the trail to see exactly what it was given. If a draft is wrong, edit it or write your own, then tell your team lead which message it was, so the rule or prompt can be fixed and the case added to the test set.

Runbook

docs/RUNBOOK.md

How to run Care Desk day to day. Every command runs from the project folder, with Wrangler signed in to the Cloudflare account that owns the site (npx wrangler login once). --remote means the live settings, not a local copy.

What is running

  • The site is static files (pages and the precomputed demo data), served by one Cloudflare Worker named care-desk.
  • The live box ("Try it") is the same Worker's /api/try route. It runs the pipeline on the typed message, calls the Claude API with the key stored as a Worker secret, and streams the trail back.
  • Settings live in one Workers KV namespace, bound as CARE_DESK_KV. It holds one config value (the limits and the on/off switch) and running counts (tries per visitor per hour, requests and spend per day). It never holds a typed message.
  • Secrets are set with npx wrangler secret put NAME and can never be read back: ANTHROPIC_API_KEY (the Claude key), VIP_TOKENS (the private-link codes), ADMIN_TOKEN (for the status check below) and, if used, TURNSTILE_SECRET and VISITOR_SALT.
  • There is no database of patient data. Every patient is fictional, and each visitor's decisions stay in their own browser.

Raise or lower the live-box limits

One command. It takes effect within about a minute, with no redeploy.

npx wrangler kv key put --binding=CARE_DESK_KV --remote config '{"enabled":true,"dailyUsd":5,"perVisitorPerHour":20,"vipDailyUsd":10,"vipPerVisitorPerHour":60}'

The value replaces the whole config, so always write every field. A field that is missing or unreadable falls back to its default, and nothing can go above US$200 a day or 1,000 tries an hour, so a typo cannot switch the limits off.

FieldWhat it limitsDefault
dailyUsdWhat it limitsPublic spend per day, in US dollarsDefault3
perVisitorPerHourWhat it limitsPublic tries per visitor per hourDefault10
vipDailyUsdWhat it limitsPrivate-link spend per day, in US dollarsDefault10
vipPerVisitorPerHourWhat it limitsPrivate-link tries per visitor per hourDefault60
enabledWhat it limitsThe on/off switch (see "Pause the live box")Defaulttrue

Days are UTC. To see the current values:

npx wrangler kv key get --binding=CARE_DESK_KV --remote config

No output means the key is not set and the defaults apply.

A VIP link is a private link for someone who needs more tries, such as a team lead trialling the live box. It has its own budget (vipDailyUsd and vipPerVisitorPerHour), separate from the public one, so public traffic can never use up a private link's allowance. It also skips the bot check.

  1. Make a code that cannot be guessed (24 letters, digits, - or _; codes shorter than 12 characters are ignored):

    node -e "console.log(require('crypto').randomBytes(18).toString('base64url'))"
  1. Store every live code in the VIP_TOKENS secret, separated by commas. The secret cannot be read back and each put replaces the whole list, so keep the list somewhere safe, such as a password manager. Paste it when asked:

    npx wrangler secret put VIP_TOKENS
  1. Send the link: https://caredesk.autopilotyourworkflow.com/try/?k=CODE

The page takes the code out of the address bar and keeps it for that browser tab only.

To revoke a code, run npx wrangler secret put VIP_TOKENS again with the list minus that code. It stops working as soon as the secret is saved. To see how many codes are live, use the status check under "Check spend".

Rotate the API key

  1. In the Claude Console, create a new key named care-desk.
  2. Store it in the Worker. Paste it when asked; the Worker picks it up at once.

    npx wrangler secret put ANTHROPIC_API_KEY
  1. Send one message through "Try it" and check the trail reaches "Facts checked".
  2. Revoke the old key in the Console.
  3. Put the new key in .env.local too, so npm run precompute keeps working on this machine. Never commit it.

Check spend

  • Today, as the Worker counts it (US dollars, the day in UTC). Spend is kept per tier, so there are two keys; no output means nothing was spent:

    npx wrangler kv key get --binding=CARE_DESK_KV --remote spend:public:2026-09-24npx wrangler kv key get --binding=CARE_DESK_KV --remote spend:vip:2026-09-24
  • Everything in one view: the status check returns today's spend, requests, limits and remaining budget for both tiers, whether the live box is on, and how many VIP codes are live. Set ADMIN_TOKEN once with npx wrangler secret put ADMIN_TOKEN, then:

    curl -H "Authorization: Bearer ADMIN_TOKEN_VALUE" https://caredesk.autopilotyourworkflow.com/api/status
  • The source of truth is the Usage page in the Claude Console, filtered to the care-desk key. Set a monthly spend limit there as a backstop to the Worker's cap.
  • Live requests and errors as they happen: npx wrangler tail care-desk

Pause the live box

Write the config with "enabled": false, keeping the other fields as they are:

npx wrangler kv key put --binding=CARE_DESK_KV --remote config '{"enabled":false,"dailyUsd":5,"perVisitorPerHour":20,"vipDailyUsd":10,"vipPerVisitorPerHour":60}'

Within about a minute every live try, public and VIP, is turned away before any AI call. "Try it" shows the same notice as when the day's live AI budget is used up, and still runs the safety rules in the visitor's browser, with the labelled offline stand-in for the AI. The rest of the site is static and carries on as normal. To resume, put the same value back with "enabled": true.

In an emergency, removing the key stops every AI call at once: npx wrangler secret delete ANTHROPIC_API_KEY. The live box then answers with the offline stand-in until a key is put back.

Re-run the evaluation

Run this after any change to the safety rules, the prompts or the test set.

npm run precompute   # runs every demo message through the pipeline with Claudenpm run eval         # scores the results against the labelled test setnpm run deploy       # rebuilds the site data and publishes it

precompute keeps saved results that already match the current rules, prompts and models, so a second run only pays for what changed. To redo one message: npm run precompute -- --only MSG-0042 (comma-separate several).

What the release gate means

npm run eval is the release gate. It fails (exit code 1) when:

  • any safety case in the test set was not sent to a clinician, or an urgent case lost its priority;
  • any case that must hold the patient's orders did not;
  • a draft could be sent to a patient whose death was reported, or to a patient with an open urgent message;
  • a labelled message has no result, or the results came from the offline stand-in instead of Claude.

Do not deploy on a failed gate. Fix the rule or the prompt, add the message that exposed the problem to data/testset.json as a new case, and run the three commands again. The Test results page shows exactly what the last passing run measured.

Stack

Next.js 16, React 19 and TypeScript, styled with Tailwind CSS, served as a static site by one Cloudflare Worker, with the Claude API (Claude Haiku 4.5 to sort, Claude Opus 5 to draft) and Vitest for the tests.

Keyboard shortcuts

Shortcuts pause while you type.

One-key shortcuts

Turn these off if they get in the way of speech input or another tool.

One-key shortcuts

Anywhere

Show keyboard shortcuts
?
Close menus and panels
Esc