<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Samuel Amankwah Donkor</title>
    <description>The latest articles on DEV Community by Samuel Amankwah Donkor (@samadon1).</description>
    <link>https://dev.to/samadon1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F402549%2F894d41f9-2ee8-4c39-89f7-ad7cea157a06.jpeg</url>
      <title>DEV Community: Samuel Amankwah Donkor</title>
      <link>https://dev.to/samadon1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/samadon1"/>
    <language>en</language>
    <item>
      <title>I built an agent that can fire other agents</title>
      <dc:creator>Samuel Amankwah Donkor</dc:creator>
      <pubDate>Tue, 25 Aug 2026 10:16:28 +0000</pubDate>
      <link>https://dev.to/samadon1/i-built-an-agent-that-can-fire-other-agents-2of0</link>
      <guid>https://dev.to/samadon1/i-built-an-agent-that-can-fire-other-agents-2of0</guid>
      <description>&lt;p&gt;&lt;em&gt;I built this for the All Things Agentic Hackathon (#AllThingsAgenticHackathon).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Everyone is shipping AI agents and almost no one can stop one. Kiteworks' 2026 survey put numbers on it: 63% of enterprises cannot enforce purpose limits on an agent they have already deployed, and 60% cannot shut one down when it misbehaves. The incidents are already public. Replit's agent deleted a production database during a code freeze. A single GitHub issue hijacked an agent inside Google's own ADK repository and reached a service-account credential.&lt;/p&gt;

&lt;p&gt;The agent is rarely the problem. It has real access, nothing is watching what it does with it, and nothing can stop it in time. So I built the agent that watches the agents. Warden is a guardian for a fleet of ADK agents on Google Cloud. Here is the four minute demo:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/QoA2OqhHZGo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The interesting decisions had less to do with the model and more to do with what I decided not to hand the model at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mandate is the product
&lt;/h2&gt;

&lt;p&gt;Every agent registers a machine-readable contract next to its deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;agent_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;refunds-agent&lt;/span&gt;
&lt;span class="na"&gt;allowed_tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;payments_refund&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;refunds_list&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;approvals_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;forbidden_tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;customers_pii_read&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;constraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Refunds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;over&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$300&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;need&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;approval&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;they&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run"&lt;/span&gt;
&lt;span class="na"&gt;data_boundaries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;may_read&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;orders&lt;/span&gt;&lt;span class="pi"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;may_not_read&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;customers.pii&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;warn_below&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.95&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;quarantine_below&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.90&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;window&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;20&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;enforcement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;autonomous&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;human_override&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The constraints are written in plain English on purpose, because that is the part a rules engine cannot read and a model can. The rest is the opposite case: forbidden tools and the allow-list are decided in code, at confidence 1.0, before any model is asked. A verdict can then point to the exact clause that was broken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsamadon1%2Fwarden%2Fmain%2Fdocs%2Farchitecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsamadon1%2Fwarden%2Fmain%2Fdocs%2Farchitecture.png" alt="Warden architecture" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Judge the trace, not the transcript
&lt;/h2&gt;

&lt;p&gt;ADK agents emit OpenTelemetry spans, and with content capture on, the &lt;code&gt;execute_tool&lt;/code&gt; spans carry the arguments a tool was called with and what it returned. A small shared runtime exports those to Pub/Sub, and Warden judges each action against the mandate with structured JSON at temperature 0. At the end of each task a second judge reads the whole sequence and asks whether it drifted, which is the check a content filter cannot do, because nothing in the text of any single message is wrong.&lt;/p&gt;

&lt;p&gt;Two lessons about the prompts. Tell the judge to rule on this action only, or it will blame the harmless confirmation email for the bad refund that came before it. And stop the end-of-task judge from re-counting what the per-action judge already caught.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two tiers, and a first tier that cannot fail
&lt;/h2&gt;

&lt;p&gt;One read of a PII table is already one too many, so a hard breach quarantines the agent on a single confident verdict. Softer drift, like a refund going out over the limit without approval, decays a rolling score across the window the mandate defines, so the agent gets a warning before it gets quarantined.&lt;/p&gt;

&lt;p&gt;Enforcement is layered. The first tier is a Firestore status flag that every agent checks before it acts, and it fails safe, so no model sits in that path. The second tier disables the agent's own service account through IAM, which turns its next Google API call into a real 403. Only then does Warden's own agent, itself an ADK agent with tools, take over: it revokes the credential, works out what changed, writes the incident report, and tells a human who can reinstate the agent with one click.&lt;/p&gt;

&lt;p&gt;Here is that sequence from the demo, when a compromised remediation agent tried to delete a production backup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsamadon1%2Fwarden%2Fmain%2Fdocs%2Fincident.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsamadon1%2Fwarden%2Fmain%2Fdocs%2Fincident.png" alt="The breach" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It builds the agents it supervises
&lt;/h2&gt;

&lt;p&gt;You describe an agent in plain words, and a factory turns the brief into a full design: the code on ADK, the mandate, the tools from a vetted catalog, and a set of test scenarios including adversarial ones. Cloud Build deploys it under its own service account with the Warden layer already wired in, and it has to pass simulation before it goes live. Governance is there from the agent's first request, not bolted on later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsamadon1%2Fwarden%2Fmain%2Fdocs%2Ffactory.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsamadon1%2Fwarden%2Fmain%2Fdocs%2Ffactory.png" alt="The factory" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that bit me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ADK's &lt;code&gt;Runner&lt;/code&gt; ignores the early-exit from &lt;code&gt;before_run_callback&lt;/code&gt;, so the first-tier guard uses &lt;code&gt;before_agent_callback&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gcloud run deploy --source&lt;/code&gt; obeys &lt;code&gt;.gitignore&lt;/code&gt; unless you add a &lt;code&gt;.gcloudignore&lt;/code&gt;, which is how my dashboard build quietly disappeared once.&lt;/li&gt;
&lt;li&gt;Firestore needs a composite index for &lt;code&gt;where&lt;/code&gt; and &lt;code&gt;order_by&lt;/code&gt; on different fields, so I kept the hot paths on single documents.&lt;/li&gt;
&lt;li&gt;Revoking a real credential means re-enabling it has propagation delay. Design the human loop around that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it composes
&lt;/h2&gt;

&lt;p&gt;Warden does not replace the Google Cloud agent pieces, it composes them: OpenTelemetry for what agents do, a service account per agent for identity and revocation, and versioned mandates as the registry. Model Armor guards what an agent says. Warden guards what a fleet of agents does.&lt;/p&gt;

&lt;p&gt;It runs on Gemini 3.5 Flash, the Agent Development Kit, and Google Cloud (Cloud Run, Pub/Sub, Firestore, Cloud Storage, Cloud Build, IAM, Secret Manager).&lt;/p&gt;

&lt;p&gt;Code and spin-up: &lt;a href="https://github.com/samadon1/warden" rel="noopener noreferrer"&gt;https://github.com/samadon1/warden&lt;/a&gt;&lt;br&gt;
Demo: &lt;a href="https://www.youtube.com/watch?v=QoA2OqhHZGo" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=QoA2OqhHZGo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are running agents in production, how are you stopping one that goes wrong today? I would like to hear what people are actually doing.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>googlecloud</category>
      <category>gemini</category>
    </item>
  </channel>
</rss>
