BrainMCPAgentRegistry
BrainMCPAgentRegistry registers agents as a compact on-chain record: agentId, agentAddress, tenantId, scopeHash, and behaviorHash, each registration authorized by an EIP-712 signature from a tenant-allowlisted signer. On-chain reputation is planned, not yet implemented. See RFC 0001.
Network
Base L2
Solidity
0.8.x
Pattern
Immutable. No upgrade path in MVP; changes ship as audited redeploys
Standard
EIP-712 signed registration (ERC-8004 reputation planned. See RFC 0001)
Interface
This is the deployed MVP surface. Each lifecycle call carries an EIP-712 signature from a signer the tenant has allowlisted.
contract BrainMCPAgentRegistry {
struct AgentRegistration {
bytes32 agentId;
address agentAddress;
bytes32 tenantId;
bytes32 scopeHash;
bytes32 behaviorHash; // keccak256(model_id, model_version, prompt_template_hash, tool_manifest_hash)
uint256 registeredAt;
uint256 revokedAt; // 0 while active
}
event AgentRegistered(
bytes32 indexed agentId,
address indexed agentAddress,
bytes32 indexed tenantId,
bytes32 scopeHash,
bytes32 behaviorHash
);
event AgentRevoked(bytes32 indexed agentId, bytes32 indexed tenantId);
event AgentBehaviorUpdated(bytes32 indexed agentId, bytes32 indexed tenantId, bytes32 behaviorHash);
event TenantSignerSet(bytes32 indexed tenantId, address indexed signer, bool allowed);
// A tenant must configure ≥1 allowlisted signer before any agent can be
// registered for it; the first signer is bootstrapped by initialAdmin.
function setTenantSigner(
bytes32 tenantId, address signer, bool allowed,
address authSigner, bytes calldata signature
) external;
function registerAgent(
bytes32 agentId, address agentAddress, bytes32 tenantId,
bytes32 scopeHash, bytes32 behaviorHash, bytes calldata tenantSignature
) external;
function updateBehaviorHash(
bytes32 agentId, bytes32 behaviorHash, bytes calldata tenantSignature
) external;
function revokeAgent(bytes32 agentId, bytes calldata tenantSignature) external;
// Views
function isAuthorized(bytes32 agentId, bytes32 tenantId) external view returns (bool);
function getAgent(bytes32 agentId) external view returns (AgentRegistration memory);
function isTenantSigner(bytes32 tenantId, address a) external view returns (bool);
}The fuller ERC-8004 identity record (identity Merkle root, mcpEndpoint, capability-hash array) and per-capability scope grants are the planned target. See RFC 0001. Reputation is out of scope here and lives in BrainReputationRegistry.
Agent Record
The deployed AgentRegistration struct stores exactly these fields:
agentId
Global agent identifier. The registry's primary key
agentAddress
The agent's on-chain address
tenantId
The tenant this registration is bound to
scopeHash
Hash of the agent's granted scope set; the agent's JWT scope_hash must equal this
behaviorHash
keccak256(model_id, model_version, prompt_template_hash, tool_manifest_hash). Pins model/prompt/tools (§6 check 1.5)
registeredAt
Block timestamp at registration
revokedAt
Block timestamp at revocation; 0 while active (isAuthorized reads this)
The fuller record below is the planned target. See RFC 0001. None of these fields exist in the deployed struct today:
identityRoot
ERC-8004 identity Merkle root (planned. RFC 0001)
mcpEndpoint
URL where RobotMoney can reach the agent over MCP
capabilities[]
Hashes of capability identifiers (e.g., keccak256("pay_invoice"))
reputationRoot
Reputation pointer. Now a separate contract, BrainReputationRegistry
Registration
Agents cannot self-register. A registration is authorized by a tenant-allowlisted signer who signs this EIP-712 message:
The contract recovers the signer, rejects it unless it is on the tenant's allowlist (isTenantSigner), stores the record, and emits AgentRegistered.
Per-Tenant Scoping
A tenant must first configure at least one allowlisted signer with setTenantSigner. The very first signer for a tenant is bootstrapped by initialAdmin, after which signers manage each other. Only an allowlisted signer can register, re-attest, or revoke an agent for that tenant.
Each registration binds the agent to exactly one tenantId plus a single scopeHash that encodes the whole granted scope set; the agent's JWT scope_hash must equal it. The predicate RobotMoney consults before granting a session key is:
Finer-grained, per-capability scope grants (grantScope/isScoped) are the planned target. See RFC 0001. The MVP collapses scope into one signed scopeHash.
Deactivation
Revocation (signed by a tenant signer) sets revokedAt to the current block timestamp. isAuthorized then returns false, and RobotMoney refuses to grant or use session keys for the agent. Revocation is permanent for that agentId; promoting a new model/prompt/tools instead uses updateBehaviorHash.
Behavior updates and revocations are replay-protected. Their EIP-712 payloads include per-agent nonces (behaviorNonce(agentId) and revocationNonce(agentId)) that increment on accepted updates, so a previously observed signature cannot later roll an agent back to an older behavior hash or replay a stale lifecycle action.
Reputation lives in a separate contract
Reputation is not stored in this registry. It lives in BrainReputationRegistry. An ERC-8004-style per-agent pointer / Merkle root (RFC 0001, UNAUDITED testnet). This registry's deployed AgentRegistration struct stores only agentId, agentAddress, tenantId, scopeHash, and behaviorHash. There is no reputationRoot field here. Policy reads the reputation pointer as a tighten-only threshold input; it is never a money gate or a §6 precondition.
Discovery
On-chain, the deployed registry answers per-id queries. getAgent(agentId) and isAuthorized(agentId, tenantId). Richer discovery (by capability, by reputation standing) is resolved off-chain today; on-chain capability indexing is the planned target. See RFC 0001.
Authorization
isAuthorized(agentId, tenantId). Registered, not revoked, tenant matches
Capability filter
Active agents declaring a capability (resolved off-chain; on-chain index planned. RFC 0001)
Reputation threshold
Resolved via BrainReputationRegistry (testnet); Policy maps the pointer to a minimum score off-chain
ERC-8004 Alignment (RFC 0001)
Identity record
BrainMCPAgentRegistry. agentId / tenantId / scopeHash / behaviorHash
Reputation root
BrainReputationRegistry.scoreRoot per agent (testnet)
Validation records
Committed off-chain under the reputation scoreRoot (testnet)
Discovery
View functions across both registries
behaviorHash Pinning
registerAgent now also takes a behaviorHash = keccak256(model_id, model_version, prompt_template_hash, tool_manifest_hash), emitted on AgentRegistered and stored on the registration. This freezes the agent's behavior at a known version. Enterprise security teams get a "the agent cannot silently change its model/prompt/tools" guarantee.
The §6 gate adds check 1.5: the runtime
behaviorHashmust equal the registered value, or the action is rejected regardless of every other signal.Promotion to a new behavior requires fresh tenant re-attestation via
updateBehaviorHash(agentId, behaviorHash, tenantSignature)(EIP-712 signed by a tenant signer) with the currentbehaviorNonce(agentId). The on-chain analogue of re-signing the ScopeAttestation.
What's Next
Last updated
