OutputBuffer, Progress, boundOutput, sanitizeOutput, characterEnd and the
output types move from Durable's internal harness/output.ts to the new
Chord subpath, with OutputSkip (the shape of Durable's ShellOutputSkip) and
utf8ByteLength. Durable imports them from Chord; its truncate.ts re-exports
Chord's utf8ByteLength instead of keeping a second copy.
A call through a remote reference checked the ctx against its own host,
so a ctx from another host, or an object not made by Chord, passed and
its root was carried over the link. It now runs the same checks as a
local reference and rejects with invalid/foreign_ctx.
closes#10959
- an awaited get or a ready hold has every state member of its variant, local or remote
- holds subscribe to their variant's state; per-member opt-out with state: false
- remote binding keys use canonical identity; client holds carry an opaque origin
- shape frames no longer carry codeVersion
- self.state(name, schema, { init }) loads before the object goes live
- the principal type is a parameter; createHost self, connect and route principals are required
- remote contracts declare their host (hostNamed, hostFamily); get no longer takes at
serve(Type, factory, { lifetime: "host" }) compiles only for a local type whose input has no required fields, and
not together with linger; the runtime check stays as a backstop. ServeOptions is a union (ReferencedServeOptions,
HostServeOptions), picked per contract by ServeOptionsFor<C>.
A host that wrote to a peer process which had just exited (an ack timer firing after the worker's exit, before the
channel's disconnect) got EPIPE as an unhandled 'error' event. IpcLink now passes a send callback and ends the link on
failure; host-manager control messages to a gone peer are dropped (its exit reports it).
- A child that says hello during a cluster reload is fenced at once and reconciled after the reload ends, to the
committed code version (it was marked in sync without voting); a running reconciliation also waits for the reload.
- A child retires a code version only after its parent did (spec section 12): the coordinator retires before it
prepares, and children learn already retired versions at hello and before a reconciliation's prepare.
- Forced retirement aborts the leftover roots of the old code version, relayed calls included, before it closes the
resources only that code used.
- A task's replacement starts after the old run has ended, at most taskStopDeadline later.
- A kept resource adopts the newest declaration's close at commit.
- Disposed objects and old code generations are released: Ref bindings are weak, finished disposals leave the dying
table, stopped tasks are dropped, and contexts link abort signals without AbortSignal.any (which kept linked signals
and their listeners reachable).
- Facets get FacetApi.signal, aborted when their run is replaced, removed, aborted or the host closes.
- Regression tests for each, including a garbage-collection test of old code generations.
- self.state(name, schema, initial, { version?, migrate? }) returns the cell; the state version and migration live on
the cell (ServeOptions.state is gone). self.derive(sources, fn) has no name and is recomputed at commit.
self.resource(name, { version, deps?, exclusive?, open, close? }) (rev is version). self.task(fn). self.on(name,
source, [select,] handler) replaces listen/listener. cell(), derivation(), resource(), listener() and the
declaration maps are gone; a name declared twice in one factory run is an error.
- A listener on state receives the current value at start and after every reload: handlers must reconcile.
- method(args?, result?): an omitted result is void, omitted arguments make a no-argument method called as
ref.name(ctx) and implemented as (ctx) => ...; the wire omits both.
- References are Ref<C> (was Handle<C>), objects ObjectImpl (record.ts is object.ts), ImplementationOf<C> (was
BehaviorOf<C>), ResourceDeclaration (was ResourceSpec); epochs are code versions (codeVersion in Ctx, RootOptions,
Shape, Host, frames and reload reports). Docs say object and reference.
- A remote object's variant is named variant (was kind): factories return { variant }, references expose ref.variant.
- Links announce a protocol version (PROTOCOL_VERSION = 2) in hello; peers of different versions close the link with
reason protocol-version <n> and report it through LinkOptions.onProtocolMismatch.
- ServeOptions.lifetime ("referenced" | "host") replaces eager: a host-lifetime object is local, has an empty input,
exists once per host and lives until the host closes; awaitEager is awaitHostObjects. A reload that makes a type
referenced releases the host's own reference.
Peers acknowledge processed frames (ack, announced with acks in hello). While a window of frames is unacknowledged, state frames wait at the sender, coalesced per subscription, instead of queuing in front of call results; slots that outgrow their bound become resets, or a downstream resubscription on relays. AcceptOptions gains maxCalls, answering calls beyond it with unavailable/too_many_calls. Link.send returns the encoded frame size.
forkDriver spread process.env into the child, so a child host that forks its own children (or a test run inside a p2 worker) passed its lock on, and children without a lock of their own failed with "locked".
Call args, results and state snapshots whose JSON exceeds 64K characters are deflated
(level 1): over IPC as z: [field, base64], over WebSockets as a binary message
(envelope plus deflated payload). A relaying host forwards the bytes as received; the
host that uses the payload inflates it. Browsers inflate with DecompressionStream in
order; the p2 TUI launcher turns packed messages back into text frames. Servers send
packed frames to clients but refuse them from clients.
On the 8b6ab5cc session: first view 523 KiB -> 78 KiB, all history 23.5 MB -> 4.9 MB.
Frame, RootRef, WireError and the reload types are derived from FrameSchema instead of
being declared twice. Error codes, contract tokens (Description is now a schema in
contract.ts) and ops are checked as typed; ops stay unknown until the delta decoder
validates them.
A facet listed before another facet whose directory it imports from loaded a second copy of that module; register
all roots first so every facet of one load shares one copy, whatever the order.
A deep TMPDIR, such as ./test.sh's below macOS's per-user one, made the
lock socket path exceed 104 bytes and listen() failed with EINVAL. Fall
back to a private per-user directory under /tmp when the path does not fit.
7f022043f added packages/chord/node_modules, a symlink from a local worktree. The ignore pattern matched only
directories, so a node_modules symlink could be committed; it now matches files and links too.
A route that returned a dead link (a child that exited before its link was wrapped) left a closed connection in
the host, so every later call to that host name failed with unavailable until restart. Such a connection is not
added, closed children are skipped when routing by name, and no ping timer is started for it.
Chord: README rewritten for the new runtime; notes.md and requests.md removed, their open limits and
decisions folded into spec.md section 12; guide and spec status lines updated. Durable: drops the
pico-v5 handoff and delta findings, renames the Pico spec, and uses current Chord terms. Delta README,
issue categories, and PI_RADIUS_GATEWAY updated.
Moves chord2 (contracts, objects, cells, roots, plugins, hot reload, memory/IPC/WebSocket links)
from p2 into packages/chord, with its tests (now on vitest) and docs. The root export is the new
runtime plus the shared base (Context, JsonValue, JsonRepresentation, Draft, copyJson, isJsonValue,
replicatedState and its types). ./node now holds the host manager, child control, process locks,
and nodeLoader. ./context and ./delta are unchanged; ./bundler is removed.
replicatedState stays a standalone module with the same exports. It no longer exposes the exact-op
subscription the removed service provider used, so listener failures of mutable states are always
reported asynchronously. The package depends on typebox instead of esbuild. The wire format stays JSON.
The delta tracker planned every structural array change with a walk over all
base elements; inserts and removals that keep the remaining elements in order
now get the same plan from the changed ranges alone. applyImmutable built an
appended array as a copy that the insert then grew, a second copy; an append now
builds it with one concat. Ops, values, immutability and errors are unchanged.
32,000 one-item appends, one commit each: 11.3 s -> 1.4 s
(npm run bench:document-append in packages/durable).
Session workers now host a durable Harness over a per-session sqlite storage.
The server keeps each session in <sessionDir>/<id>/ with meta.json and
session.sqlite and never opens the storage itself. Workers hold retirement
while the Harness task graph has live tasks and resume interrupted work on
open.
Transcript serves the root conversation's durable view directly, Models
follows the conversation's agent document, and AgentController maps prompt,
steer, follow-up, queue cancellation, abort, and compaction onto the root
conversation, plus waitForPrompt. navigate, nextRun, and resume are dropped
(see experimental/services/README.md TODO). The durable TUI and the worker
share harness-setup.ts.
pi-server exports its own SessionMetadata and no longer depends on
pi-agent-core. Chord accepts readonly JSON types in service contracts without
walking them, which recursive JSON types like pi-ai's made too deep.
Addresses GHSA-82fw-gwwq-j7x9 (vitest mocker path traversal) and
GHSA-3wwx-pv8p-q78v (undici 6.x permessage-deflate DoS). Both are
dev/example-only; the shipped coding-agent shrinkwrap is unchanged.
Replace the tsgo native preview with typescript@7.0.2 and port the check scripts to the TS 7 API. Target ES2024, drop useDefineForClassFields: false and unused decorator options, and enable verbatimModuleSyntax. Replace tsx with node plus the source resolver hook, passed to --import as a file URL.
closes#9965
Replace the alternate tracker implementations with the optimized immutable overlay, validate draft placements as strict JSON, and document the ownership contract. Consolidate tracker tests and benchmarks around the canonical implementation.