Skip to content
adeia fence
Sign in

Agents that act
inside a fence

An agent asks Adeia before it spends money or calls an API. Rules you wrote decide: run it, refuse it, or stop and ask you. Every outcome is recorded — including which ones a model decided.

3waiting on you
1ran today
0refused today
WhatStateWhen
DELETE api.cloudflare.com /client/v4/zones/…/dns_records/… waiting on you 17 Aug, 05:25
POST api.github.com /repos/NayanVangala/adeia/issues A model judged this low risk and let it run ran 17 Aug, 05:25
GET api.github.com /repos/NayanVangala/adeia ran 17 Aug, 05:24
The problem

An agent with an API key has no idea what it is about to cost you

DELETE /zones/{id} and GET /zones differ by one word, and one of them ends a website. By the time it has run there is nothing left to inspect — the thing you would inspect is gone.

Without a fence

  • A retry loop spends the budget in ninety seconds.
  • A confused agent deletes the wrong record, then reports success.
  • Injected text points a call at a host you never approved.
  • Nothing is written down, so nobody can say what happened.
Budget left $0.00

9 calls · budget gone, and nobody was asked

With one

  • Over the limit, it stops and asks a person.
  • Off the allowlist, it is refused before any request goes out.
  • Private addresses are blocked at three separate layers.
  • Every decision is in an append-only trail, with its reason.
Budget left $2,000.00

1 call · held at $50.00 — waiting on a person

How it works

Three layers, and each one assumes the last can be lied to

Each one assumes the one before it was lied to. That is why there are three and not one.

01 — Schema

Refuse before you think

https only. No private, loopback or link-local address. A body small enough that a person can actually read it in an approval email. These checks are cheap and they run first, so the expensive ones never see the obvious cases.

It is a fast reject, not the real defence — an attacker-chosen hostname can resolve to any of those addresses without saying so. That is what layer three is for.

what never gets past the shape
{ "url": "http://api.example.com/zones" }
400 url must use https

{ "url": "https://169.254.169.254/latest/meta-data" }
400 url must not point at a private address

{ "url": "https://api.example.com/zones/9" }
ok — on to layer two
See it

Eleven lines, and a delete that waits for you

import { AdeiaClient } from "@adeia/sdk";

const adeia = new AdeiaClient({ apiKey });

// The agent holds no credential of its own.
// It can only ask.
const action = await adeia.requestAction({
  type: "http",
  params: {
    method: "DELETE",
    url: "https://api.cloudflare.com/…/dns_records/…",
  },
});

action.status; // "pending_approval"
the agent reads the repoGET api.github.com/repos/…✓  executed the agent files an issuePOST api.github.com/…/issues⚖  model: creates an ordinary issue,   easily noticed and reversible✓  executed the agent tries to delete a DNS recordDELETE api.cloudflare.com/…⏸  waiting on a human Nothing has been sent.

Adeia · approvals

Approval needed: $500.00 to acct_contractor

Approval needed

$500.00

to acct_contractor

amount 50000 exceeds per-action limit 5000

Review and decide

The request has not been sent. Opening this link does not approve anything — you decide on the page itself. The link can be used once.

what lands on the phone of whoever you named

Approval needed

DELETE

api.cloudflare.com

/client/v4/zones/9f2c…/dns_records/41b8…

method DELETE is on the approval list — it stops for a person every time, whatever any model concludes

The request has not been sent. Nothing downstream has been contacted. This link can be used once.

Agent asks Schema https, not private Policy deny rules first Model inside the band You approve or refuse Adapter re-resolves, then runs

An agent you cannot trust is still worth having — as long as it cannot act alone.

That is the whole design. Everything above is how it is enforced, and everything below is what it costs.

The model's part

A model decides, but only inside a band you opened

Stopping every write for a human buries the approval that matters under the ones that do not. So a classifier judges the ordinary ones — and is never consulted about a call the fence already refused.

It cannot open a door you shut

Every deny rule runs first. By the time a model can be asked, the host allowlist, the private-address check and the daily cap have already answered. Injected text can win inside a band you opened. It cannot open one.

It never reads the excuse

The classifier sees the method, the URL and the body — never the agent's description of what it is doing. That sentence is written by the party under evaluation and exists to persuade. The endpoint cannot be talked around.

Failing means asking

Timeout, malformed answer, dead key, a verdict nobody recognises — all of them send the action to a person. A classifier that cannot answer must never mean yes.

Deny list — you wrote it

DELETE

no model is asked

Refused outright, with the rule that refused it. Nothing downstream is contacted and no approval is sent.

The band you opened

POSTPATCH

a model decides

Judged low risk, it runs and the trail says a model let it through. Anything else — and every failure — goes up a band.

the only reach an injected instruction has

Approval list — you wrote it

PUT

always a person

Stops for a human forever, whatever any model concludes. That is a floor, and it is yours.

Every part is interesting for what it refuses to do

Approvals

Single-use, expiring, and only the hash is stored. Deciding is never a GET — mail scanners and link unfurlers issue those on their own.

Audit log

Append-only, secrets stripped at write time, and the write cannot throw. It records whether a person or a model decided — those must never read the same.

Risk classifier

Judges the ordinary writes so the dangerous ones stand out. Binary, never a score — a score becomes a threshold somebody guesses.

The SDK

Three methods, and no retry. A blind retry against a payment endpoint is how double charges happen — the idempotency key makes a retry safe, but the decision stays the caller's.

Adapters

The seam where something actually happens. The ledger adapter records a payment and stops where settlement would begin — it does not imitate a processor, because a convincing fake is worse than an obvious gap. The server says NO PAYMENT PROCESSOR ATTACHED on every boot, out loud.

FAQ

The questions that actually come up

Is Adeia an agent?

No. It has no model, no browser and no hands. Something else decides what to do; Adeia decides whether that is allowed and records the answer. It is the seatbelt, not the car.

Does it move real money?

Not yet. The ledger adapter records a payment and stops where settlement would begin — no processor identifier, settled: false. Attaching a real processor is one adapter, and the fence in front of it does not change.

What stops a prompt-injected agent?

Ordering. Every deny rule runs before anything a model or a human sees, so injected text is arguing about a decision that has already been made. The classifier never reads the agent's own description of itself, which is the surface that argument would need.

What happens if the classifier is down?

The action goes to a person. Timeout, malformed answer, missing key — every failure path means ask. A missing key must never quietly widen what an agent may do on its own.

Can I see what happened afterwards?

Every action carries an ordered trail: what was requested, which rule answered, who or what decided, and what the far end said. Credentials are stripped as it is written, not as it is read.

Give your agent hands,
not your credentials

Sign in with GitHub and you get a project, a policy and a key in about ten seconds.

push it — it bends, it warms, it holds