Good Fences Make Good Agents *with apologies to Robert Frost* Let's have a fence and a neighbor. I can talk to the neighbor much like the conventional... ```js client.chat.completions.create({ model: 'llama3.1:8b', messages: [{ role: 'user', content: 'Tell me a story' }], stream: true, }); ``` but with a twist: ```js messages: [{ role: 'user', content: 'you can use this mower for the weekend', things: { mower }, about: { mower: { ts: 'type Mower { start(), push(), turn(), stop() }' }} }], ``` I send `mower` by putting a **webkey** into a password field on the fence form, which fetches an `RpcTarget` stub to give to the neighbor. The neighbor can make its own tenants: it can write code to compose the things I gave it, and load that code into a dynamic worker that is endowed with the things. A `Mower` DO factory makes mowers that go all over the place (depicted as a table with green cells for grass). But with a little js playground, I can wrap it in a governor so it never goes more than 100 yards away. I give only the governed mower to my neighbor. Even though he has 50 acres, I know most of them are for tenants, and my neighbor has no particular need to let them use it. Even if they gets a little tipsy and wander around, try as he might, my neighbor and his tennants can't mow more than 100 yards away. ## Design IBIS ### ?! 1. Who is the neighbor, and where does it run? ``` :- 1a. an LLM (llama3.1:8b) alone - 1a-i. a model only sees text; it can't hold `things` :+ 1b. an LLM plus a harness we write that holds `things` and acts for it (see 2) :+ 1b-i. harness and tenants run on my desktop under `wrangler dev`, for now + 1b-i-a. I want to learn the CF platform + 1b-i-b. CF is the only place to get dynamic workers . 1b-i-c. the Worker Loader binding works in local dev (cf-fun1) ?! 1b-ii. Where does inference run? : 1b-ii-a. local Ollama, via its OpenAI-compatible endpoint - 1b-ii-a-i. the local model may not be powerful enough . 1b-ii-a-i-a. I do have an M5 with 48GB RAM : 1b-ii-b. remote, on Workers AI, through the AI binding from `wrangler dev` + 1b-ii-b-i. I want to learn the CF platform :+ 1b-ii-c. either, behind one interface; start remote + 1b-ii-c-i. starting remote lowers the odds of failing because the model isn't powerful enough ``` ### ?! 2. How do `things` get from a chat message to something the neighbor can call? ``` . 2-i. no chat-completions API has `things`/`about`; this is our extension :+ 2a. the harness strips `things`, keeps the stubs, and shows the model only `about` : 2b. expose each thing as a tool, one per method in its `about.ts` :+ 2c. code mode: the model writes JS against the `about.ts` types; the harness runs it in a tenant (see 5) + 2c-i. it's the story: the neighbor writes code to compose the things and loads it into a dynamic worker endowed with them ? 2d. What does the model see when a call fails or the thing is revoked? ``` ### ? 3. What is the fence? ``` : 3a. a web page: chat with the neighbor, plus a password field for pasting a webkey ?~ 3b. Which server resolves a webkey to an `RpcTarget` stub? :- 3b-i. cf-fun1's sturdyref registry (`resolveSturdyref`) - 3b-i-a. cf-fun1 got wrapped around the axle on this :~+ 3b-ii. diigo-cache's model: the webkey is the DO's id; a Cap'n Web `/api` trades it in-band for a facet (`CacheApi.cache(key)` → `CacheCap`) + 3b-ii-a. no registry: the DO namespace is the table; malformed and unknown keys get the same refusal, and the key is never logged + 3b-ii-b. stubs die with the session, but the key is data: the harness keeps the key and trades it again each session + 3b-ii-c. attenuation is a facet class (`FillCap` under `CacheCap`; `viewer()`) : 3b-ii-d. (postponed; see 8b-ii) the governed mower is its own DO, wired to the raw mower's key and the limit; its id is the webkey I hand over - 3b-ii-d-i. diigo-cache has no revocation: a DO id can't be withdrawn + 3b-ii-d-ii. the governor DO can drop its wiring, so its key gets "no such mower" (see 4) ?! 3c. Why a password field rather than a link? :+ 3c-a. so the password manager saves the key under a petname I choose (diigo-cache's `pmSave`), and the key stays out of URLs, history, and logs ``` ### ? 4. How does "for the weekend" end? ``` . 4-i. postponed to a later scope, where we can inject a clock and such : 4a. the governor checks a deadline (the clock is passed in, not ambient) : 4b. I tell the governor to drop its wiring, by hand (see 3b-ii-d) : 4c. the fence mints a webkey with an expiry ``` ### ? 5. How does the neighbor make tenants? ``` : 5a. Worker Loader (dynamic workers), with the things passed in as `env` . 5a-i. `env` takes only structured-clonable values and service bindings, so a Cap'n Web stub can't go in directly : 5a-i-a. wrap each thing in a `WorkerEntrypoint` handed over as a `ctx.exports` loopback binding; it holds the stub and forwards calls : 5a-i-a-i. the loopback carries the thing's webkey in `props` (a string clones fine) and trades it for the DO stub on each call (see 3b-ii) + 5a-i-a-ii. the tenant never sees the key: `props` stay on the host side + 5a-i-a-iii. no stub outlives a request, so 5c's cross-request limit doesn't bite + 5a-ii. `globalOutbound: null` confines the tenant to what it's given ? 5b. Who sees or approves tenant code before it runs? ? 5c. Does a tenant outlive the neighbor's turn, e.g. one that mows on a schedule? ``` ### ?! 6. What is a Mower, concretely? ``` :+ 6a. a DO per mower, made by a `Mower` DO factory (the story says so) :+ 6b. one shared yard: an `RpcTarget` in my browser that renders the table; each mower is given the yard and calls `yard.cut(x, y)` :+ 6c. one cell is 10 yards; the yard is 40×40 cells, so "100 yards" is 10 cells from where the mower started . 6c-i. the governor's limit is a square, max(|dx|, |dy|) ≤ 10: a fence :+ 6d. Logo turtle moves: `start()` puts the mower down facing north and cuts its cell; `push()` is FORWARD 1 and cuts the cell it enters; `turn()` is RIGHT 90; `stop()` parks it . 6d-i. pushing past the yard's edge wraps to the other side, so an ungoverned mower really does go all over the place . 6d-ii. the governor counts its own unwrapped moves (see 7a), so wrapping can't sneak a mower past the fence ``` ### ?! 7. How does the governor know how far the mower has gone? ``` :+ 7a. it proxies every Mower method and tracks position itself from `push()`/`turn()` - 7a-i. drifts if anyone else holds the raw mower + 7a-ii. nobody else does; the governor is the only one I hand out ?! 7b. What does the governor do at the limit: refuse silently, throw, or report? :+ 7b-i. it refuses that `push()` by throwing "fence: 10 cells from start"; the mower stays put, and `turn()` always works + 7b-ii. the tenant can catch it and turn around; the model sees why its code stopped (see 2d) :- 7b-iii. refuse silently - 7b-iii-a. a tipsy tenant would think it mowed cells it didn't ``` ### ?~ 8. Where does the js playground run, and what can it reach? ``` : 8a. in the fence page; it writes the governor, which runs in its own dynamic worker holding the raw mower :~+ 8b. in my browser, wrapping the stub on the client - 8b-i. the governor then lives only as long as my tab is open + 8b-i-a. a durable governor is out of scope for now . 8b-ii. then the neighbor holds a Cap'n Web stub to my browser, not a webkey, so 3b-ii-d (governor DO) and 5a-i-a-i (key in `props`) wait with the durable governor ``` ### ?! 9. Who watches the yard table, and how does it update? ``` :+ 9a. I do, in my browser: the yard is an `RpcTarget` there (see 6b), and it repaints a cell on each `cut` - 9a-i. the mowers' stub to the yard dies when I close the tab + 9a-ii. fine for a proof of concept: keep the tab open (see 10-i) ``` ### ?! 10. What is this for: a demo, a talk, or a product? ``` . 10-i. the answer decides how much of the above can be faked :+ 10a. a proof of concept, for now :+ 10b. toward a pattern for all sorts of rich cooperation with little vulnerability + 10b-i. an alternative to OpenClaw, where 12–17% of ClawHub skills were found malicious in Feb 2026 (sources below) ``` Sources for 10b-i: > "A security audit of 2,857 skills on ClawHub has found 341 malicious skills across multiple campaigns" (about 12%, from Koi Security) > — [Researchers Find 341 Malicious ClawHub Skills](https://thehackernews.com/2026/02/researchers-find-341-malicious-clawhub.html), The Hacker News, Feb 2026 > "Around 17% of OpenClaw skills analyzed in the first week of February 2026 exhibit malicious behavior." > — [Helpful Skills or Hidden Payloads? Bitdefender Labs Dives Deep into the OpenClaw Malicious Skill Trap](https://www.bitdefender.com/en-us/blog/labs/helpful-skills-or-hidden-payloads-bitdefender-labs-dives-deep-into-the-openclaw-malicious-skill-trap), Bitdefender, Feb 2026