The admin socket now also speaks OCapN over Cap'n Web: a WebSocket there
reaches the VatAdmin DO, an OCapN peer whose bootstrap (swissnum
vatAdmin) offers createVat(src, params). params may hold remote objects;
the vat's root comes back as one.
Each OCapN vat is its own peer: SES and @endo/ocapn run in its facet
(vat-runtime.js, bundled with esbuild), around the vat's own module,
which exports buildRootObject(vatPowers, params). Passing a client's
object to a vat, or a vat's root to the client, is an OCapN handoff, so
the receiver dials the exporter. Nothing here can dial out, so the
VatAdmin is also the router: a client registers its gateway under a
designator, a vat's designator is its id, and every peer reaches every
other through it. The netlayer is ertp-clerk's, made to work over Workers
RPC stubs and plain promises as well as Cap'n Web.
Not yet: sturdyrefs that survive a restart, an adminNode, and grants
(egress, mount) for OCapN vats. App vats (export class App) still work
beside them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
createVat (POST / on the admin socket) makes a vat with newUniqueId and
returns its id, which addresses it only there. A vat is reachable only
where it is mounted: ?mount=<labels> on each start, as ?egress= is
granted; absent means none.
The ingress reads a request's labels from its query string, plus a
Toybox-Labels header for secret ones (a URL ends up in logs and
Referers). It routes to the vat whose mount labels the request carries,
every one: stricter than Flower's no-conflict match, since an ingress
client holds no constrained handle and a label it omits must not match.
Registration keeps Flower's rule, so a mount must conflict with every
other and no request can carry two. The app keeps the whole path and
sees its connection labels in Toybox-Connection-Labels.
A vat from before ids lives at idFromName(name). PUT /by-name/<name>
reaches it on the admin socket; it stays unreachable from outside until
mounted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The switchboard now serves a Cap'n Web Handle on each of its sockets:
public (no loopback or private addresses) and local. A socket's labels
are fixed in config, so holding the socket's fd is holding that handle;
constrain(labels) returns a narrower one, as Flower's Constrain returns
a narrower fd. Labels only accumulate: a missing key matches anything,
and a key both sides set must agree (labels.js, shared by both).
The switchboard enforces the labels; toybox's Egress no longer checks
URLs itself. It opens a session to its one switchboard socket, constrains
the handle to the vat's grant, and forwards through it; a WebSocket
crosses the session as a pair of message streams. A vat narrows its own
grant with env.egress.constrain().
Grants are now labels as JSON. One stored before, a bare host[:port],
reads as {host, port}.
pnpm start and the systemd units give toybox the public handle; the
switchboard unit binds the local one in its own 0700 runtime directory.
The tests give their toybox the local handle to reach an echo server,
and check that a toybox holding only the public one never reaches it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A vat's globalOutbound is now always null. A granted host arrives as
env.egress, an Egress the app can hand to whichever of its parts should
have it; as globalOutbound it was ambient to every module in the vat.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A vat granted a host (PUT /<vat>?egress=<host> on the admin socket) gets
Egress as its globalOutbound, with the host in ctx.props where the vat
can't change it. Egress forwards matching requests, WebSocket upgrades
included, to the switchboard and refuses the rest with a 403; with no
grant, fetch still throws.
The switchboard is a separate workerd: a Switchboard DO that calls fetch
and returns the response, so a 101's webSocket passes through. toybox
reaches it over a unix socket in HTTP proxy style, which keeps the full
URL, scheme included. Its network service is the outer fence, with TLS
trusting the browser CAs; systemd units for it join toybox's, and only
the toybox group can open its socket.
The loaded Worker is now named by vat and incarnation, not by code hash:
the same code with a different grant must not share a Worker. The new
incarnation table sits beside the old ones; vats installed before it
read as incarnation 0 with no grant.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each vat is a Durable Object that stores code by hash and runs it as a
facet through the Worker Loader: an empty env, no fetch, storage that
carries over upgrades. Two sockets with no configured address: whoever
starts workerd hands them in (systemd socket activation), so the admin
socket is the authority to install code.
Tests run a real workerd: install, upgrade, isolation between vats, no
fetch, the public port can't install, and code and state survive a crash.
They spawn the package binary rather than the .bin shim, which forks
workerd, so killing the shim orphaned it and the test run never ended.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The card shows last week's issue with a link to it, or a button to
draft it. Drafting a week that already has an issue (draft or
published) is refused, with a link to that issue, rather than making
a duplicate.
Generated-by: Claude Opus 5.5
An EmDash plugin (native, content:write only) with one admin page:
a diigo username and an ISO week. It reads diigo-cache's public page
(?in=<week>&by=created) as microformats, follows rel=next, and
creates a draft in issues shaped like seed/issues.json.
Transport is a DIIGO_CACHE service binding rather than ctx.http.fetch:
EmDash's SSRF guard refuses localhost, and the binding names one
worker. Logic lives in src/week.ts with the fetcher passed in; tests
read fixtures in diigo-cache's markup. The checklist's goal 2 now
names potluck; DiigoBridge waits.
Generated-by: Claude Opus 5.5
Microformats are the public page's API for other sites (potluck
first): each bookmark is an h-entry with u-bookmark-of, p-name,
p-summary, a p-category per tag (linked to a tag search),
dt-published (bookmarked) and dt-updated (last edited). Prev/next
links carry rel=prev/next, so a reader can follow a filter's pages.
Tests parse the page with microformats-parser to pin the contract.
Generated-by: Claude Opus 5.5
Obsidian and GitHub render each newline as a line break, so the
hard-wrapped READMEs and CONTRIBUTINGs rendered ragged. Whitespace
only, except a note in potluck's CONTRIBUTING that other workerd
hosts are untested: they'd have to supply the D1 and R2 bindings.
Generated-by: Claude Opus 5.5
New README: two sentences, the dev run through connect, fill, and a
filtered public page, then features. CONTRIBUTING gains the 1d
filters and its own run and deploy sections instead of pointing at
the checklist; links to the old README now point at CONTRIBUTING.
Generated-by: Claude Opus 5.5
The README was EmDash's template blurb. Now it says what potluck is
and its first block runs from a fresh clone to /issues, using a new
`pnpm seed:issues` script that applies seed/issues.json to the local
D1 database. CONTRIBUTING covers how it's put together, seeds, checks,
the EmDash rules, and deploying; the template's stale notes (Inter,
three-column articles) are gone. AGENTS.md is now a symlink to it.
Generated-by: Claude Opus 5.5
/public/<username> takes days, after, before, in (year, month,
quarter, ISO week), by=updated|created, and q (words, "phrases",
#tags, -negation, gmail/Slack/GitHub date operators). JSON answers
echo the parsed filter; a malformed one is a 400.
Filters query the stored items in sqlite through the read-only
viewer instead of walking diigo pages: the public page keeps no
fetch authority, and a filled cache already holds every item.
Results say `complete` when every diigo page is stored; freshness
is 1f's head refresh. So the fetch budget and the O(log pages)
walk test go, and the fake deep account shrinks to 300.
Generated-by: Claude Opus 5.5
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) <noreply@anthropic.com>
Neighbor is a Project Think agent on gpt-oss-120b. Its execute tool
runs model-written code in a dynamic worker; MowerConnector puts the
mower there as the `mower.*` namespace (start/push/turn/stop, each
taking an args object). makeMower is the Logo-turtle mower on a
wrapping yard; for now it lives inside the agent's DO.
Getting to green, one symptom at a time:
- chatted instead of acting: say it acts by running code
- reply cut mid-sentence: maxOutputTokens 4096 via beforeTurn
- execute failed creating codemode's runtime facet ("Incorrect type
for the 'class' field"): declare CodemodeRuntime as a DO class
- looked for the mower in the workspace filesystem: activeTools
["execute"], and name the namespace in the prompt
- found the mower, then asked for lawn dimensions: "don't ask me
questions: put it to use"
Green: 36 mower calls, a 30-cell rectangle.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first end-to-end test: a Project Think agent, on the real model,
writes code that runs in a dynamic worker and drives a mower; the test
counts the cells cut, and saves the turn to itest-output/.
Installs @cloudflare/think, @cloudflare/codemode, agents, ai,
workers-ai-provider, zod. The model-test worker now has a main entry,
the Neighbor DO, and a LOADER binding.
Red: src/worker.ts doesn't exist yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
codeModeMessages turns what I say plus `about` into a prompt: a
neighbor persona, rules for the reply (one async arrow function over
`env`), and the lent things' types. Things are remote and calls on one
are delivered in order, so a lamp example shows pipelining: await only
for a value, or on a batch's last call. With only the rule, the model
awaited every call; with the example, it pipelines.
aboutMower gives the Mower type with Promise returns. The model test
checks the code calls only Mower methods, pushes, and awaits less often
than it calls; it saves the code to itest-output/ via a reporter, since
the test runs in workerd, which can't write files. Model tests get 60s.
Green: both model tests.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gpt-oss-120b reasons before it answers, and Workers AI's default
max_tokens (256) ran out mid-thought: finish_reason "length", content
null. Ask for 4096, and when there's no reply text, say which choice
and message keys came back instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Code mode (2c): the harness turns what I say plus `about` into a prompt;
the model replies with `(env) => ...` code. Checked textually for now,
since workerd won't eval it: the code pushes `env.mower` and calls only
methods in `about`. It also prints the code, to see what the model writes.
Red: src/code-mode.ts doesn't exist yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
makeWorkersAiModel narrows the AI binding to one model's chat
completion: the harness gets `complete`, not `run` on any model at
any cost. gpt-oss-120b replies in the OpenAI shape (checked with a
one-off probe), so the text is choices[0].message.content; anything
else throws, naming the keys it got.
Green: the model counts 3 rs in strawberry.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A first integration test of the model call: Workers AI through the AI
binding, behind the same `complete(messages)` interface as the sim's
fake model. Opt-in (`pnpm test:model`) since it needs `wrangler login`
and costs money.
Red: src/model.ts doesn't exist yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The chat and load arrows drew `things: { mower }` whether I lent the
raw mower or the governed one. Name objects by the reply arrow that
introduced them, so the diagram reads `things: { mower: governed }`
and lending the raw mower would fail the trace comparison.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The spec is a sequence diagram of the a2030.md story: I lend a governed
mower; the neighbor's harness shows the model only `about`, runs its
code as a tenant, and the governor stops the tipsy tenant at a 2-cell
fence. The test runs the actors through a causal tracer and compares
the trace with the diagram, then checks the yard and what the model saw.
The tracer comes from agoric-sdk PR #12895, extended to name
participants made mid-sequence (governor, tenant) and to draw thrown
errors as replies.
Red: src/ doesn't exist yet (ERR_MODULE_NOT_FOUND for src/fence.ts).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
How a Discord message could carry references to our DOs, after
@endo/chat: names in the text, references beside it in a mailbox DO,
an Adopt button answered ephemerally, and per-user petstores. Webkeys
in markdown work but disclose the capability to every channel reader
and to Discord itself; ephemeral replies still pass through Discord,
so references should never cross it at all.
Generated-by: Claude Opus 5.5
toybox-stories.md: the weekly roundup (#23), album-to-album sync
(endojs/playground#16), and drag-to-archive.org (#118), with the needs
they share. The checklist gains the toybox tree: a changed signal (T1),
Blockly composition by petname (T2), and confined code in Dynamic
Workers (T3).
Generated-by: Claude Opus 5.5
The public cache keeps public items whole, private annotations and
comments included (2,525 of 14,645). Dan finds that acceptable; the
goal is to finish thinking it through, not to fix a leak.
Generated-by: Claude Opus 5.5
1d's query.ts plans to filter while walking diigo's pages, but a filled
cache holds every item: choose before implementing. madmode's public
reads need no cache id, so DiigoBridge's cache-id TODO is moot unless a
private-reading caller wants it.
Generated-by: Claude Opus 5.5
- issues collection (ISO-week slug, week, dek, rich-text body); home,
/issues, and /issues/<week> pages; seed/issues.json holds three
sketched issues (W38-W40) from public diigo bookmarks, editorial
lines marked (draft); issues-sketch.md is the dry run behind them
- microformats: h-feed of h-entry lists; an issue is an h-entry
(p-name, dt-published, p-summary, e-content); off-site links render
as h-cite, /tag/<tag> links as rel-tag p-category (CiteLink.astro)
- diigo tags: shown on each bookmark and attached to issues via the
tag taxonomy (now posts + issues); /tag/<tag> lists issues
- demo blog content gone: posts dropped from the seed, home page lists
issues, site title/tagline, footer About widget, and About page
rewritten; nav is Home / About / Issues; search and RSS cover issues
- .column container gives potluck's pages the header's margins
Generated-by: Claude Opus 5.5
https://www.kmjn.org/notes/modern_plain_html.html and its stylesheet:
system fonts (Verdana; no Google webfonts), one 800px column,
line-height 1.35, links in the text color with a dotted underline, no
rounding, shadows, or tracking; the header is plain, not sticky glass.
Token overrides live in theme.css; per-page scoped styles unchanged.
Generated-by: Claude Opus 5.5
pnpm create emdash@latest potluck --pm pnpm --install --yes, per
https://docs.emdashcms.com/ ("Create your first EmDash site"); the
--yes defaults are cloudflare + blog. Replaces the old madmode/
starter (never committed).
Generated-by: create-emdash 1.2.0, run by Claude Opus 5.5
The existing export (mastodon-archive) reads favourites, bookmarks,
and mentions, which ActivityPub doesn't carry. So the client API with
a narrow read-only token (held in webapp) is the primary source;
items are still keyed by ActivityPub uri, and outbox/follower-actor
sources move to a later portability goal.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mastodon is one implementation; the cache speaks the protocol: outbox
for backfill, and fedi-cache as a follower actor so Update/Delete are
delivered rather than polled for. Its own keypair replaces an access
token, so webapp drops out of this one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both follow the diigo-cache pattern (webapp holds the credential,
read-only and minted narrow at the source; the cache serves a public
page). Zotero's versioned sync answers diigo's stale-head problem;
Mastodon's doesn't announce edits/deletes, so its tree carries that
as a design question.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comments written for the reader: no restating what the shebang says, no traces of the design conversation; the publish comment no longer mentions a projection that no longer exists.
Generated-by: Claude Opus 5.5
Comments written for the reader: no restating what the shebang says, no traces of the design conversation; the publish comment no longer mentions a projection that no longer exists.
Generated-by: Claude Opus 5.5
compare-export.ts: the manual is a HELP string at the top of the module
(a docstring, as a value) and main prints it on bad usage. No reading
its own source, no regex, one copy; @file points at it.
Generated-by: Claude Opus 5.5
compare-export.ts: the usage message comes from the @file manual's
synopsis line (read from the script's own source, on the bad-usage path
only), docopt-style, so the synopsis lives in one place; if the header
changes shape, it says "see the @file comment".
Generated-by: Claude Opus 5.5
compare-export.ts: the usage exit status is a named constant,
BAD_USAGE = 2, whose JSDoc cites the convention (Bash Reference Manual
§3.7.5); the manual just says "Exits 2 on bad usage".
Generated-by: Claude Opus 5.5