Describe an agent in a sentence. Deploy it in about a minute.
Limen turns a sentence into a working agent on Stellar — its own smart account, its own signing key, and a spending boundary installed on chain before it is allowed to run. What that agent may then spend on your behalf is decided by the account itself: not by us, and not by the model that drafted it.
Not a key in an environment variable, and not a notification asking you to approve something.
Building one on testnet submits real transactions, funded by friendbot — no real money anywhere. Deploying a bounded agent runs today; holding a conversation with one does not yet, and scene 04 says which half is which.
77 transactions recorded on Stellar testnet, across 9 smart accounts and 10 installed context rules, as of 2026-08-15. 1186 tests over 58 files. Every hash on this page is checkable in a block explorer.
01Why this is hard
An agent that can pay for things is an agent holding your money.
Which leaves two options, and both of them are bad. Give it a key and hope. Or approve every transaction yourself, and not really have an agent.
Hand over a key
The agent can do anything you can do. Every transfer, every contract, every amount, for as long as the key exists. The limit is your trust in a model’s judgement and in the code around it, and neither of those is a limit.
Approve every call
Nothing moves without you. Which is safe, and which means the agent is a suggestion engine with extra steps — you are still the one doing the work, only now you are doing it at the speed of notifications.
There is nothing between them. That gap is why most agentic money today is either a demo on testnet or a key in an environment variable — and it is the gap this platform is built in. An agent on Limen holds a key that can only do one narrow thing, and the account it holds the key to is what stops it doing anything else.
02Build
Say what it should do. Read back what it may do.
You write one sentence. Limen drafts an agent from it — the job, the token, a ceiling per payment, a window — and shows you the limits it intends to install before anything is signed.
Describe
“Pay my contractor up to 50 XLM a week.” One sentence, in the box, in your own words.
Review
The draft comes back as limits rather than as prose: which token, how much, how often, until when. Every field is editable, and nothing is installed until you say so.
Deploy
You sign once. The account, the agent’s key and the boundary all go on chain together, and the agent does not exist until the boundary does.
The order matters more than the speed. The boundary is installed in the same flow that creates the agent, not bolted on afterwards as a setting somebody might skip — so there is no window in which the agent exists and is unbounded.
03Deploy
Four writes, one after another, and then it is live.
A smart account of its own, a fee account for the agent's key, the key registered as a signer, and the boundary installed on that signer. Below is a deployment this screen actually performed on testnet.
- Smart account created
- CBFLEN…LMYM
- Closed on ledgers
- 4273973-4273978
- Account deployed
- 0ce46f61…ae1e
- Agent key registered
- c72786df…4789
- Boundary installed
- d121671f…9c6b
- Ceiling installed
- 0.1 per ≈ 1 day, derived at ledger 4,273,971
How this was producedapps/web/e2e/agent-builder.spec.ts, driving a real Chromium with a CDP virtual authenticator against a production `next start`, live Neon and live Upstash. `page.route` appears nowhere in the file: nothing below the browser is stubbed. A Playwright spec drove that browser and no person clicked through it, so this is evidence that the path runs end to end, not that somebody found it easy. ANTHROPIC_API_KEY was deliberately unset, so /api/agents/generate degraded to an empty draft carrying the description. Asserted as a working path — generated=false and every draft field empty — rather than skipped. The form a person fills in is this spec's subject, and a run where a model answered would be exercising something else.
04Talk
Then you talk to it — and the account still decides.
You ask the agent to pay someone. It builds the transaction and signs with its own key, and the account checks the boundary before anything moves. Your approval is not in that loop, because you already gave it once, when you installed the boundary.
Nothing in that sentence asks you to trust the model. The agent can propose anything it likes — a larger amount, a different token, a contract you never mentioned — and the account refuses it in the same way it would refuse a stranger, because the check is not a policy the agent is asked to observe. It is a policy the agent is unable to exceed.
What you do
Say what you want done. You approve nothing per transaction and sign nothing per transaction — you already signed the only thing that mattered, which was the boundary.
What it cannot do
Exceed the ceiling, touch another token, call another contract, or outlive the window. Each of those is refused on a ledger, and the three scenes below are the hashes.
What runs todayThe boundary, the deployment and every refusal below are live on testnet and have hashes. The conversation does not: an agent that acts while your browser is closed needs a key held somewhere other than your browser, and today the agent key this flow creates is generated in your browser and stays in it. Until that changes, this scene describes the loop the boundary was built for rather than one you can run.
05Why you can trust it
The limit is not a setting. It is a rule on the account, and the account is what enforces it.
Not by this repository's evaluator, and not by a server that could be down or persuaded. The boundary is a context rule and a policy contract on a Soroban smart account, checked inside __check_auth before a token moves.
The agent’s key is registered as a signer that may only act under that rule. When it signs, the account’s own code runs first and either authorizes the call or traps. If Limen disappeared tomorrow, the boundary would still be there and would still be enforced, because the thing enforcing it is the account.
And where the rule comes from
A boundary you have to describe is a boundary you can also mis-describe — correctly, in advance, in the abstract, about money. So Limen would rather read one off a transaction that already happened. Perform the flow once and it derives the narrowest rule that permits it: the contracts it touched, the functions it invoked, the outflow that actually occurred, and a window. Nothing wider. The derived cap is the outflow that happened, not a round number near it — and that equality is the claim, not a coincidence. Below is a boundary derived from a transaction observed on live testnet, with both numbers as the recording holds them.
- Observed transaction
- 525d5cf0…a35e
- At ledger
- 3,929,381
- Function invoked
- transfer
- Token contract
- CDLZFC…CYSC
- Outflow observed
- 0.1
- Cap derived
- 0.1
How this was producedstep 1 of /app/simulator, driven by hand in a browser — not by scripts/ and not by the test suite. This transaction was not piped into the install below. The recorded install was built by scripts/testnet.mjs from the same parameters the lowering produces; ingest-to-install in one pass needs a browser signer, which does not exist yet. Both halves are real and they are recorded as two runs, because that is what they are.
What it looks like once installed
- Smart account
- CBNPFN…3G3V
- Context rule
- #5
- Policy contract
- CDWPYL45…TH4CSE — an audited OpenZeppelin primitive, configured. No Rust is generated and none is written by hand.
- Cap installed
- 0.1
- Window
- 17,280 ledgers ≈ 1 day
- Install transaction
- 173bcdef…6702
06The proof
What it refuses.
A boundary that permits the thing it was derived from proves nothing — the permitted flow is the one flow it was built to allow. What matters is what happens one step to the side.
Six ways to be adjacent to the permitted flow. Each changes exactly one dimension: the amount, the function, the asset, the contract, the number of invocations, the ledger it arrives on. Every one was attempted against a real rule on a real account, and every one was refused on a ledger. This is the table to read if you are deciding whether to believe anything else on this page.
| Verdict | Axis | What was attempted | Refused by | On ledger |
|---|---|---|---|---|
| DENY | amount | transfer above the cap | SpendingLimitExceeded#3221 | ac477549…7a77 |
| DENY | function | approve on the permitted token contract | NotAllowed#3223 | 45a0eb20…1f58 |
| DENY | asset | transfer of a different token | UnvalidatedContext#3002 | 1312be89…4880 |
| DENY | contract | execute on the smart account itself | UnvalidatedContext#3002 | 6b7f4ded…9389 |
| DENY | invocation | appended second invocation | ContextRuleIdsLengthMismatch#3014 | e365e681…7149 |
| DENY | expiry | same call after valid_until | refused on ledger; code not recovered | f5ebce51…8405 |
6 of 6 attempts reached a ledger and failed there; every hash above is checkable in an explorer. 5 of 6 also had a contract error code recovered from the transaction’s own diagnostic events — the remainder is recorded as it happened rather than filled in from the simulation.
What the recording saysOne rule (id 2) with a 5,000,000 cap on the native SAC, plus a short-lived rule (id 3) for the expiry axis. Every attempt reached a ledger and failed there; none is simulation-only. `sim` is the error simulation reported, `ledger` the error decoded from the submitted transaction's diagnostic events.
07Taking it back
The agent cannot remove its own boundary. You can.
A permission you cannot withdraw is not a permission. And a boundary the agent can lift is not a boundary — so the network refuses that too, and there is a hash for it.
Removing a context rule requires the account to authorize itself. The agent’s rule permits calls to a token contract, which is not a call to the account, so its attempt traps — refused by the contract, not by this application declining to draw a button. The owner’s rule does permit it. Afterwards the same call that worked before stops working, and it fails for a different reason.
The agent spends inside the boundary
0.04 of a 0.1 cap — deliberately under it, which is what makes the last step legible.
The owner removes it
Re-read from the chain after the write, not assumed. Rule 1 is gone; the owner's Default rule 0 remains.
The agent repeats the call that worked
ContextRuleNotFound#3000 — it fails because the rule is gone, not because the limit was reached. Those are different claims and only one of them is evidence the boundary worked.
A different runThese four transactions are from node packages/chain/scripts/acceptance.mjs run, recorded 2026-08-05 on smart account CBYJPU…5GKW — a different account from the one in scene 05, and from the deployment in scene 03. Three accounts’ hashes in one column read as one account’s unless the page says otherwise.
08What you get back
An agent that can act, inside a boundary you did not have to describe.
An agent
With its own smart account, its own key, and a job it can do while you are asleep and your browser is closed.
A boundary
One token contract, transfer only, up to your ceiling, until your expiry — installed on the account and checked by it, not by us.
A way out
One signed call takes the boundary away, and only yours works. The agent’s does not.
What this is not, stated plainly
- Testnet only. Every address and hash on this site is Stellar testnet. No real funds are involved anywhere.
- Not audited. The OpenZeppelin contracts Limen installs are audited. The code that decides what to install is not, and no third party has reviewed it.
- Composition only. Every policy is a configuration of an existing primitive. 0 lines of Rust are generated, which is the claim as a number rather than as a promise.
- No owner custody. The key that owns your account — a passkey, or a key generated in your browser — never reaches a Limen server, an environment variable, or a log line. Limen cannot move your funds outside the boundary you installed, and cannot remove that boundary.
- Single-transaction derivation. A boundary is derived from one observed transaction. Deriving from a set of them, and arguing about what their union should permit, is not built.
- No agent runtime. An agent is deployed, bounded and revocable today. It is not yet conversational and does not yet act on its own: the key it holds is generated in your browser and stays there, so nothing signs while your browser is closed. Scene 04 is the loop this is built for, not a loop you can run.
A sentence, a passkey, and a boundary you read before you sign it. The account, the agent’s key and the rule all go on testnet in the same flow.
09If this is your problem
Tell us what you're building.
Limen is in development and changing weekly. If you want an agent that can spend and the two bad options are your two bad options, we would like to hear from you.