Ensemble: an ENS-bound agent with full onchain read/write and an authority gate

I’ve been experimenting with ensemble, a research prototype that lets end users manage ENS names through natural language.

The first iteration was human-operated: you connect your wallet, then search, register, and edit records on a name — all through the NLI — and read your balances. Every write was an ENS operation, and every signature came from your browser wallet. The agent had no hands of its own.

The current iteration gives the agent its own:

  • agentic identity
  • keys
  • execution environment

so it can act onchain — transfer, swap, bridge, trade perpetuals and prediction markets, lend, and borrow — all headless, all through natural language, with no human signing each step. It can even rewrite its own ENS records, self-signed by its own wallet.

Two things made that leap possible: TEE server wallets (keys that live in a secure enclave and sign headless, with no human in the loop) and Adapter8004 (composing ERC-8004 + ENSIP-25 + ENSIP-26 into a single provisioning flow).

I integrated both into ensemble — and because an agent that can take leverage and place bets on its own needs a brake, I put a new primitive in front of every fund-moving action: an authority gate I’m developing at Steg.

It reads the agent’s permission straight from its ENS records, so the operator can revoke it at any time — denying the agent’s very next action without ever touching its keys. And because the agent owns its own name, it can even flip that switch on itself.

Browse a live agent’s records: uitest10.steg.eth on ENSWhois.

How it works

Authority lives as an ENS record

The whole point is to separate authority from the key. Today, “revoking” an agent means rotating its key or draining its wallet — blunt, slow, and tangled up with the signing material itself. So instead, the agent’s permission lives on its ENS name, in three records:

Record Meaning
auth.credential[primary] who may sign (the agent’s wallet)
auth.capability[primary] what is allowed (a policy; e.g. erc20.transfer)
auth.revocation[primary] on/off{"revoked": false | true}

Before any fund-moving action, a relying party reads these fresh from L1 and decides. No key access, no callback to the agent, no shared secret — just a public read of public records.

Flip auth.revocation to {"revoked":true} at ENS and the agent’s next action is denied. Fail-closed.

The operator controls what the agent may do, independently of the signing key
and because the agent owns its own name, it can flip that switch on itself. Pause is just an ENS record edit (never fund-moving, so never blocked), so the agent can always un-pause itself too.

Identity via Adapter8004

Provisioning an agent composes three standards into one coherent identity:

  • ERC-8004 — the agent gets a registered on-chain agent ID.
  • ENSIP-26 — identity records (display name, description, avatar, skills, trust models).
  • ENSIP-25 — a registration claim binding the agent id back to the name.

The contribution here isn’t any one standard — it’s that they compose into a working agent identity, written in a single provisioning flow.

The 9-step flow, from an email. A user picks <label>.steg.eth and “signs in with email” (the email is just a queue label — not auth). The operator co-signs the subname mint (on a hardware wallet); an on-chain watcher then kicks off a background job that runs:

wallet_create → fund → records → bind → identity → agent_uri → ensip25 → reverse → transfer
  • records writes the auth.*
  • authority records; bind does the ERC-8004 registration;
  • identity/ensip25 write the ENSIP-26/25 data; and the final transfer hands the name to the agent’s own wallet — a self-sovereign handoff.

The operator’s only role was minting; it does not hold the name at rest.

The gate. Every fund-moving tool — a transfer, swap, or bridge; a perps or prediction-market order; an Aave supply or borrow; a raw tx — first calls a keyless verifier: the agent TEE-signs a canonical probe, the verifier reads the agent’s auth.* from L1, and returns allow/deny. Revoked → the action is refused and nothing broadcasts.

The trio. Three deployed services make this run: a Frontend (the NLI + an on-chain watcher that fires provisioning), a Brain (the orchestrator — it runs the 9-step job, hosts the agent, and enforces the gate, all while driving a headless TEE wallet), and a Worker (the public agent-identity endpoint + the keyless verifier the gate calls, which reads auth.* straight from L1). All open source.

The lynchpin: a headless TEE wallet

None of this matters if the keys aren’t safe — or if “safe keys” means a human has to babysit a signer. The agent’s wallet lives in a TEE, and the cloud only orchestrates it. Three things I want to call out, because they’re the hard part:

  • Keys never leave the TEE, yet the wallet runs unattended. It signs real mainnet transactions with no human in the loop and no exposed private key.
  • It survives redeploys. The wallet’s state lives on a persistent volume, so the agent’s identity and keys aren’t ephemeral — you can ship new code without losing the wallet.
  • It’s fully CLI/script-driven and reproducible. The entire wallet lifecycle is a command-line agent-wallet tool plus a handful of viem scripts — no GUI, no clicking.

So the agent is identity + authority + agency: it has a name, an operator-revocable authority layer, and it can actually act — read balances and market data, transfer, swap, bridge, trade perpetuals and prediction markets, lend and borrow on Aave, sign typed data, and rewrite its own ENS records (when allowed) — all headless, all driven through natural language.

Proof: live on mainnet

This isn’t a testnet toy. The agent acted on its own keys on mainnet — no human
in the loop, no browser wallet to connect:

  • Self-sovereign agent: uitest10.steg.eth — ERC-8004 agent id
    35417, the name’s owner equals the agent’s own wallet address (0x36A4Ca97…), reverse record set.
  • The agent writing its own ENS record via natural language: it self-signed a setText
    setting urlhttps://uitest10.steg.eth.limo, from its own wallet (0xa477cea8…, status: success). No operator, no external wallet to connect — the agent edits its own records.
  • The authority gate, proven: the agent self-revoked — a setText writing auth.revocation[primary]{"revoked":true}, again from its own wallet (0x49cf20e8…, status: success). The same gate sits in front of every fund-moving tool — swap, bridge,
    perps, etc.

Check out the repo

The whole thing is open source — clone it, read it: https://github.com/estmcmxci/steg-ens-agent

A caveat on standing up your own, though: this isn’t fully self-serve yet.

  • It depends on a gated TEE agent-wallet program. The agent’s keys live in a secure enclave via a TEE-based agent wallet that isn’t publicly open — you’d need access to that program to reproduce the headless signing.
  • Provisioning is bound to my operator account. The wallet session is tied to my email and the operator co-signs every subname mint, so I can’t provision agents for arbitrary end users. You can interact with a live agent, but the actual mint currently routes through me.
  • You’d also need your own operator key and a parent ENS name to mint subnames under, plus the Frontend / Brain / Worker trio deployed.

So treat this as a showcase of what the auth.* + TEE-wallet pattern makes possible — not a one-click template, yet.

If you’re building agent infrastructure on ENS, I’d love to compare notes, especially on whether the auth.* pattern is worth converging on.

2 Likes

Looking forward to seeing the demo!

1 Like