From efb54ad4ccec579b8af738c9ee88e48d9c65e4c4 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 9 Oct 2026 01:00:15 -0500 Subject: [PATCH] docs: Good Fences Make Good Agents (design story + IBIS) The story behind good-fences: lend a governed mower over the fence to a neighbor whose tenants are model-written code, with the design IBIS worked out so far. Previously an untracked note at the workspace root. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/good-fences-make-good-agents.md | 166 +++++++++++++++++++++++++++ good-fences/docs/lend-the-mower.md | 2 +- 2 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 docs/good-fences-make-good-agents.md diff --git a/docs/good-fences-make-good-agents.md b/docs/good-fences-make-good-agents.md new file mode 100644 index 0000000..8830839 --- /dev/null +++ b/docs/good-fences-make-good-agents.md @@ -0,0 +1,166 @@ +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 diff --git a/good-fences/docs/lend-the-mower.md b/good-fences/docs/lend-the-mower.md index aa02cb8..c603492 100644 --- a/good-fences/docs/lend-the-mower.md +++ b/good-fences/docs/lend-the-mower.md @@ -1,6 +1,6 @@ # Lend the mower -The journey from [Good Fences Make Good Agents](../../../a2030.md): I lend +The journey from [Good Fences Make Good Agents](../../docs/good-fences-make-good-agents.md): I lend my neighbor a governed mower; the neighbor's tenant, a little tipsy, pushes it until the fence stops it. The design decisions behind each actor are in that doc's Design IBIS (numbers in parentheses below).