The Agent That Approves Itself

The two-key rule, the proposer who never ratifies, is a design constraint for putting an agent into any workflow where being wrong actually costs something.

Let's Call It Two-Key Engineering

A deployment agent notices, on a Friday afternoon, that a smoke test is failing against production. It reads the failure, decides the assertion is the part that is wrong, rewrites it, reruns the suite, watches everything go green, and promotes the build. Every individual action in that sentence is something we've spent the last two years teaching agents to do, and to do well. The sequence as a whole is a quiet disaster, because the process that decided the change was acceptable is the same process that produced the change. Nobody else looked. Nothing in the pipeline had the standing to say no.

That failure is usually filed under testing, because that's where most of us first met it: an agent that writes a test and then certifies its own test as passing. The framing is too narrow. The same shape appears wherever an agent both authors a consequential action and holds the authority that releases it, and the consequential actions are multiplying fast. Agents now open pull requests, run data migrations, adjust infrastructure, assemble refund batches, publish content, and send mail to real customers. Hand any one of those an approval step it can grant itself, and you have collapsed two roles that banks, militaries, and accounting departments have kept deliberately apart for a very long time.

The rule is simple to state and genuinely uncomfortable to enforce: the hand that proposes a change must never be the hand that ratifies it. A separate ratifier has to stand between the proposal and the consequence, and that ratifier has to be capable of refusal. This is the two-key principle, the proposer who never ratifies, and it isn't a testing technique. It's a design constraint for putting an agent into any workflow where being wrong actually costs something.

SPONSORED CONTENT

The Lithium Boom is Heating Up

Lithium stock prices have more than doubled in the past year in response to ballooning costs and shortages. $ALB climbed 185%. $SQM, 133%.

This $1B unicorn’s patented technology can recover up to 3X more lithium than traditional methods. That’s earned investment from leaders like General Motors.

Now they’re preparing for commercial production just as experts project 5X demand growth by 2040. EnergyX is tapping into 100,000+ acres of lithium deposits in Chile, a potential $1.1B annual revenue opportunity at projected market prices.

Energy Exploration Technologies, Inc. (“EnergyX”) has engaged Beehiiv to publish this communication in connection with EnergyX’s ongoing Regulation A offering. Beehiiv has been paid in cash and may receive additional compensation. Beehiiv and/or its affiliates do not currently hold securities of EnergyX.

This compensation and any current or future ownership interest could create a conflict of interest. Please consider this disclosure alongside EnergyX’s offering materials. EnergyX’s Regulation A offering has been qualified by the SEC. Offers and sales may be made only by means of the qualified offering circular. Before investing, carefully review the offering circular, including the risk factors. The offering circular is available at invest.energyx.com/.

Comparisons to other companies are for informational purposes only and should not imply similar results. Past performance is not indicative of future results. Market shortfall are forward‑looking estimates and are subject to substantial uncertainty.

COMPANION SCRIPT

Companion script for this issue: samehand. Point it at a plain-text description of your pipeline and it flags every step where the proposer and the ratifier are the same identity, plus any step missing one of the two keys entirely. It parses in pure awk, so there is nothing to install, and the full version runs inline below in the Quick Tip. Hand-raiser keyword: SAMEHAND. The full implementation lives in the bashmatica-scripts repo, but the inline copy is complete and runnable on its own.

The Pattern Is Older Than Software

Separation of duties isn't a software idea that the agentic era stumbled into; it's a control that predates the computer by centuries, and software has been quietly re-deriving it the whole time. The accounting version splits a transaction into four duties, authorization, custody, record-keeping, and reconciliation, and hands each to a different person, on the theory that fraud and catastrophic error both require collusion once no single individual controls the whole chain. The banking version, maker-checker, is even more direct: one clerk makes the entry, a different clerk checks and authorizes it, and the system refuses to let one person do both. The most vivid version is the two-person rule on nuclear weapons, where the launch keys sit too far apart for one operator to reach both, a physical guarantee that no single hand can end the world on its own initiative.

Software adopted the same control without always noticing the lineage. The reason a developer doesn't approve their own pull request is maker-checker with different vocabulary. GitHub ships the constraint as a branch-protection setting that requires the most recent push to be approved by someone other than the person who pushed it, and the SLSA framework hardens it into a supply-chain requirement: changes to a protected branch must be agreed to by two or more trusted persons, and the author and the reviewer have to be two different people. The consequences of skipping this aren't theoretical; insider attacks on software supply chains have repeatedly come down to a single trusted account pushing a malicious change with nobody required to look. Every one of these controls is the same sentence rephrased for a new domain: the proposer is never the ratifier, on purpose, by design.

What the agentic era changes isn't the principle but the speed and the plausibility. A human who tries to approve their own expense report at least knows they're bending a rule. An agent that both writes a fix and certifies it passing has no such awareness; it's optimizing for the signal you gave it, which is usually "make this green," and it will collapse proposal and approval into one confident motion precisely because no part of its objective tells it those are supposed to be separate jobs. The control existed to constrain humans who might cut a corner. It now has to constrain a process that doesn't even know the corner is there.

FOR FURTHER READING

  • Separation of duties (Wikipedia). The parent concept: authorization, custody, record-keeping, and reconciliation belong to different people, because no one should control more than one part of a transaction.

  • Maker-checker (Wikipedia). The banking incarnation, where one individual creates a transaction and a different individual must confirm it; the cleanest statement of proposer-never-ratifies in the wild.

  • The two-person rule (Wikipedia). The nuclear version: two operators, two keys, set too far apart for one person to turn both, so that no single human can launch alone.

  • About protected branches (GitHub Docs). The setting most teams already own: require that the most recent reviewable push be approved by someone other than the person who pushed it.

  • SLSA source requirements (slsa.dev). The supply-chain mandate that changes to protected branches be agreed to by two or more trusted persons, with the uploader and the reviewer counted as two different people.

Spotting The Same-Hand Steps

The control only helps if you can find the places that need it, and in an agentic pipeline those places hide in plain sight, because a single agent doing a whole job end to end looks like exactly the efficiency you were trying to buy. The diagnostic question is narrow and unforgiving: for each consequential step, who proposes the change, and who grants the approval that lets it take effect? When the answer to both is the same identity, you have a same-hand step, and a same-hand step on anything with real consequences is a latent incident waiting for the input that triggers it.

Run that question across the work agents are already doing, and the same-hand steps surface quickly. An agent that generates a code fix and also runs the check that blesses the fix is grading its own paper. An agent that proposes a database migration and also decides the migration is safe to apply is the custody and the authorization duty fused into one. An agent that drafts a customer email and also makes the send-or-hold call is the writer and the editor collapsed, with the customer's inbox as the proving ground. An agent that assembles a refund batch and also authorizes the disbursement is maker and checker in a single account, which is the exact arrangement maker-checker was invented to forbid. None of these look dangerous in a demo, because in a demo the agent is right. The danger is structural, and it only cashes in when the agent is wrong and confident at the same time, which is the one combination no self-grading process can catch.

The consequences land on people, and naming them is the fastest way to feel why this matters. The engineer who has to explain, in next quarter's incident review, why a deploy that "passed" took down checkout for ninety minutes, didn't get to rely on a second set of eyes, because the pipeline never had a second set. The controller who signs the books inherits a refund batch that an agent both built and approved, with no independent record that anyone other than the agent ever agreed the numbers were real. The customer who receives an email an agent wrote and an agent sent had no editor between the draft and their inbox. In every case the cost is the same: a consequential action went out with one hand on it, and the audit trail that should let someone reconstruct who agreed to what is a single signature signed twice.

Negative Space

Once you've found a same-hand step, the fix is to interpose a separate ratifier, and you have exactly two honest options for what that ratifier is. The first is a deterministic gate: a piece of plain, non-agentic code that checks the proposal against concrete criteria and passes or fails on the numbers, no judgment and no subjectivity involved. A gate that holds a deploy until the error budget, the migration lock, and the change-freeze window all check out is a ratifier the proposer can't sweet-talk, because it doesn't reason; it measures. The second option is a different agent, with a different prompt and ideally a different model, whose only job is to evaluate the proposal and who never authors changes itself. Either works. What doesn't work is letting the proposer keep both keys.

The arrangement is easiest to hold in your head as a small policy that names the two keys for every consequential step explicitly:

# pipeline-policy.yaml: every consequential step carries two distinct keys.
deploy_to_prod:
  proposer: deploy-agent          # builds and stages the release
  ratifier: deterministic-gate    # checks error budget, migration lock, freeze window
  on_reject: hold-and-page-human

refund_batch:
  proposer: finance-agent         # assembles the batch
  ratifier: human:controller      # a person, because money leaves the building
  on_reject: queue-for-review

publish_issue:
  proposer: draft-agent
  ratifier: editor-agent          # a different model and prompt, never the writer
  on_reject: return-with-notes

The detail that separates a real ratifier from a decorative one is the reject path. A ratifier that can only ever approve isn't a control; it's a rubber stamp wearing a lanyard, because it manufactures the appearance of oversight while providing none. The ratifier has to be able to refuse, the refusal has to actually stop the consequence, and the rejection has to go somewhere humans regularly look. That's what on_reject is doing in the policy above: a held deploy pages a person, a rejected refund batch queues for review, a returned draft goes back with notes. If your proposed ratifier has no path that ends in "this doesn't ship," you haven't added a second key. You've added a second signature from the same hand and told yourself it counts.

For the teams whose consequential changes already flow through Git, most of this is a setting you already own rather than a system you have to build. Turning on the branch-protection rule that requires approval from someone other than the last pusher, and pointing the approver at a separate reviewer agent or a human, converts "an agent merges its own work" into "an agent proposes and a different identity ratifies" without writing a line of orchestration. The two-key principle doesn't always demand new infrastructure. Often it just needs you to stop routing around the controls already available.

SPONSORED CONTENT

You’re gonna want to see this live

On July 16th at 1PM ET,  is unveiling the next chapter for audience-led businesses.

For years, creators and brands have been forced to stitch together bloated stacks of tools just to publish content, grow an audience, and make money online.

Newsletters in one platform. Websites in another. Podcasts somewhere else. Analytics scattered everywhere.

thinks there’s a better way. Now, they’re ready to show it off at their Summer Release Event.

QUICK TIP

Find Your Same-Hand Steps In One Pass

Before you can interpose a ratifier, you have to know which steps lack one, so the first move is an inventory. Write your pipeline as plain pipe-delimited lines, one step per row, naming the proposer and the ratifier for each, then let samehand flag the collisions:

#!/usr/bin/env bash
# samehand.sh - flag any pipeline step whose proposer is also its ratifier.
# Format: pipe-delimited "step | proposer | ratifier". Blank lines and # comments ignored.

scan() {
  awk -F'|' '
    function trim(s){ gsub(/^[ \t]+|[ \t]+$/,"",s); return s }
    /^[ \t]*#/ || /^[ \t]*$/ { next }
    {
      step=trim($1); proposer=trim($2); ratifier=trim($3)
      if (proposer=="" || ratifier=="") {
        printf "INCOMPLETE  %-16s missing a proposer or a ratifier\n", step; warn++; next
      }
      if (proposer==ratifier) {
        printf "SAME-HAND   %-16s %s proposes AND ratifies\n", step, proposer; fail++
      } else {
        printf "TWO-KEY     %-16s %s -> %s\n", step, proposer, ratifier
      }
    }
    END {
      printf "\nVerdict: "
      if (fail>0)      printf "NOT CLEAN (%d same-hand, %d incomplete)\n", fail, warn+0
      else if (warn>0) printf "INCOMPLETE (%d step(s) missing a key)\n", warn
      else             printf "CLEAN (every step has two distinct keys)\n"
      exit (fail>0 || warn>0) ? 1 : 0
    }
  ' "$1"
}

case "${1:-}" in
  --pipeline) [ -n "${2:-}" ] || { echo "usage: $0 --pipeline FILE"; exit 2; }; scan "$2" ;;
  --example)
    tmp="$(mktemp)"
    cat > "$tmp" <<'EOF'
# step             | proposer       | ratifier
deploy_prod        | deploy-agent   | deterministic-gate
db_migration       | migrate-agent  | migrate-agent
refund_batch       | finance-agent  | human:controller
test_autofix       | fixer-agent    | fixer-agent
publish_post       | writer-agent   | editor-human
send_campaign      | outreach-agent |
EOF
    scan "$tmp"; rc=$?; rm -f "$tmp"; exit $rc ;;
  *) echo "usage: $0 --pipeline FILE | --example"; exit 2 ;;
esac

The bundled demo seeds two same-hand steps and one missing key:

./samehand.sh --example

# TWO-KEY     deploy_prod      deploy-agent -> deterministic-gate
# SAME-HAND   db_migration     migrate-agent proposes AND ratifies
# TWO-KEY     refund_batch     finance-agent -> human:controller
# SAME-HAND   test_autofix     fixer-agent proposes AND ratifies
# TWO-KEY     publish_post     writer-agent -> editor-human
# INCOMPLETE  send_campaign    missing a proposer or a ratifier
#
# Verdict: NOT CLEAN (2 same-hand, 1 incomplete)

Exit 0 means every consequential step carries two distinct keys; exit 1 means at least one step is a proposer grading itself or is missing a key entirely. Run it against your own pipeline with --pipeline pipeline.txt, and wire it into CI so a new same-hand step can't land without someone deciding, on purpose, to allow it.

Quick Wins

🟢 Easy (~20 min): Pick one pipeline and write every consequential step as a single line naming its proposer and its ratifier, then circle each row where the two are the same identity. The inventory is the whole exercise; if you can't name a distinct ratifier for a step, you just found a same-hand step the hard way.

🟡 Medium (~1 hour): On a repository where an agent opens or merges pull requests, turn on the branch-protection rule that requires approval from someone other than the last pusher, and point that approval at a human or a separate reviewer agent. You have converted a self-merging agent into a proposer with a real ratifier using a setting you already had.

🔴 Advanced (half day): Take one agentic workflow with genuine consequences and split it into a proposer and a separate ratifier, the ratifier being either a deterministic gate or a different agent with a different model, and give it a working reject path that stops the consequence and surfaces the rejection to a person. Confirm it can say no by feeding it a proposal that should fail and watching the action not ship.

Next Week

The 80/20 problem no one talks about, but they absolutely should.

A deployment agent rewriting its own failing test on a Friday afternoon isn't a test problem, and it isn't really an agent problem either; it's the oldest control failure there is. The accountants, the bankers, and the missile crews all landed on the same answer long before any of us were automating anything, and the answer survives the translation intact.

The hand that proposes a change must never be the hand that acts on it. Find the steps in your pipeline where the green light comes from the same hand that earned it, put a ratifier between the proposal and the consequence, and make sure that ratifier can say no. Do that, and your agents can move as fast as you like, because the speed is no longer the thing deciding right from wrong.

Two keys, two hands. That's the discipline.

P.S. I'm building the TestScout MCP suite on exactly this principle: tools that propose and tools that ratify, kept in separate hands, so an agent never certifies its own work. The furthest-along piece, LightScout, is a deterministic gate made concrete, a check that holds a page to hard performance boundaries and fails only when the numbers genuinely miss. If this issue gave you something, forward it to someone whose pipeline has a same-hand step in it. If someone forwarded it to you, subscribe at bashmatica.com.

NODEBRIDGE AUTOMATION SOLUTIONS

Standing up agents in your stack and want the guardrails built in from day one?

NodeBridge sets up your team's Claude Code and AI dev environment with the two-key gates, deterministic checks, and safe-automation patterns from these issues already wired in. Fixed scope, done-for-you.

SPONSORED CONTENT

Your Business Website. Built in Under 2 Minutes

Running a business is hard. Building a website shouldn't be.

With , just describe your business, the AI handles your website design, performance, SEO optimization, and more. 

No coding or design skills needed. Launch in 2 minutes.