{"slug": "approve-all-is-how-your-agent-ships-the-dangerous-one", "title": "'Approve All' Is How Your Agent Ships the Dangerous One", "summary": "A developer building agentproto argues that coding agents should auto-approve read-only commands and gate state-changing writes, rather than relying on manual approval or blanket permissions. The approach avoids approval fatigue and destructive errors by classifying actions by mutation risk at the tool level.", "body_md": "Disclosure up front: I build\n\n[agentproto], one of the\n\ntools in this market. The primitives in the second half are real and the\n\ncommands are checkable; the problem in the first half stands on its own.\n\nCorrections welcome — file an issue.\n\nYou gave your coding agent a shell. Now you live in one of two bad houses.\n\nIn the first house, every risky command stops and asks you. *Can I run\ngit push? Can I delete this file? Can I curl this?* You approve, approve,\n\nIn the second house, you got tired of that and ran the agent with\n\n`--dangerously-skip-permissions`\n\n. Now it never asks. It also never asks before\n\nthe `rm -rf`\n\nin the wrong directory, or the `DROP TABLE`\n\nagainst the wrong\n\n`DATABASE_URL`\n\n, while you're at lunch.\n\nThe one idea, if you remember nothing else:\n\nAuto-approve reads. Gate writes. The line isn'trisky vs. safe— it's\n\ndoes this change state.\n\nBoth houses share one bug: they treat \"should the agent need approval?\" as a\n\nproperty of your *attention* — how much you're willing to click — instead of a\n\nproperty of the *action*. Fix that, and the whole problem changes shape.\n\nAsk ten teams where the approval boundary goes and you'll get three wrong\n\nanswers. By **tool category**: \"shell is dangerous, file-read is fine\" — except\n\n`cat ~/.aws/credentials`\n\nis a read that exfiltrates and `echo`\n\ncan clobber a\n\nfile. By **reversibility**: \"block the irreversible stuff\" — except you can't\n\nreliably tell at call time what's reversible. By **vibes**: skip-all until\n\nsomething breaks.\n\nThe boundary that actually holds is simpler and it's not about the tool at all:\n\nDraw it at state mutation.One widely-cited write-up on human-oversight for\n\ncode agents (Niklas Heidloff) lands on the exact rule:auto-approve only theNot by tool, not by\n\ncommands that don't change state; gate the ones that do.\n\nreversibility — by whether the world is different afterward. A pure read can run\n\nfree. A write waits.\n\nAnd here's why \"just approve each one\" was never going to save you, stated by the\n\npeople who studied it: **manual-approve, skip-all, and static allowlists all fail\nthe same way.** Review every action and you hit approval fatigue — after hundreds\n\nThat's the same wall from [the trust piece](https://dev.to/agentiknet/you-can-parallelize-the-typing-you-cant-parallelize-the-trust-2fkd),\n\nwearing a different hat: you are the serial bottleneck, and \"approve each action\"\n\njust moves the bottleneck to your clicking finger.\n\nPlace yourself before you build. Four honest answers, each with its ceiling.\n\n**You approve each one.** Every gated call pings you; you click yes. Safe-ish,\n\nand completely unscalable — this is the approval-fatigue trap, and it caps at one\n\nagent you babysit full-time. *Ceiling: you, again.*\n\n**You skip all of it.** `--dangerously-skip-permissions`\n\n, YOLO mode. Fast, fully\n\nunattended, and one hallucinated path from a very bad afternoon. *Ceiling: the\nfirst destructive command nobody caught.*\n\n**You keep a static allowlist.** A file of pre-blessed commands. Better — but it's\n\nfrozen; the agent hits something new and either stalls or you widen the list until\n\nit's `*`\n\n. *Ceiling: it can't tell a read from a write, so it over- or\nunder-blocks.*\n\n**You classify by risk and relay only the writes.** Read-only calls auto-approve;\n\nstate-changing calls pause and route to a decision — a human *or* a policy — and\n\nthe important ones surface instead of drowning. *This is the only one that scales,\nand it's the one you can build.* The rest of this piece wires it.\n\nWhere are you?If your honest answer is\"skip-all, and I hope,\"you don't\n\nhave an autonomy setup — you have an unexploded one. Everything below moves the\n\nyes/no off your attention and into the architecture.\n\nThe relay only works if a call knows whether it mutates *before* it runs. So put\n\nthe risk class on the tool contract, not in a human's head. In agentproto a tool\n\ndeclares what it changes; a read-only tool mutates nothing and approves itself:\n\n``` js\n// a read-only tool — nothing changes, so it runs free\nexport const knowledgeSearch = defineTool({\n  id: \"knowledge.search\",\n  // …schema…\n  mutates: [],              // touches no state\n  approval: \"auto\",         // ⇒ auto-approved, never interrupts you\n})\n\n// a write tool — declares the mutation, so it must be cleared\nexport const applyPatch = defineTool({\n  id: \"fs.apply_patch\",\n  // …schema…\n  mutates: [\"fs\"],          // changes files on disk\n  approval: \"gated\",        // ⇒ pauses for a decision before it runs\n})\n```\n\nThat's \"auto-approve reads, gate writes\" as two lines of contract instead of a\n\npolicy you enforce by clicking. `knowledge.search`\n\nfrom\n\n[the knowledge piece](https://dev.to/agentiknet/your-coding-agent-knows-the-internets-average-heres-how-to-make-it-know-yours-1aeg) is literally `mutates: []`\n\nfor\n\nexactly this reason. The boundary is now data, so a machine can enforce it — which\n\nis the whole point of making approval, in the words of one human-in-the-loop\n\nbuild (Nylas), **a first-class architectural feature, not an afterthought you\nbolt on per tool.**\n\nNow the gated calls need somewhere to go that *isn't* a modal blocking your\n\nterminal. agentproto turns a pending write into an item in a cross-session inbox:\n\nthe agent pauses, the decision waits, and anyone — or anything — can answer it.\n\n```\n# what's waiting on a decision right now?\npermissions_list\n# → [{ id: \"perm_9f\", sessionId: \"sess_42\",\n#      tool: \"fs.apply_patch\", summary: \"write migrations/003_add_index.sql\" }]\n\n# answer it — approve once, approve-always, or deny\npermissions_respond { id: \"perm_9f\", decision: \"approve\" }\n```\n\nTwo things this buys that a terminal prompt doesn't. First, it's **out of band**:\n\nthe approval isn't trapped in one attached session, so a supervisor (or you, from\n\nyour phone) can clear it without sitting in front of the agent. Second, it's\n\n**programmable**: `decision`\n\nis just `approve`\n\n/ `deny`\n\n, so the responder can be a\n\nhuman for the calls that need judgment and a *policy* for the calls that don't.\n\nThe relay is the L2 rung, built.[The supervision ladder]\n\nmaps the whole climb fromyou watchtoa gate decides; this is the L2\n\n\"permission relay\" rung as actual wiring. Read-only auto-approves (Rung 1),\n\nrisky writes land here, and only the genuinely judgment-worthy ones ever reach a\n\nperson. Approval fatigue dies because the reads never showed up in the first\n\nplace.\n\nRelaying the write to a human is fine for a `DROP TABLE`\n\n. But for \"did this diff\n\nactually fix the bug,\" a human clicking approve is just a slower version of the\n\nagent grading itself. Some writes need a *check*, not a *click*.\n\nThe top rung replaces the human judgment call with something that scales: a gate\n\nthat runs *outside* the working agent and has to say yes before the write lands.\n\nYou attach it to the session, it fires at the end of the agent's turn, and its\n\nverdict — not the agent's self-report — decides what happens next.\n\n```\n// attach a gate to the session; a green result stages a commit behind YOUR ack\nattach_policy({\n  sessionId: \"sess_42\",\n  then: \"commit\",\n  gate: {                              // a shell gate: exit 0 = pass\n    command: \"pnpm\",\n    args: [\"test\", \"--run\", \"payments\"]\n  },\n  commit: {\n    paths: [\"src/payments/retry.ts\"],  // stages EXACTLY these — never `git add -A`\n    message: \"fix: cap payment retries at 3 (incident #2026-04)\",\n    requireHumanAck: true              // green gate ⇒ waits for your yes\n  }\n})\n```\n\nThe lifecycle is the part that matters: the gate runs, and on green the policy\n\ndoesn't commit — it moves to `awaiting-ack`\n\nand emits `policy:commit-ready`\n\n. The\n\ncommit **does not happen** until you call `ack_policy({ policyId, approve: true })`\n\n,\n\nwhich stages *only* the listed paths (`git add -- <paths>`\n\n, never `-A`\n\n, never a\n\nglob), commits, and emits `policy:committed`\n\n. It never pushes. The agent cannot\n\nreach past the gate to the commit; the gate cannot reach past your ack to the\n\npush.\n\nWhen exit codes can't judge, spawn a skeptic.Swap the shell gate for\n\n`gate: { judge: { adapter, prompt } }`\n\nand agentproto runs a short-lived LLM\n\nreviewer that ends in`VERDICT: PASS|FAIL`\n\n— fail-safe, so a timeout or an\n\nunparsable answer counts as FAIL. Prompt it torefute, not admire (\"try to\n\nprove this diff did NOT fix the bug\"). That's the[kill-the-loop]\n\nlesson in one primitive: the check has to sit outside the loop it's checking, or\n\nit rubber-stamps.\n\nYou now have the full plane: reads run free, writes relay, load-bearing writes\n\nwait on an external verdict, and the irreversible act — the commit — is staged\n\nbehind one human yes.\n\nThis is real infrastructure, which means it has sharp corners. Four I hit live,\n\nso you don't:\n\n`test`\n\nisn't on it.`.agentproto/allowed-commands.json`\n\n). `test -f x`\n\nfails with `blocked`\n\n. Use an allowlisted binary — `ls x`\n\n, not\n`test -f x`\n\n; `pnpm`\n\n/`node`\n\nfor tests.`cwd escapes the workspace`\n\n.\nIf you run agents in per-feature worktrees outside the repo root, shell gates\nare effectively unusable — fall back to `then: \"emit\"`\n\n(milestone only) and\nverify the result yourself with `git`\n\n/`gh`\n\n.`turn-end`\n\n; attach the policy\n`attach_policy`\n\n, the event bus, the ack. Don't build a control tower on the part\nthat isn't load-bearing yet.And the fair comparison, because credibility depends on it. **Claude Code already\nships a good version of Rung 1–2** — per-command permission prompts and an\n\nAutonomy was never the scary part. An agent that can read your whole codebase,\n\nrun your tests, and draft the fix is exactly what you want. The scary part is the\n\n*write* — the one command that changes something you can't un-change — landing\n\nbecause you were too tired to catch it or too far away to be asked.\n\nSo stop deciding approval with your attention and start deciding it with the\n\naction. Auto-approve the reads; they can't hurt you. Relay the writes; a human or\n\na policy clears them. Put an external check on the writes that matter, and stage\n\nthe one irreversible act — the commit — behind a single deliberate yes. The agent\n\nproposes. Something outside the agent disposes. That's the whole plane.\n\nGive your agent the keys. Just keep the ignition where it can't reach it.\n\nIf your setup draws the approval line somewhere smarter than state mutation, or\n\nyou've made approve-each actually scale, tell me where — I'll fix the piece.\n\nTen pieces, one argument. Start anywhere; each one cross-links the rest.\n\n| Piece | The one idea | |\n|---|---|---|\n| 1 |\n|\n\n*Written by the maintainer of agentproto (Apache-2.0, source). Same contract as our /compare page — dated facts, named strengths, corrections by issue. Got something wrong? File an issue.*\n\n*Building agentproto in the open — follow @theagentproto and @agentik_ai on X.*", "url": "https://wpnews.pro/news/approve-all-is-how-your-agent-ships-the-dangerous-one", "canonical_source": "https://dev.to/agentiknet/approve-all-is-how-your-agent-ships-the-dangerous-one-2ma", "published_at": "2026-07-14 01:36:42+00:00", "updated_at": "2026-07-14 01:57:10.108784+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "developer-tools"], "entities": ["agentproto", "Niklas Heidloff"], "alternates": {"html": "https://wpnews.pro/news/approve-all-is-how-your-agent-ships-the-dangerous-one", "markdown": "https://wpnews.pro/news/approve-all-is-how-your-agent-ships-the-dangerous-one.md", "text": "https://wpnews.pro/news/approve-all-is-how-your-agent-ships-the-dangerous-one.txt", "jsonld": "https://wpnews.pro/news/approve-all-is-how-your-agent-ships-the-dangerous-one.jsonld"}}