Silent Agents docs
Token $SILENT, at silentagents.xyz
Silent Agents is a mixnet for the traffic between AI agents and the model providers they call. It has one job: stop anyone watching the network, the provider included, from tying a request to the person behind it through addresses or timing.
Three things do this together:
- Three relays per route. Each request passes through three relays run by different operators in different regions. A relay only knows the hop before it and the hop after it.
- A fixed rate. Every client and every relay sends packets of a single size at a fixed rate, around the clock. Real packets replace cover packets, so the stream never changes shape.
- Relays with stake. Relays lock $SILENT on Ethereum, are paid daily for holding the rate, and are slashed when they are caught skipping cover traffic.
The starting point is the privacy wrapper Vitalik Buterin described for his own AI agent on October 16, 2026. Read his post. Silent Agents is independent and is not affiliated with him or the Ethereum Foundation.
Where it fits
His wrapper has five layers. This network takes over the two that deal with the network itself and leaves the other three as they are.
| Layer | What it hides | Handled by |
|---|---|---|
| Local prompt rewrite | How you write | A local model on your machine, unchanged |
| Sandbox | Anything the agent might leak by itself | bubblewrap or a microVM, unchanged |
| zkAPI payments | Who paid for the request | zkAPI, unchanged |
| Cover traffic | When you are active | Silent Agents, as constant-rate traffic in place of random extras |
| Tor circuit per chat | Your IP address | Silent Agents, as a three-relay mixnet built for this traffic |
Random cover traffic on top of Tor still leaves bursts an active attacker can spot. Handling both jobs inside one network built for a constant rate closes the timing gap he described.
Architecture
There are four parts.
- The client runs on the user's machine next to the agent and looks like an OpenAI-compatible endpoint on localhost. It picks routes, seals packets and keeps the constant-rate stream going.
- Relays receive packets, remove one layer, wait a short random time, reorder and forward. Each relay also keeps its own outgoing rate constant by adding cover packets.
- The exit is the third relay on a route. It rebuilds the request from its packets, sends it to the provider over TLS, then returns the answer the same way in fixed-size packets.
- Ethereum holds the relay list, the stakes, the daily reward pools and the challenge votes. Nothing about any single request is ever written onchain.
agent ── localhost ──▶ client ══▶ relay 1 ══▶ relay 2 ══▶ relay 3 ── TLS ──▶ provider
│ (eu) (asia) (na)
└─ reads the relay list and keys from Ethereum
══ fixed-size packets at a constant rate, three layers of encryption
Packets and timing
Every packet on the network has the same size. A short request might fit in one packet and a long answer might take many, but from the outside they all look identical. Cover packets are encrypted exactly like real ones, so a relay in the middle of a route cannot tell them apart either. Only the client that made a packet knows whether it was real.
Rate
The minimum rate for a relay is one packet per second, every second of the day. That is 86,400 packets per day, and it is the figure the rewards contract checks. Busier relays send more, but never less than the minimum, and their rate never follows demand.
Mixing delay
Each relay holds a packet for a short random time before passing it on, and sends packets out in a different order from the one they arrived in. That breaks the link between a packet going in and a packet coming out, even for someone watching a single relay closely.
What an observer sees
Someone watching your connection sees a flat stream to one relay, all day. Someone watching the provider sees requests arriving from many exit relays. There is no burst on your side to line up with an arrival on theirs.
Choosing a route
The client reads every active relay from RelayRegistry: its endpoint hash, its region and its stake. It then picks three relays by these rules:
- All three must be in different regions.
- Within that, a relay's chance of being picked grows with its stake, up to the 250,000 cap.
- Every new conversation gets a new route, so conversations cannot be tied together through the relays they used.
The endpoint hash onchain lets the client confirm that the onion address and key a relay announces are the ones it registered. If they do not match, the client skips that relay.
Sending traffic
Point your agent at the local client instead of the provider. Anything that talks to an OpenAI-compatible API works with no other changes.
OPENAI_BASE_URL=http://127.0.0.1:8462/v1
OPENAI_API_KEY=<your zkAPI credential>
The client keeps the cover stream running whether or not the agent is busy. Expect a small, steady amount of bandwidth while it is on, and an extra second or two of delay at the start of a conversation while the route is built.
Traffic fees are paid in $SILENT and collected by FeeSplitter. Pair this with zkAPI credentials so the payment itself cannot be traced back to you.
Running a relay
What you need
- A server that can stay online all day with steady bandwidth.
- An onion address and an ed25519 public key for the relay.
- At least 50,000 SILENT in the wallet you will join from.
Joining
- Run the relay for a day first and check it never drops below one packet per second.
- Hash the onion address and public key together. The join form on the home page does this for you with SHA-256.
- Let the registry take your stake:
approve(registry, amount)on the token. - Call
join(endpointHash, region, amount)on the registry. It returnstrueonce you are in.
Region codes
| Code | Region | Code | Region |
|---|---|---|---|
| 1 | North America | 5 | Middle East |
| 2 | South America | 6 | Asia |
| 3 | Europe | 7 | Oceania |
| 4 | Africa |
Leaving
Call beginExit(). Your relay stops being picked at once and your stake stops counting toward rewards. After 14 days, call finishExit() to take the stake back. Your stake can still be slashed during those 14 days, so a relay cannot misbehave and walk away in the same move. You can call cancelExit() at any point before you finish.
If a slash leaves you below 50,000, the registry starts your exit for you.
Days and rewards
Rewards run in days, counted from the moment the rewards contract was wired. currentEpoch() returns today's day number.
| Day | What happens |
|---|---|
| N | Relays carry traffic. Fees arrive and are added to day N's pool. |
| N+1 | Each relay calls report(N, packets). A report under 86,400 is refused. |
| N+1 to N+2 | Anyone can challenge a day N report. Votes run for one day. |
| N+3 onward | Relays call claim(N) to collect their share. |
A relay's share of day N's pool is its weight divided by the combined weight of every relay that reported. Its weight is its stake at the time of reporting, capped at 250,000. A relay that missed its report window gets nothing for that day.
If nobody earned anything on a day, anyone can call rollover(N) after day N+3 to move that pool into the current day.
Challenges
A relay that quietly drops its cover traffic when nobody is using it saves money and leaks the timing of everyone routed through it. Challenges are how the network catches that.
- Open. During day N+1, anyone approves 1,000 SILENT to the court and calls
open(relay, N, evidenceHash). The evidence is published offchain and the hash pins it. The relay's day N claim is frozen. - Vote. For one day, active relays call
vote(id, support). Each vote counts with the voter's full stake. - Resolve. Once voting closes, anyone calls
resolve(id).
| Outcome | Rule | Result |
|---|---|---|
| Upheld | Votes cast reach 10% of active stake and yes beats no | Day N reward wiped. 20% of stake slashed, half to the challenger, half burned. Bond returned. |
| Rejected | Quorum reached and no is equal to or above yes | Bond goes to the accused relay. Claim unfrozen. |
| No quorum | Votes cast under 10% of active stake | Bond returned. Claim unfrozen. |
What counts as evidence
The most useful evidence is a capture from the relay's neighbours: if its peers saw long gaps in what it sent them, it was not holding the rate it reported. Voting relays can check that against their own records, since they were the ones receiving its packets.
Fees and burn
Traffic fees go to FeeSplitter. Anyone can call split(), which sends 85% of the balance to the rewards contract for the current day and burns the other 15%. The split is fixed in the contract and there is no owner who could change it.
Half of every slashed stake is burned as well, by the registry.
$SILENT
| Name | Silent Agents |
|---|---|
| Symbol | SILENT |
| Ticker | $SILENT |
| Website | silentagents.xyz |
| Decimals | 18 |
| Supply | 1,000,000, minted once at deployment |
| Network | Ethereum mainnet |
| Address | LAUNCHING SOON |
| Extras | EIP-2612 permit, burn, burnFrom, sendMany |
$SILENT is the token of Silent Agents. Relays stake it to join and earn it each day for holding the rate, and users pay their traffic fees in it. No owner, no mint after launch, no transfer fee, no pause, no blocklist.
Contracts and wiring
None of the contracts take constructor arguments. Each is deployed bare and then connected once by the deployer with wire. After wiring, the deployer has no further powers. No function in any of the contracts reverts: anything that cannot be done returns false or 0.
1. deploy Token
2. deploy RelayRegistry
DayRewards
ChallengeCourt
FeeSplitter
3. registry.wire(token, court)
rewards.wire(token, registry, court)
court.wire(token, registry, rewards)
splitter.wire(token, rewards)
| Contract | Job | Address |
|---|---|---|
Token | The $SILENT token | LAUNCHING SOON |
RelayRegistry | Relays, stake, exits, slashing | LAUNCHING SOON |
DayRewards | Daily pools, reports, claims | LAUNCHING SOON |
ChallengeCourt | Challenges and votes | LAUNCHING SOON |
FeeSplitter | 85/15 fee split | LAUNCHING SOON |
Contract reference
RelayRegistry
| Function | Who | Does |
|---|---|---|
join(bytes32 endpoint, uint16 region, uint256 amount) | anyone | Locks at least 50,000 and registers the relay. Needs approval first. |
addStake(uint256 amount) | relay | Locks more stake. |
beginExit() | relay | Leaves the active set and starts the 14 day wait. |
cancelExit() | relay | Returns before the wait ends, if stake is still at least 50,000. |
finishExit() | relay | Pays the stake back after the wait. |
slash(address relay, uint256 bps, address challenger) | court only | Takes up to 50% of stake, half to the challenger, half burned. |
isActive(address), stakeOf(address) | view | Relay status and stake. |
relays(address), relayAt(uint256) | view | The full relay record, and relays in join order starting at 1. |
activeCount(), totalActiveStake() | view | Size of the active set. |
DayRewards
| Function | Who | Does |
|---|---|---|
fund(uint256 amount) | anyone | Adds tokens to today's pool. Needs approval. |
sync() | anyone | Adds tokens sent straight to the contract to today's pool. |
report(uint256 epoch, uint256 packets) | active relay | Reports the previous day. Only during the next day, at least 86,400 packets. |
claim(uint256 epoch) | relay | Pays out its share from day N+3 on. Returns the amount. |
rollover(uint256 epoch) | anyone | Moves an unclaimed pool with no earners into today. |
hold, forfeit | court only | Freezes or wipes a relay's share for one day. |
currentEpoch(), claimable(epoch, relay), pool(epoch), weightOf(epoch, relay) | view | Day number, payout, pool size, weight. |
ChallengeCourt
| Function | Who | Does |
|---|---|---|
open(address relay, uint256 epoch, bytes32 evidence) | anyone | Posts the 1,000 bond and opens a case. Returns the case id, or 0. |
vote(uint256 id, bool support) | active relay | Votes once per case with its stake. |
resolve(uint256 id) | anyone | Settles the case after voting closes. |
cases(uint256), caseFor(epoch, relay) | view | Case details, and the case id for a relay and day. |
FeeSplitter
| Function | Who | Does |
|---|---|---|
split() | anyone | Sends 85% of its balance to rewards and burns 15%. |
totalToRelays(), totalBurned() | view | Running totals. |
Reading the registry
const reg = new ethers.Contract(REGISTRY, [
"function relayCount() view returns (uint256)",
"function relayAt(uint256) view returns (address)",
"function relays(address) view returns (uint256 stake, uint64 joinedAt, uint64 exitAt, uint16 region, bytes32 endpoint)",
"function isActive(address) view returns (bool)"
], provider);
const n = await reg.relayCount();
for (let i = 1n; i <= n; i++) {
const addr = await reg.relayAt(i);
if (await reg.isActive(addr)) console.log(addr, await reg.relays(addr));
}
Parameters
Every one of these is a constant. None can change after deployment.
| Constant | Value | Contract |
|---|---|---|
MIN_STAKE | 50,000 SILENT | Registry |
EXIT_DELAY | 14 days | Registry |
MAX_SLASH_BPS | 5,000 (50%) | Registry |
EPOCH | 1 day | Rewards |
MIN_PACKETS | 86,400 per day | Rewards |
STAKE_CAP | 250,000 SILENT | Rewards |
BOND | 1,000 SILENT | Court |
VOTE_TIME | 1 day | Court |
QUORUM_BPS | 1,000 (10% of active stake) | Court |
SLASH_BPS | 2,000 (20%) | Court |
TO_RELAYS_BPS | 8,500 (85%) | Fee splitter |
Threat model
Covered
- A provider that wants to know which IP address sent a request.
- A provider working with the first relay on your route to match timing. This is the attack Vitalik Buterin raised against Tor, and a constant rate is the answer to it.
- Someone watching your home connection who wants to know when your agents are working.
- Tying two of your conversations together through the network, since each one gets a fresh route.
Outside its scope
- The content of a request. The provider has to read it to answer it.
- One person running all three relays on your route. Region rules and stake weighting make this unlikely, not impossible.
- An agent that leaks things by itself. That is the sandbox layer's job.
- Payment tracing if you pay fees straight from your main wallet. Use zkAPI-style credits.
FAQ
Is this Vitalik Buterin's project?
No. It builds the missing piece he described in his post. It is not affiliated with him or the Ethereum Foundation.
Why not use Tor on its own?
Tor sends traffic when you send traffic. That makes it quick for browsing and weak here, because the bursts can be lined up. Silent Agents takes longer to start but sends the same amount at all times.
Does the client use a lot of bandwidth?
It uses a small, steady amount the whole time it is on, instead of nothing most of the time and a lot in bursts. That steady amount is what the privacy costs.
Can a relay fake its packet count?
It can write any number into its report, but its neighbours saw what it actually sent. That is what the challenge court is for, and a proven lie costs 20% of its stake.
Who controls the contracts?
Nobody. The deployer wires them once and has no powers after that. Every parameter is a constant.


