- Web: base bundles built from the app's entry at start (content-hashed, served forever), plugin scripts that register themselves and one combined file, WebScripts/TuiScripts showing the current set, startPage with the connection loop, in-place plugin reloads and a reload on a new base bundle. - React bindings: HostProvider, useHold, useWatch, useRegistry, useRefState. - Terminal: startTerminal, a launcher that signs in with a token, a token file or device pairing and always runs the app's terminal code in a sandboxed child process (pipes, empty environment, --permission granting nothing, no SQLite, a builtin allowlist and a frozen process inside); output filter, confirmed copy/open/attach actions; startTerminalRuntime and TuiScope inside. - Themes: the Themes registry, ThemeChoice per principal, CSS variables on the page, terminal colours by role; CSS modules in plugin builds. - App kit: startServer gains web and tui; startApp renamed startServer, exportAddress/importAddress renamed exportData/importData and export no longer stops the worker; ephemeral sessions end with their client's last link. - Examples 56-web, 57-tui, 58-themes, 59-chat.
20 KiB
Chord v2: the slice plan
Status: approved (owner, 2026-10-22); kept current. design.md states the rules; this file says which slice builds
what, and (section "Carry-over") what a slice deferred to a later one.
Why slices
Building bottom-up made each layer guess the needs of callers that did not exist yet; L2 grew generic machinery (nodes, sinks, deliveries, publications) that nobody could follow, and was deleted. A slice is the thinnest end-to-end path through every layer a feature needs, so every API has a real caller from the first line. Later slices extend earlier code; reworking it when a real need appears is expected, guessing ahead is not.
Per slice (owner, 2026-10-22): a one-page spec written by this session (slice-N.md, including the carry-over
entries it takes), the owner's approval, a build by a fresh Opus 5.5 subagent, reviews of the uncommitted code by this
session and by the reviewers Astra and Fable (v2-review-astra, v2-review-fable), fixes by the builder, the owner's
review of the uncommitted diff, commit. Deferred work goes into the section "Carry-over" below, under the slice
that must do it, in the same change (owner, 2026-10-22; moved here from carry-over.md on 2026-10-24). Every slice that touches a hot path (links, the store, relays) also adds a light benchmark script
(packages/chord/test/next/bench/, run by hand) for timing, memory and wire size of realistic payloads, with results
in benchmarks.md (owner, 2026-10-22). Every slice adds one or more runnable
examples (packages/chord/test/next/examples/NN-topic.ts, numbered across slices, in the format of Durable's
test/examples/), each on one topic: for the owner to run and dig around in, and as references for Chord's users
(owner, 2026-10-22). Implementers and reviewers read
the slice spec, the feature map below and the whole of design.md in full, and check the work against all three; a
slice spec's section citations are for orientation, not a reading limit (owner, 2026-10-22).
What exists
- L1 (
src/next/contracts,src/next/data): contracts, params and identity, argument checks, principal keys, errors.index.tsfor apps,internal.tsfor Chord's own code. - Slices 1, 2, 3a, 3b, 4a, 4b, 5, 6a, 6b, 8, 7a (committed; 5
4a5b45c45, 6a and 6b 2026-10-27/28, 8 2026-10-28, 7a 2026-10-29):src/next/documents,state,host,jobs,link,remote,store,registries; examples 01-32 (Durable's 35-37); benchmarks inbenchmarks.md. - Slice 7b (built 2026-10-29,
slice-7b.md):host.reload, staging (host/staging.ts), fates, adapted members (host/adapted.ts), references, jobs and registries across a reload; examples 33-34;test/next/bench/reload.ts. - Slice 7c (committed 2026-10-30,
slice-7c.md):src/next-node/plugins/(discovery, builds, evaluation,pluginFile,mapStack,installDependencies, the watcher,rollout), per-pluginloadandreload; examples 35-39. - Slice 10 (built 2026-10-31,
slice-10.md):src/next-node/http/(afetchcore with the security baseline, Chord's Node listener,nodeRequest, plugin sockets, links),src/next-node/workers/,createPluginRuntime, kinds, the kernel'sRouteHandleand anonymoushost.root, origins only from relays; examples 40-48. - Slice 12 (committed 2026-11-01,
slice-12.md): the plugin model insrc/next-node/plugins/(the app's versioned API, per-kind files, single-file plugins, installs from git and npm,testPlugin); examples 49-52. - Slice 13 (committed 2026-11-02,
slice-13.md): the app kit,src/next-node/app/(startServer,startWorker, sessions, owner link, pairing,Files,exportData/importData); examples 53-55. - Slice 11 (committed 2026-11-03,
slice-11.md):src/next/surfaces/(scripts objects, registered scripts, the connection loop, clients, themes),src/next-web/,src/next-react/,src/next-tui/,src/next-node/surfaces/,src/next-node/tui/(the launcher and its child-process sandbox); examples 56-59. - Chord's shared code (upstream pi):
src/watch.ts(Watch,NativeWatch,WatchHandle,WatchEnd,NativeWatchable), Delta (track,diffRevisions,applyImmutable, encoders,PendingOps,appendText), context, JSON, output, SQLite drivers.
The slices
Order (owner, 2026-10-22: routines and jobs before the store and reload; 2026-10-27: slice 8 before slice 7, whose reload has to reload real registries; 2026-10-29: slice 7 split into 7a, 7b, 7c; the app kit as slices 10 and 11; telemetry last, since it instruments the app kit too; 2026-11-01: the plugin model (12) and the app kit (13) before web and TUI (11), which builds into the kit).
| # | Slice | In short |
|---|---|---|
| 1 | One host, local objects | host, facets, local objects, references, in-memory documents, transactions, derives, watches |
| 2 | Failures and natives | self.open, activation failures and recovery, backoff, the drain, timeouts |
| 3a | Remote objects over one link | remote contracts, principals, variants, a memory link, the message format, holds, calls, one message per commit (no relay, no drops) |
| 3b | Surviving drops | re-holds, parking, ref.state, host.hold, ref.retry(), linger |
| 4a | Links and relays | host kinds, host addresses (placement then), routes, two-sided links, relays and principals, IPC and WebSocket, flow control, heartbeat |
| 4b | Authority and housekeeping | revocation, cycles across hosts, host.references, host.activity, size limits, compression |
| 5 | Routines and jobs | self.routines, self.job |
| 6a | The store | the store (backends, tables, queries, writes, commit queue, outbox, documents engine, migrations, export), persisted documents, self.table, keyed watches and retirement |
| 6b | Durable and jobs on the store | persisted jobs, Durable's StoreStorage, the Chord source over Durable's views (self.follow) |
| 8 | Registries and host.root |
registries, self.writable, retryOn, Durable's registryReader, host.root (chord(fn, context) and the harness object are p3's base code) |
| 7a | The dependency model | requests that outlive providers, parked attempts without timers, wakes, the publication gate, getters, registries as dependencies, parked_long (slice-7a.md, dependencies/model.md) |
| 7b | Reload in the kernel | host.reload: staging, commit, rollback; swaps, restarts, removal and re-serving; holds and accepts across a reload; jobs and registries across a reload; per-name wakes (slice-7b/ brief) |
| 7c | Plugins | chord/next/plugins (discovery, buildFacet, evaluateFacet, pluginFile, installDependencies, the watcher, rollout, mapStack; no dev/prod split; the kernel's load/reload per plugin; slice-7c.md) |
| 10 | HTTP and workers | chord/next/http (server, Http registry, responses as descriptions, WebSocket entry) and chord/next/workers (a worker per address, IPC, idle stop) |
| 12 | The plugin model | imports (relative within a repository, package names pinned across), versioned app API with Chord re-exported, per-kind API modules and contract files with the build's import check, single-file plugins, git installs, testPlugin, a required root (slice-12.md) |
| 13 | The app kit | chord/next/app: startServer, startWorker, layout, worker kinds, owner link, deny by default, sessions, CLI tokens, storage, the files plugin, export per address, plugin roots and "apply changes", base code as API, bootstrap and a start-data facet (slice-13.md) |
| 11 | Web and TUI | chord/next/web (page host, React hooks) and chord/next/tui (launcher, sandbox), from p2's code |
| 9 | Telemetry | spans, events, metrics across hosts |
The playground (apps/chord-playground) moves to the new code once slice 4 exists. Round-3 cuts (C3-C16) and
carry-over entries are decided in the slice that first needs them.
Feature map
Every feature of design.md, by section, assigned to the slice that builds it. A slice's spec covers every entry
assigned to it; implementers and reviewers check the slice spec, this map and the whole design (owner, 2026-10-22). A feature missing here is a planning bug: add it before the slice that needs it. "Doc" means a documented
pattern, no code. Decided cuts are noted where they apply.
| § | Feature | Slice |
|---|---|---|
| 3 | createHost with telemetry, onError; host without address |
1 |
| 3 | createHost with principal, principalKey (3a), address (4a), revokedBy (4b), the store (6a) |
3a, 4a, 4b, 6a |
| 3 | load([{ name, facet }]) with static facets; start barrier (host-lifetime objects live before load resolves) |
1 (failed plugins reported: 2) |
| 3 | connect, accept, memory link pair, incarnations in the handshake, protocol-version handshake |
3a |
| 3 | host kinds, addresses, route (authorize, resolve), relayed holds and calls, re-resolve after a drop |
4a |
| 3 | IPC and WebSocket links, trusted links, the ack-clocked window per origin (K unacked state messages, pending queues, call window N per origin), relays with flow control per origin (origin, closed, senders FIFO), heartbeat |
4a |
| 3 | timeouts: closeTimeout, call drain, readyTimeout (C13: two host timeouts) |
2 |
| 3 | provider-side linger (C7: kept) |
3b |
| 3 | park deadline (= readyTimeout, C13) |
3b |
| 3 | host.activity (keep-alive counts) |
4b |
| 3 | host.root(fn, …) for code outside objects |
8 |
| 3 | requests that outlive providers: the destination by address, the publication gate, the unbound index, the check against the selected provider only; the start barrier's first outcomes (decision (a)) | 7a |
| 4 | principal type P, links stamp principals, principal keys in identity |
3a |
| 4 | revocation (revokedBy, host.revoke, propagation to children) |
4b |
| 5 | local types: params, members, checked arguments (Q15), results unchecked |
1 |
| 5 | version and fingerprint match at acquisition; reserved member names (then, release, state, retry) |
1 |
| 5 | remote types, variants chosen by the factory ({ variant, members }), identity with principal key, calls checked against the binding's variant |
3a |
| 5 | accepts mappings at runtime (old consumers), keyed members per key, contract_mismatch |
7b |
| 6 | lifecycle, one object per identity, lifetime from kind and params (Q-A), factory, activation, ending | 1 |
| 6 | failure causes, backoff per identity, dependency recovery (R18) | 2 |
| 6 | retryOn |
8 (first independently living source: registries) |
| 6 | parked attempts (no dependency deadline or backoff, wakes on every change, retry passing on), FailureCause waiting, parked_long and the authoring rules against cycles |
7a |
| 6 | cycle rejection on one host (Q5) | 1 |
| 6 | cycle rejection across hosts (activation chain in hold headers) | 4b |
| 6 | shared store with per-principal views; worker-wide native owners; natives following a relocation | doc (1, 3, 2) |
| 7 | self.get, host.get, hydrated references, members as watchables, release |
1 |
| 7 | per-reference watches (released with their reference) | 1 |
| 7 | ctx.get (call-scoped references) |
3a |
| 7 | ref.state (tagged), ref.retry(), host.hold, calls parking, failed references |
3b |
| 7 | getters wait through waiting; host.hold throws only for invalid params |
7a |
| 7 | ref.variant as discriminant, RefIn (C15: no inVariant/expectVariant) |
3a |
| 7 | state: false (C8: cut) |
- |
| 7 | optional dependencies as registries | doc (8) |
| 8 | ctx with telemetry and signal |
1 |
| 8 | ctx.principal, ctx.incarnation (R20), ctx.get (3a); relayed incarnations (Q13, 4a) |
3a, 4a |
| 8 | ctx.trace (C11) |
9 |
| 8 | a job's ctx.jobId, ctx.state |
5 |
| 9 | defineState, singleton and keyed handles, self.state, in-memory documents (P2) |
1 |
| 9 | self.tx, tx.draft, tx.retire, drafts revoked (R12), no existence check (R13), lines per object |
1 |
| 9 | handle.change/handle.retire shorthands (C14: kept) |
1 |
| 9 | initial() checked once per definition (R8, D3) |
1 |
| 9 | persist, checkpointWhen (required for persisted documents), persisted only in local empty-params objects (P3), eviction |
6a |
| 9 | keyed watches (handle.watch(key, ctx)), handle.at(key), retirement as a flag (decision "One retirement mechanism") |
6a |
| 9 | versions, migrate, schema fingerprints on records |
6a (in-memory migrate on reload: 7b) |
| 9 | 1, removed in 6a | |
| 9 | rules across owners, reconciling routines | doc (5) |
| 10 | the store: backends, tables, query runner, writes, commit queue, outbox, migrations, export/import | 6a |
| 11 | Watchable, watches (Chord's Watch), the batch (W1), reentrancy |
1 |
| 11 | NativeWatchable, self.writable |
8 |
| 11 | Durable's views as sources (self.follow over Durable's observeXXX) |
6b |
| 12 | self.derive, failures per member, lazy when unwatched, mapShared |
1 |
| 12 | container-width benchmark (R24) | 9 |
| 13 | self.open, close reasons, order, restart patterns (R16) |
2 (restarts on reload: 7b) |
| 14 | self.on (W2b), self.onLive, sleep, untilAborted (C4 decided); the handover per object on reload |
5 (handover: 7b) |
| 15 | defineJob, self.job, intent/run/finish, state documents, dedupe (Q9), busy, running, blocked finish (C3 decided); persistence, resume, restart limit; accepts in use |
5 (persistence: 6b; accepts and reload: 7b) |
| 16 | defineFacet<P>, facet as declarations only (documents and routines removed in 6a) |
1, 6a |
| 17 | registries: defineRegistry, contributions, compose, versions (Q8), use-site guards |
8 |
| 17 | registry readers as parked dependencies (waiting), host.registry waiting, the last good value dropped when a contribution leaves |
7a |
| 18 | constant(value), start data as a core object (SessionInfo) |
1 (core facet pattern: 4a) |
| 18 | platform bindings as core objects | doc (8) |
| 19 | host.references (presence) |
4b |
| 20 | flows kept by their owner, the steps helper, the Questions service |
doc (5); deferred decisions #9, #11 |
| 21 | host.reload: staging, atomic commit, pure swaps, native restarts, removal and re-serving |
7b |
| 21 | loading plugins (loader D), dev loop; the rollout across hosts is the app's (decision "Slice 7b's open points" (3)) | 7c |
| 22 | message format, header schema, unknown ids, one message per commit per link, consumers apply ops, plugin errors | 3a |
| 22 | relays, size limits per part, compression at the origin, adapted bindings get diffs | 4a (relays), 4b (limits, compression), 7b |
| 22 | trace carriers (R5) | 9 |
| 23 | HTTP (routes, wraps, responses as descriptions, request bodies, plugin sockets), workers; channels and chunked transfers stay owner patterns until a second user | 10 |
| 23 | the app kit: startServer, startWorker, start-data objects, deny by default, sessions, owner link and token file, device pairing, files (with chunked transfers), export and import per address, plugin roots and "apply changes", optional users, invite and ephemeral-login plugins |
13 |
| 23 | web and TUI: base bundles, registered plugin scripts and the combined file, WebScripts/TuiScripts, startPage with the connection loop and in-place reloads, React bindings, the sandboxed terminal (launcher, sandbox, TuiScope), themes (Themes, ThemeChoice, CSS variables, terminal colours), CSS modules |
11 |
| 24 | Durable integration: chord(fn, context) (Q6), a RegistryReader over composed registries; the harness object and its model and env registries are p3's (base code, owner, 2026-10-27) |
8 (Chord parts), p3 |
| 24 | StoreStorage |
6b |
| 27 | versions table: contracts (L1 done), registries 8, documents 6, jobs 5, link protocol 3a | as listed |
| 29 | telemetry spans, events, metrics, carriers | 9 (cost counters: every slice) |
| 30 | cost guards per hop | every slice |
Size
About 9-10k source lines in total, as estimated for the layers (owner: fine, on the order of Durable).
Carry-over
Obligations a slice deliberately left to a later one, under the slice that must do them, with where they came from.
Rules (owner, 2026-10-22): whoever defers something (this session, a subagent, a reviewer) adds it here in the same
change; a slice's spec includes every entry of its section, and its build and review check them off; a done entry is
deleted (the slice's spec and code are the record). Moved here from carry-over.md (owner, 2026-10-24).
Any slice (protocol and checks)
- Bump
PROTOCOL_VERSIONfor any message or reason change once p3 has shipped (slice 3b addedretry,loadingandfailed'sretryIn; slice 4a parts, the listbound,hello'scalls,ack,ping,pong,closed,originand the hold'saddress,principalandincarnation; all without a bump: nothing is deployed). (Owner, slice 3b.) - The park deadline's per-type override is not built: a parked call waits the reference's
readyTimeout. Build it only if a real case needs it. (Slice 3b spec.) - No CBOR; revisit only if benchmarks show encoding as a bottleneck (slice 3a's and 4a's do not). (L2 decisions.)
- One N on every host: a relay forwards calls at once, so a worker announcing a smaller N than the relay's could see more running calls of one origin than it accepts (a protocol error). Every host announces 256; per-link N, if ever needed, must bound the relay's forwarding too. (Slice 4a rework.)
- The Durable conversation view's size on re-hold is a known cost until pi #11327 (windowed view) lands. (Slice 4.)
Later: reload (when a case needs it)
rebuildof tables (key and column-type changes):evolveTablehas no edit for them; build one only when a real case needs it. (Slice 6a's second spec review; out of slice 7b's scope,slice-7b.md§12.)
After slice 11
- Messages and languages in the app kit (decision "Messages and languages in the app kit", proposal
docs/next/i18n/):defineMessages,t.message/t.render,MessagesOf, theTranslationsregistry with fallbacks and conflicts, the locale preference per principal (default from the device, live switch,lang/diron the page),useMessagesand the terminal scope's redraw,formatTime/formatNumber; tests and an example (the notes plugin in English and German, a French translation plugin, a server message rendered per viewer). - Small examples (owner, 2026-10-30: the 7c examples are complete but complex): once HTTP, workers, web and TUI exist, a pass of small, easy-to-read examples, each showing one thing a plugin author or app does.
Slice 9: telemetry
-
Spans for routine calls and job runs (from slice 5).
-
The use of a persisted run's stored start trace carrier (
trace, stored since slice 6b). -
Round-3 cut to decide: C11 (
ctx.trace).
When v2 goes upstream
mapSharedlives insrc/next/state/derive.tsand moves into Delta. (L2 decisions.)withLinkedAbortSignallives insrc/next/host/ctx.tsand moves next towithAbortSignalinsrc/context(pi first): a call's context linked to long-lived abort signals until the call settles, instead ofAbortSignal.any, which keeps cleanup entries per call while its sources live before Node 24.16.0 (nodejs/node#62367). (Slice 4a.)- The
./nextpackage export returns when the new code has an entry point (removed with the frozen-kernel move). - A
./next/nodeexport fornodeCompression(src/next-node/), with./next. (Slice 4b.)
The sync layer (not a slice; when Radius sync is built)
- Upload, acknowledgement and
store.outbox.trim, epochs, pull-back, the split (S5, S7); a store opened with an outbox (decision "The outbox is optional, internal and versioned") starts its sync from an archive. - Snapshot reads for large exports on a second SQLite connection (S4), if exports get large.
Outside the slices
- The playground's runtime tests fail on a stale
node_modulescopy of chord 1.3.0; relink withnpm install --ignore-scriptswhen the playground is used again. (Frozen-kernel move.)