The agent was auditing a Proxmox host. It came back with findings: plausible configuration details, IDs that looked right, the confident tone you'd expect from a tool that had done its job. It read like real output. It was fiction. The agent had never actually run the commands. It generated text that looked like command output, wearing all the right formatting, and handed it to me as fact.

I want to be precise about why that's dangerous, because it's easy to shrug off as "AI hallucinates, everyone knows that." The danger isn't that the model made something up. The danger is that untrusted prose and real observations were coming through the same interface, so they looked equally authoritative. I had no structural way to tell a claim it verified from a claim it invented. If I make a decision about a firewall, a routing table, or a backup based on a claim that turns out to be fabricated, I've acted on a lie, and nothing in the system told me it was one.

No amount of prompt engineering fixes that. You cannot instruct a language model out of producing plausible text, because producing plausible text is the one thing it always does. A better system prompt, a stricter classifier, a fine-tune, none of it addresses the actual problem, which is that the thing making the claim is also the thing I'm trusting to tell me whether the claim is real.

So I stopped trying to make the agent honest and made honesty structural instead. That's VIRP.

The one idea

The whole protocol comes down to a single principle: the component making a claim must not be the sole authority for verifying that claim. Execution evidence has to come from a trust boundary the requesting agent cannot control.

In practice that means the agent never touches the device. A separate process (the collector) does. It runs the actual command, captures the real output, and cryptographically authenticates that output against the exact command and target before the agent ever sees it. The agent can read the result and reason about it. It cannot manufacture one that verifies, because it never holds the key the collector uses.

One thing to be clear about up front, because it governs everything below: VIRP does not prove that a device is honest or uncompromised. It proves that a designated collector authenticated a particular operation record under a defined trust boundary. That is narrower than "proof of reality," and it is the honest ceiling of what this can do. Everything else is engineering in service of it.

Watching it work

Let me show you the real thing, running in my colo, rather than describe it further. Three commands. Every line of output below is copied from an actual session, not a mockup.

It authenticates what it reads

I ask for a read through the gate. Note that I'm not writing a URL or an HTTP method, I'm asking for a named operation (backup.version.read) and the driver decides what that resolves to. I couldn't smuggle in a request the gate hadn't classified even if I tried.

device=pbs-lab command="pbs op=backup.version.read" trust_tier=GREEN (0x01) seq=1605 obs_type=0x07 (authenticated observation) authentication=VALID gate_decision=allowed payload: pbs-lab>pbs op=backup.version.read [GET /api2/json/version] [HTTP 200] {"data":{"release":"8","repoid":"...","version":"3.4"}}

The lines that matter:

  • authentication=VALID: the payload below was authenticated with a key the collector holds and the agent does not. It means the record has not changed since the collector authenticated this request-and-response material. (It's an HMAC, a symmetric authentication code, not a public key signature. More on what that does and doesn't buy in the limits section.)
  • gate_decision=allowed: this wasn't "nothing stopped me." An explicit classify-and-decide step happened, and it's recorded.
  • [GET /api2/json/version] [HTTP 200]: the record preserves the exact request the collector issued, not just the answer. A verifier can check that the recorded response was authenticated against that recorded request, so the two can't later be silently re-paired.

The payload is what the collector read back from the backup server: a report that it's running version 3.4. The record doesn't prove the server is honest or uncompromised, it proves the collector obtained and authenticated exactly these bytes for exactly this request. That's a receipt, not a log line that says "trust me."

It refuses what it shouldn't, and the "no" is authenticated too

Now I ask for something that would act on the server instead of reading from it.

device=pbs-lab command="pbs op=backup.verify.run" trust_tier=RED (0x03) seq=1647 obs_type=0x0f (ERROR — authenticated rejection, nothing executed) authentication=VALID gate_decision=blocked payload: ERROR: tier gate blocked 'pbs op=backup.verify.run' (tier=RED max=YELLOW) reason: unknown operation id — the operation table is closed and RED by absence; no write operation exists at any tier. Use op=backup.version.read, backup.datastore.usage, backup.snapshots.list or backup.verify.tasks proposal_id=2308fe583c38563032da277985b20e93

What's happening here:

  • RED, nothing executed: the gate classified it as not-allowed and it never touched the server.
  • obs_type=0x0f versus the 0x07 above: a rejection is authenticated under a distinct type from a real observation. A verifier checking the type field can tell "the gate refused this" apart from "the collector returned this," so a rejection can't be re-presented as a successful read without failing verification.
  • authentication=VALID: and this is the part people miss, so slow down on it: the refusal itself is authenticated. It's not just that the command was blocked. The record of the block is evidence you can check later. You can show that this was asked and this was denied.
  • The teaching reason: it doesn't just say no. It explains why (the operation table is closed, anything not explicitly listed is denied by default) and tells you what you can do. Deny-by-default, in plain language.
  • proposal_id: even a blocked command files a proposal. If this were something I legitimately needed to do, there's now a record a human could review and approve with a separate key the collector can't hold. The gate isn't just a wall, it's the front door to a governed approval process.

Same authentication=VALID on both the allow and the block. One executed, one didn't. That contrast is the entire idea in two commands.

You can detect unauthorized changes to the record

Finally, the chain. Every observation and every rejection lands in a record where each entry carries an authenticated digest of the one before it (a keyed value the collector computes; the ENTRY_HASH column below is that per-entry value, and PREV_HASH is the prior entry's).

SESSION SEQ TYPE ENTRY_HASH PREV_HASH autopilot:2026-08-01 459 observation 1ca15c67f11c4c34 cec07e05dbb9aa9d autopilot:2026-08-01 460 observation 3fa74d94ccb15c7f 1ca15c67f11c4c34 autopilot:2026-08-01 461 observation a3a93fb92ed09bc2 3fa74d94ccb15c7f autopilot:2026-08-01 462 observation 054fe7491d3161a0 a3a93fb92ed09bc2

Follow the columns: entry 459's ENTRY_HASH is 1ca15c67..., and that exact value is entry 460's PREV_HASH. 460's is 3fa74d94..., which is 461's PREV_HASH. Every entry commits to the one before it.

An actor without the collector's key cannot alter a middle entry, remove one, or insert one and produce a replacement chain that still verifies. Unauthorized changes are detectable. (An actor who does hold the key is a different story, and I'll get to it, that's exactly why external anchoring matters.)

Authenticated, refused, recorded. That's the protocol.

What this is good for beyond AI

I built this because of an AI agent, but the agent turned out to be the least important part. The gate doesn't know or care whether an AI, a plain script on a timer, an SDN controller, or a human at a keyboard is submitting the command. The problem it solves isn't "AI lies." It's "the thing reporting what happened is not a trustworthy witness to its own behavior," and that's true of a buggy script reporting success on a failed job, a compromised monitoring box, or a tired operator transcribing the wrong output into a ticket.

That's why it runs perfectly well with no AI in it at all, which turns out to be exactly what regulated environments want. A bank or a utility that can't put an AI on its network still needs to show an examiner what was requested, what the collector attempted, what response came back, what was approved, and that the record wasn't altered afterward. VIRP produces that evidence whether there's a model in the loop or not. What it does not do is certify that the response reflects the device's true state, only that the collector authenticated this request-and-response under its trust boundary.

What it does NOT do yet

If you want to know whether a security project is honest, read its limitations, not its features. Here are mine, plainly.

It's HMAC, not a public-key signature. I say "authenticated" and "cryptographically bound" deliberately, not "signed." The integrity guarantee is symmetric: anyone holding the collector's key can produce a valid record. That means the chain is tamper-evident within a trusted collector, and it proves nothing against a collector that has itself been compromised. A key holder can rewrite the chain and recompute the authentication values. Closing that gap means external anchoring, publishing chain checkpoints to an independent witness so a replaced history can't pass as the real one, and hardware backed keys. That work is designed, not done.

Authentication is not the same as truth. A VALID record proves the collector authenticated those bytes for that operation. It does not by itself prove the device originated them, that they correspond exactly to the intended command, or that they reflect current rather than cached state. My own security docs record a live case where a collector authenticated output that didn't correspond to the command it was labeled with. Cryptography preserves that kind of error, it doesn't correct it. Tightening operation-to-response correlation is active work, and I assume the problem class is wider than the cases I've closed until proven otherwise.

The collector is a concentrated point of trust, on purpose. Everything depends on that one process being sound: the device credentials, the key, the classification logic. Compromise it and you compromise the guarantee. That's an acceptable trade, the same one you make with an HSM or a credential vault, but only if the collector is hardened accordingly, and hardening it fully is ongoing.

The AI-side enforcement is accountability, not a completeness proof. The piece that checks an agent's claims against evidence relies on the agent declaring which of its statements need backing. The same untrusted model is therefore partly responsible for enumerating what should be checked. It's a genuine accountability mechanism, but it does not by itself prove that every assertion in the model's prose was verified. The honest boundary is that VIRP authenticates evidence; controlling what unverified prose reaches a human is a separate layer's job, and I'd rather draw that line clearly than overstate what the protocol covers.

Some primitives are implemented but not battle-tested. The observation and approval paths have live proofs and formal work behind them. Others, deviation detection and cross-tenant federation in particular, are built and unit-tested but have no production hours, and in federation's case no real multi-tenant deployment exists yet. I mark those honestly in the repo rather than counting them as done.

It is a research prototype I run on my own gear. I do not install it on client networks. It has rough edges and open findings I'm working through, and it would need independent evaluation before anyone should trust it in production. I'm sharing it because the problem is real and getting worse, not because it's finished.

None of that invalidates the core idea. It just means the honest claim is narrower than "cryptographically verified reality," a phrase I've deliberately stopped using. The accurate version is: a protocol for producing authenticated, request-linked evidence about automated infrastructure operations, under an explicit collector trust model. That's narrower. It's also genuinely useful, and as far as I can tell nobody else has assembled exactly this.

Where it's going

It's open source under Apache 2.0, and it's an individual Internet-Draft at the IETF (an individual submission, not a working-group document or any kind of endorsement), now in its sixth submitted revision (draft-06). That revision narrows the claim to the language above, reconciles the specification with what the code actually implements, and corrects the formal-verification statement to cover exactly what's proven and nothing more.

The direction I'm most interested in isn't more vendors, it's the pattern generalizing. The first non-network domain went through the gate this week, a backup platform, and the same machinery that governs a router governed it unchanged. The bet is that "produce authenticated evidence of what an automated system requested, what a trusted collector attempted, and what came back" is a primitive the whole field is going to need as automation arrives faster than the accountability for it, and I'd rather that layer exist before everyone needs it than after.

If you've hit the fabrication problem yourself, or you want to attack the protocol and tell me where it breaks, that's exactly the kind of contact I'm looking for. The repository is public, the findings list is honest, and the hostile questions are the useful ones.

The code, the security analysis, and the IETF draft are all in the open. I'm a network engineer who got tired of automation I couldn't verify, so I built the thing that produces the evidence. It's rough in the places I've told you it's rough, and solid in the places I've shown you.