Most agent platforms have added identity this year. Almost all of it is a name in a config file. The agent calls itself billing-agent, the log records billing-agent, and the dashboard shows billing-agent. Nobody along that path can refuse the name, because nothing about it can be checked.
Agent Mesh, the trust module in the Agent Governance Toolkit, starts somewhere more awkward. It will not let you create an agent at all unless you name a human who is answerable for it.
Everything below was run against agentmesh 5.0.0, the current agent-governance-toolkit-core release on PyPI, on my own machine, and the source references are pinned to commit 675a556 so the line numbers stay put. The module ships inside agent-governance-toolkit-core, so install that rather than the bare name: the agentmesh project on PyPI is an unrelated package last touched in 2024.
You cannot mint an anonymous agent
The factory method is AgentIdentity.create(), and sponsor is positional and required:
AgentIdentity.create(name="billing-agent", sponsor="ops@example.com", capabilities=["read:ledger"])
Pass something that is not an email address and it raises IdentityError before any key material is generated. There is no flag to skip it and no default. That single constraint is the design decision the rest of the module hangs off: every agent in the mesh traces to a person, and the registry indexes it, so get_by_sponsor() returns every agent a given human brought into existence.
If you have ever tried to answer “who owns this service account” in a mature estate, you know that question is usually unanswerable by construction. Here it is answerable by construction instead.
What you get back is an Ed25519 keypair and a DID derived from it:
did:mesh:a4082046d180d169df25e65edd7232b4
Thirty-two hex characters, one keypair, one sponsor.
The name is only worth what the signature is worth
An identity that cannot be challenged is decoration. Agent Mesh’s handshake is three phases: the caller issues a challenge carrying a 32-byte nonce with a 30 second expiry, the peer signs a payload of challenge_id:nonce:response_nonce:did, and the caller verifies the signature against the public key in the DID.
Signing and verification behave the way you would want, and it takes four lines to convince yourself:
verify correct : True
verify tampered : False
verify wrong key : False
Tamper with one byte of the payload and verification fails. Present a signature from a different agent in the same organisation and it fails. That is the part that makes the name refusable, and refusable is the whole point.
Credentials are separate from identity and short lived. The default TTL is 900 seconds, and capabilities are scoped strings with prefix wildcards, so a credential granting read:* answers True to read:ledger and False to write:ledger. Fifteen minutes is short enough that a leaked bearer token is a nuisance rather than an incident, and long enough that rotation is not in the request path.
The piece I did not expect
Buried in the handshake is an optional parameter called require_freshness. Turn it on and the challenge carries a second nonce, described in the source as an RFC 9334 freshness nonce for Evidence liveness proof. RFC 9334 is the RATS architecture, the attestation document, and this is the same construct attestation uses to prove a claim was produced now rather than replayed from a recording.
It is not decorative. The freshness nonce is concatenated into the signed payload, it is checked on the other side, and a mismatch returns an explicit Freshness nonce mismatch (RFC 9334). It also bypasses the handshake result cache, so a caller that asks for freshness gets a live exchange rather than a memoised verdict from up to fifteen minutes ago.
That matters more than it looks. It means an agent handshake and a hardware attestation can be made to answer the same question, at the same moment, under the same nonce. The identity layer and the attestation layer stop being two separate stories about the same agent.
Where a team should point its scepticism
Two things do not hold up, and both are about the gap between what is declared and what is enforced.
The sponsor is asserted, not verified. Every identity carries sponsor_verified, and it is False on creation. Their own identity specification, section 7.3, says implementations SHOULD verify sponsors, by email confirmation, SSO or a directory, before granting trust above the standard tier. Nothing in the shipped module sets it to True, and no trust decision reads it. So the accountability chain that makes the module interesting is currently a well-formed string that nobody checked.
Revocation is in-process and quiet about failure. The architecture table lists revocation propagation at five seconds or less as a key parameter, next to values that are genuinely enforced. In the code, REVOCATION_PROPAGATION_TARGET = 5 appears exactly once, at its own definition, and is never read. Revocation fires callbacks registered in the same process, inside a try that logs failures at debug level. I registered a callback that raises, revoked a credential, and revoke() returned True. The local credential was correctly invalidated. Whatever was supposed to hear about it was not told, and nothing said so.
Neither makes the module unusable. Both change what you can claim while running it.
What to do on Monday
Run it in the shape it is honest about. Use it for identity, signature verification and short-lived scoped credentials, where it does exactly what it says. Set require_freshness=True on any handshake that gates a real action, because the cached path is the default and a fifteen minute old verdict is not a liveness proof. Treat sponsor_verified as your integration point rather than a feature you have been given, and wire it to whatever your directory already knows. And do not build a containment story on revocation propagation until you have added a callback that fails loudly, because today one that fails silently looks identical to one that worked.
The check I would run first is the smallest one. Create an identity, sign a payload, flip a byte, and confirm verification fails. If that passes, the identity layer is real. Then revoke a credential with a deliberately broken listener and see what your own system does with a True that meant less than it looked.
What would change my mind on the sponsor question is a trust score that refuses to rise while sponsor_verified is False. That is one conditional, it is already specified, and it would turn the most interesting assumption in the module into an enforced one.
Top comments (0)