{"slug": "the-guardrails-that-matter-are-code-not-prompts", "title": "The guardrails that matter are code, not prompts", "summary": "A developer built an AI-powered eldercare coordination system for their parents in India, using five agents on Google's ADK and Gemini 3.5 Flash, but the key innovation is a deterministic enforcement layer that blocks agents from taking unauthorized actions, such as sending clinical data over WhatsApp. The system includes an Ed25519-signed receipt chain that allows public verification of record integrity without revealing content, demonstrating that guardrails must be enforced in code, not just prompts.", "body_md": "I built an agent that coordinates eldercare for my mother in India, and the interesting part was not the agents. It was the four times I stopped and decided what the system was **not allowed** to do, and then put those decisions somewhere no prompt can reach.\n\n*I created this post for the purposes of entering the All Things Agentic Hackathon (Google Cloud / Devpost).*\n\nIt's live, and you can check its work without asking me: [https://anbu-care-37j4eofpwq-el.a.run.app](https://anbu-care-37j4eofpwq-el.a.run.app)\n\nMy parents live in Thoothukudi. I don't. Every NRI I know has the same 3 a.m. fear: something happens, and by the time you hear about it, the decisions that mattered have already been made by whoever was standing there.\n\nOne rule fell out of that and shaped everything: **any step that needs the son to act is a design failure.** He is asleep, nine and a half time zones away. That's the problem, not a detail of it. So every feature got tested with him unreachable.\n\nFive agents on Gemini 3.5 Flash and Google's ADK: onboarding, triage, evidence, insurer liaison, comms, all under a coordinator. Underneath them is a deterministic layer no agent can reach past. **The model proposes; that layer decides, and it is the only thing that can write.**\n\nThe demo input for triage is a neighbour calling to say *\"she says it's probably just gas.\"*\n\nSeverity comes back HIGH.\n\nThe thing that decides severity is a Python dict. It never reads that sentence as permission, because it never reads that sentence.\n\nThe second one is the one I'd defend hardest. Clinical detail can't go over WhatsApp. India's DPDP Act and Meta's healthcare policy make that a legal line. So the gate classifies the **content**, not the caller's claim about it. A message declared `logistics`\n\nthat reads *\"just logistics: troponin 0.94 ng/mL\"* is blocked anyway.\n\nThen the demo does something that took me embarrassingly long to think of: it bypasses the agent entirely and calls the send function directly.\n\nStill blocked.\n\nThat second half is the whole claim. **An agent that is merely told not to leak a lab value is not a control.** If your only enforcement is in the system prompt, you have a strong suggestion, and you will find out which it was on the day it matters.\n\nThis is the design decision I'm most pleased with, and it came from a question I couldn't answer cleanly at first.\n\nIf clinical data can't go over WhatsApp because it \"lives somewhere protected\", what exactly is protecting it?\n\nIf the answer had been \"a URL nobody guesses\", the whole argument would have been hollow, and I'd have published the exact data I claimed to guard.\n\nSo the API has two access models, both enforced server-side:\n\n```\ncurl -s -o /dev/null -w '%{http_code}\\n' $URL/api/parents/{id}      # 401\ncurl -s -o /dev/null -w '%{http_code}\\n' $URL/api/cases/{id}/verify # 200\n```\n\nEvery action appends an Ed25519-signed receipt whose hash covers the previous one. **Verification proves the record wasn't altered without revealing what it says.** It returns hashes, a boolean and a failure mode. That's exactly why it can be open to everyone, and why it *has* to be: a receipt chain only means something if you can check it without my permission.\n\nYou can watch it catch an edit. Rewrite a stored receipt straight in Firestore, leaving the hash and signature untouched, exactly as a silent after-the-fact edit would:\n\n```\n{ \"field\": \"payload.severity\", \"before\": \"HIGH\", \"after\": \"LOW\" }\n```\n\nThen ask the public endpoint:\n\n```\n{ \"verified\": false, \"receipt_count\": 2, \"broken_at_seq\": 1,\n  \"reason\": \"payload does not hash to the recorded hash: content was altered\" }\n```\n\nIt names the receipt. Not \"something is wrong\", but *sequence one, content altered.*\n\nOne caveat I put in my own demo script, because leaving it out would be a lie by omission: **an empty chain is a valid chain.** A deleted case answers `verified: true`\n\nwith `receipt_count: 0`\n\n. `verified`\n\non its own proves the absence of tampering in nothing at all. The count is what ties the proof to the story.\n\nOn one run, the agent told me:\n\n\"I have successfully read your mother's lab report and ingested it into her health record.\"\n\nDocuments actually stored: **zero**.\n\nFor a system whose entire pitch is a verifiable record, an agent asserting a write that never happened is fatal. Not embarrassing. Fatal. Everything else I had built was an argument that you could trust the record, and here was the record's own narrator making things up.\n\nPrompt wording was never going to fix that. So the demo now prints the stored count **read back from the service**, right next to what the agent claimed:\n\n```\nGROUND TRUTH. Documents actually stored for this parent: 2\nreported status 'ingested' vs stored count 2: consistent\n```\n\nIf they ever disagree, it says `CONTRADICTED`\n\non screen.\n\nThe general lesson I took: anywhere an agent reports an outcome, report the outcome **from the system that would know**, side by side, and make the disagreement loud.\n\nThis one is more useful, because it looks like nothing.\n\nThe document reader emits the kind `lab_report`\n\n. The record stores it as `blood_report`\n\n. I added a guard (an arriving lab report should close the outstanding diagnostic order it belongs to) and compared against the *stored* word.\n\nNothing raises. No error appears anywhere. No log line. The guard simply never matches, and the loop silently closes nothing, forever.\n\n**Eighteen unit tests passed.** They all called the function directly and handed it the right word themselves.\n\nThe fix wasn't the one-word change. It was writing two tests that go through the real ingestion path end to end, and **confirming they fail first**, with the message *\"the report went in and closed nothing; the hook and the reader disagree about what a lab report is called\"*.\n\nA test that cannot fail for the reason you care about is not covering that reason. I now break every new guard on purpose and check that something goes red before I let the change stand.\n\nThat same lab-report loop has a case where it does nothing, deliberately.\n\nIf **two** tests are outstanding on an admission and one report arrives, it closes neither.\n\nAttributing that report to one of the two orders means reading it to work out which test it is, and that is a model deciding which clinical order was carried out. So it stops, writes a receipt saying exactly why, and leaves a person an accurate record and an obvious next step.\n\nThe same instinct shows up in the memory. The system remembers one class of fact between admissions: whether she answers by voice note or by typing. It's in a Vertex AI Agent Engine Memory Bank, and there is **no free-text path into that store.** Each kind of memory has its own function composing its own sentence from a value validated first. A caller can't put a symptom in it because a caller can't put a *sentence* in it. Recall is an exact scope lookup, never a similarity search, so an unrelated memory can't surface because it read as close enough.\n\nThat's the whole philosophy in one small module: **decide what may be true, in code, and the interesting failures become impossible rather than unlikely.**\n\nGemini 3.5 Flash and the Agent Development Kit on Cloud Run, Firestore for state and the receipt ledger, Pub/Sub for multi-day case events, Cloud Storage for photographs, Vertex AI Agent Engine for Memory Bank, Google Places to verify that every hospital is a real place.\n\nCloud Scheduler matters more than it sounds. Cloud Run holds no timer, so the regulatory clocks are real scheduler jobs ticking every minute, which is the difference between an agent and a demo. It keeps running when the browser is closed and nobody is watching.\n\n**1,203 tests, none of which need cloud access or a model to run.**\n\nBecause a post that spent 2,000 words on honesty should end with some:\n\n`place_id`\n\nand a verification date. Capability and insurer empanelment are a dated seed and say so on every triage call.It also doesn't watch anyone. No sensors, no passive monitoring. An episode begins because a signal *arrives*, and the receipt says `received from an external channel, not detected by Anbu Care`\n\n. The tests reject the words \"detect\", \"notice\", \"sense\" and \"monitor\" in that path unless they follow a negation.\n\n```\nURL=https://anbu-care-37j4eofpwq-el.a.run.app\ncurl -s $URL/api/cases/case-da1c2cb6db/verify | jq   # verified: true, 8 receipts\ncurl -s $URL/api/cases/case-a7cf9fa613/verify | jq   # verified: false, broken_at_seq: 1\n```\n\nDashboard: `/app`\n\n. Health, including which components are simulated: `/api/healthz`\n\n.\n\nI created this article for the purposes of entering the **All Things Agentic Hackathon**. If you take one thing from it: put your guarantees in code, then try to break them from *outside* the agent. If it still holds, you have a control. If it doesn't, you have a prompt.\n\n*Anbu (அன்பு) is Tamil for love.*\n\n`make test`\n\n, confirm 1,203 and update if not.`curl -s $URL/api/healthz`\n\n, confirm the \"what isn't real\" list still\nmatches `tpa_mode`\n\n, `whatsapp_mode`\n\n, `memory_bank`\n\n.", "url": "https://wpnews.pro/news/the-guardrails-that-matter-are-code-not-prompts", "canonical_source": "https://dev.to/blockintel/the-guardrails-that-matter-are-code-not-prompts-1ii8", "published_at": "2026-08-30 19:01:08+00:00", "updated_at": "2026-08-30 19:23:20.882160+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-products", "developer-tools"], "entities": ["Google", "Gemini 3.5 Flash", "ADK", "Firestore", "India DPDP Act", "Meta", "WhatsApp", "Anbu Care"], "alternates": {"html": "https://wpnews.pro/news/the-guardrails-that-matter-are-code-not-prompts", "markdown": "https://wpnews.pro/news/the-guardrails-that-matter-are-code-not-prompts.md", "text": "https://wpnews.pro/news/the-guardrails-that-matter-are-code-not-prompts.txt", "jsonld": "https://wpnews.pro/news/the-guardrails-that-matter-are-code-not-prompts.jsonld"}}