You lend the kitchen; the guest brings the skill — and can open only the one drawer you unlocked.
There are two ways we run software today, and they're both a kind of shipping problem.
The first: send the data to the code. Your photo goes up to a company's servers, gets processed there, comes back. That's the cloud. The second: run the code where the data already is. Everything happens on your laptop. That's a local app.
There's a third way, older than both, that almost nobody uses — and it's quietly the most interesting: send the _code_ to where the resources are. Not the data, not a request — the little worker itself. It travels to another machine, does its job there using that machine's tools, and comes home with the result.
That's a mobile agent. IBM shipped one in 1998 and called them aglets — "agile applets." They were right about the shape and 25 years early on the two things that would make them work. This post is about the version that finally does — we call them aiglets — and by the end you'll have dispatched one yourself and watched it phone home.
An old idea with two missing pieces
The aglet model is genuinely lovely. An agent can suspend itself, pack up its state and code, travel to another host, resume there, and still send messages home. Picture a courier who doesn't just carry a letter but goes and does the errand and reports back.
It didn't take over the world. Not because the idea was wrong — because 1998 couldn't solve its two hardest problems:
- Trust. A host has to run a stranger's code, and your agent has to run on a host you might not trust. Java's answer was a
SecurityManager— a big list of "please don't do that." It was a deny-list you hoped held, and it was picked apart routinely. There was no clean way to say: this visitor may touch exactly these things and nothing else. - Usefulness. An agent that only moves data around is just a slow, complicated remote procedure call. The "intelligence" had to be hand-coded, so the travel rarely paid for itself.
Both are solvable now, and that's the whole reason to revive the idea.
- The trust problem is solved by capabilities. (If you read _An App With No Server In the Middle_, this is the same machinery.) A visiting agent doesn't run in a room it can escape from; it runs holding only the specific object-references it was handed. It can't reach what it wasn't given — not because it's asked nicely, but because it has no way to name those things. The deny-list becomes a short allow-list of exactly the keys you lent.
- The usefulness problem is solved by the local model. The agent carries a brain. It can reason on arrival, not just shuttle bytes.
An aiglet, then, is an aglet plus the AI that didn't exist in 1998, bounded by capabilities instead of a security manager.
The mental model: a guest chef
Forget the code for a second. Here's the whole thing in one picture.
You invite a guest chef into your kitchen for the evening.
- They travel to your kitchen — they don't send you a recipe to cook, they come and cook. (relocation)
- They use your oven and your pans — the good equipment that lives in your kitchen, not theirs. (host reach — using the host's resources)
- Mid-cook, they phone their own supplier back home for a special ingredient, on the wall telephone by the door. (home reach — reaching back to where they came from)
- They can open only the one drawer you unlocked for them. The safe, the other rooms, your files — those don't have handles they can turn. (capability bounding)
- You handed them a set spending limit for anything they need to buy. (a traveling mandate)
- And if you change your mind, you can take the key back mid-recipe. (revocation)
Every one of those maps to something real below. Hold the picture; the code will feel obvious.
Why two reaches, not one
The single most important property is that a guest chef reaches both ways: they use your oven (host) and call their supplier (home).
An agent that can only reach the host is just remote code execution — you've let a stranger run a program on your machine, which is exactly the thing that should frighten you. An agent that can only reach home is just a normal app. It's the combination that's new: the aiglet is, at the same moment, a client of the host it visited and an agent of the home it came from. That duality is what dissolves the hard line between "client" and "server" — the thing we're chasing across all of moimoi.
And here's the neat part: home-reach rides the same connection the agent arrived on. Home doesn't have to be a reachable server sitting there waiting. The chef phones home on the line that's already open — which means it works even when "home" is a laptop behind a home router that nothing on the internet can dial into directly.
Type it yourself
Enough words. Let's dispatch one. You'll run Alice, who boots a peer, spawns Bob as a second peer, and sends an aiglet into Bob's kitchen.
git clone https://github.com/ski/moimoi
cd moimoi/peer
npm install
npm run peer:selfThat's it — no cloud account, no GPU, no build. Here's the agent you just sent. It's a plain function of the capabilities it will be handed — nothing else is in scope:
// This source travels to Bob and is instantiated inside a locked sandbox there.
// It can see ONLY { host, home, E, log } — the four things passed in. Nothing ambient.
(async ({ host, home, E, log }) => {
log('aiglet: arrived at Bob, running confined');
const caption = await E(host).infer('caption a cat'); // HOST REACH — use Bob's brain
const img = await E(host).imagegen(caption); // HOST REACH — use Bob's image-gen
const ack = await E(home).deliver({ img, caption }); // HOME REACH — phone Alice, over the open line
return harden({ ok: true, img, caption, ack });
})The host side is just as small. Bob's "kitchen" — his place — receives the agent and runs it confined, handing it exactly two keys:
// The place: instantiate the arrived agent in a fresh sandbox with NO ambient authority,
// granting it only this host's capability + the home reference it carried.
receiveAiglet: async ({ source, home }) => {
const compartment = new Compartment({}); // no endowments → no process/fetch/require inside
const aiglet = compartment.evaluate(source);
return aiglet({ host: hostCap, home, E, log }); // ← exactly the two keys, and nothing more
}And Alice's dispatch is one line — reach Bob's place and hand over the agent, carrying her own deliver cap as its way home:
const bobPlace = await E(bobPeer).place();
const result = await E(bobPlace).receiveAiglet({ source: AIGLET_SOURCE, home: alice.aegis });Run it and you'll see:
=== ADR 0065 Rung A — two-peer harness (Alice ↔ Bob) ===
PASS relocation + confined run in a separate Bob vat
PASS host reach — Bob.imagegen produced the image
PASS host reach — Bob.infer caption used
PASS home reach — Alice aegis received the delivery over CapTP
PASS home reach — Alice acked
PASS cap-bounding — no ambient authority in the arrived compartment
RESULT: PASS — two moimoi peers; aiglet reaches host + home, cap-bounded.Stop and appreciate what just happened. A little program left Alice, arrived inside Bob, used Bob's tools, phoned back to Alice over the same connection it came in on, and — the last line — could not touch anything it wasn't handed. No server brokered it. (Want to feel it more? The same two peers run on two different machines — one in Windows, one in Linux under WSL2 — over real TCP. Same result.)
What just happened (the capability part)
That last PASS is the one 1998 couldn't get. Inside Bob, the aiglet ran in a Compartment — a sandboxed JavaScript world with nothing in scope except the four things Bob passed in. The test proves it: from inside the agent, process, fetch, require, and globalThis.process are all undefined. There is no file system, no network, no way to reach Bob's other secrets — not because we forbade it, but because those things simply have no name the agent can utter.
The only power the agent had was the two references it was given: host (Bob's brain + image-gen) and home (the way back to Alice). In capability-speak, that reference is the permission — holding it is what lets you use it, and not holding it means the door isn't just locked, it isn't there. Revoke the reference and the agent loses that reach mid-flight. That's the guest chef's single unlocked drawer, made real.
Where it comes from: the Aglet API, 28 years later
If this feels like a rediscovery, it is — a deliberate one. We took the shape of IBM's 1998 Aglet API and rebuilt each piece on capabilities:
Aglet— the mobile agent → a function of its capabilities, run confinedAgletContext— the workplace it arrives in → the place (receiveAiglet)AgletProxy— a "shield" wrapping the agent → the capability reference itself — shield + location transparency, for freeTicket— destination + quality-of-service → destination + QoS + the keys to request + a spending limitsendMessage(now / future / oneway) → a capability call:await E(x).m()/E(x).m()/E.sendOnly(x).m()
The single biggest simplification: in 1998 the AgletProxy was a hand-built wrapper whose job was to shield the agent and hide where it lived. With capabilities you get both properties for nothing — a reference already only exposes what it exposes, and calling it already routes to wherever the real object is. The scaffolding that took IBM a framework is just... how references work.
Deploying it for real
The demo above is headless, so it runs anywhere. To see two aiglet-hosting peers as real desktop apps — Alice in one window, Bob in another — there's a one-command build:
make setup # one-time: system deps + toolchain + the native runtime
make bob # Bob: a peer that listens, waiting for guests
make alice # Alice: dials Bob and dispatches an aiglet into his windowSame peer code as the headless test — the only thing that changes is the shell it runs in. (The full build recipe, including the Linux/WSL2 gotchas, lives in the RUNBOOK.)
Honest edges
So you know the map — these are real, and they're what we're building next:
- The arrival handshake. Right now the host grants a fixed set of keys. The real thing is a negotiation: the visitor's ticket asks for capabilities, and the host answers — some free, some priced (paid from the visitor's spending limit), some needing a human's consent tap. A trusted host grants generously; an untrusted one grants the minimum, and the agent is written to make do. Either way it's contained by construction.
- Sealing the code in transit. Today the agent travels as raw source. It should travel as a hash-pinned bundle the host verifies before running — so "this is exactly the agent you think it is" is provable, not assumed.
- What happens to the money if the host switches off? The agent carries a spending limit, not funds — the value itself lives on-chain. A limit is bounded and revocable, so a host going dark can't strand or steal it. The clean accounting for work interrupted mid-transaction is an open question we're deliberately leaving open.
- Finding each other. The demo uses a known address. Real discovery — peers finding each other over a distributed hash table, no rendezvous server — is the same Hyperswarm machinery from the earlier post, not yet wired in here.
None of those change the core you just ran with your own hands: a worker that travels to where the tools are, reaches back to where it came from, and can touch only what you handed it.
This is the lay-reader companion to ADR 0066, "The aiglet platform API." The agent's brain is ADR 0063 (Aegis as an agent harness); the two-peer test bed you just ran is ADR 0065. The decision records, the exact code, and the prior art these examples are distilled from live alongside them for anyone who wants the full depth.
End of Transmission.