Skip to content

v0.11.0

Choose a tag to compare

@junghan0611 junghan0611 released this 16 Jun 06:14

0.11.0. The entwurf v2 dispatch substrate (#35): a single additive verb, entwurf_v2 / runEntwurfV2, that unifies dispatch to existing garden citizens behind one decider which reads peer liveness as a fact and picks transport from a frozen table keyed on target state AND intent (not state alone): live pi + fire-and-forget → control-socket send, dormant pi + owned-outcome → spawn-bg resume, active self-fetch + fire-and-forget → meta-mailbox enqueue; every other state×intent pair (live+owned, dormant+fire-and-forget, self-fetch+owned) is an honest reject. It does not mint new siblings — fresh sibling creation stays the v1 entwurf verb (0.12 cutover lane); the spawn-bg resume row resumes an already-identified dormant citizen, it does not create one. Built bottom-up as pi-only TypeScript substrate (Stage 0): contract freeze → per-gid dispatch lock → pure decider → release-policy reducer → send/spawn hands → production deps assembly → pi-native + MCP entwurf_v2 tool. On top of it the spawn-bg resident lifecycle — the headline guarantee of #35 — was proven LIVE for the first time (a real pi --entwurf-control child stands its socket up, resumes a dormant Entwurf session, and does a model turn), and that first LIVE run surfaced and closed a production contradiction in the resident-name guard. Finally a v2-only mode (PI_SHELL_ACP_V2_ONLY=1) hard-refuses every v1 entrypoint so v1 can be turned off ahead of its 0.12 removal. Scope is Stage 0 (pi-only substrate); Stage 1 (Claude↔Claude live) is explicitly out of scope. Release-gate restructured before the cut (2026-06-16): the pi floor was bumped to >=0.79.4 (runtime parity) and the live gate was split into a two-tier MUST (release-blocking, owns the exit code) + BEHAVIOR (advisory, model-in-loop autonomous MCP tool-selection) summary. The prior flat LIVE=1 → PASS=18 run (log …20260615T152058) predates BOTH changes and no longer describes the current tree. Fresh LIVE=1 release-gate on the 0.79.4 + two-tier tree (2026-06-16, log …20260616T141023): MUST PASS=17 FAIL=0 SKIP=0 (necessary condition met) + BEHAVIOR PASS=1 FAIL=1 — the lone BEHAVIOR-FAIL is sentinel (cell4/5 S7 Bash-bypass, advisory/non-blocking); resident-garden-guard positives flipped FAIL→PASS vs the earlier run with zero code change, confirming the advisory scoping. "green" applies to the MUST tier only.

Added (0.11.0 entwurf v2 — unified dispatch verb)

  • entwurf_v2 / runEntwurfV2 — one additive dispatch verb over three transports. The decider reads a peer's liveness as a fact (the entwurf_peers fact surface) and chooses transport from a frozen table keyed on state AND intent: alive pi + fire-and-forget → control-socket send, dormant pi + owned-outcome → spawn-bg resume, active/deliverable self-fetch + fire-and-forget → meta-mailbox enqueue; the complementary pairs (live + owned, dormant + fire-and-forget, self-fetch + owned, inactive self-fetch + fire-and-forget) are honest rejects, not silent fallbacks. Built as pure substrate first — the contract freeze, a per-gid lockfile primitive (F2), the pure decider, the release-policy reducer, the control-send + dead-control-send hands, the enqueue-only mailbox send body, the pure execute-router and the top-level decide→execute join — then surfaced as both a pi-native tool and an in-process MCP verb, plus doctor surface sanity and the PI_ENTWURF_PREFIX_ROOTS operator-policy SSOT. Each slice landed behind its own deterministic gate.
  • v2 dispatch reachability + lock SSOT and a 3-cell LIVE matrix sentinel. check-entwurf-v2-matrix is the reachability + lock-policy table; smoke-entwurf-v2-matrix-live is a 3-cell LIVE sentinel (control / mailbox / guard) wired into release-gate after the bridge check with an honest LIVE!=1 skip.

Added (0.11.0 entwurf v2 — spawn-bg resident lifecycle, #35 headline)

  • smoke-entwurf-v2-spawn-resume-live — the full spawn-bg resident loop, proven LIVE (22 checks). A real saved Entwurf session is resumed by a detached pi --entwurf-control child that stands its control socket up, does a model turn (nonce echoed in both the user and assistant turns of the session JSONL), releases its dispatch lock exactly once, and cleans up with zero litter. This is the first time the production spawn path (a detached spawn() pi resident — not the v1 tmux smoke fixture) was exercised end to end, closing the acceptance debt step 5c-3c had deferred to "the matrix proves it."
  • PI_SHELL_ACP_V2_RESUME_RESIDENT_SESSION_ID — sessionId-bound authorized Entwurf-child resident. The first LIVE run surfaced a production contradiction: a v2 spawn-bg resume launches its child with --entwurf-control, but maybeSetResidentName refused any entwurf-tagged resident (the tag is the entwurf_resume marker), so the resume self-terminated before doing any work. Resolved by narrowing the invariant, not dropping it: operator residents still reject the entwurf tag, but a v2 spawn-bg resume child is authorized by the sessionId-bound marker its launcher plants, keeps its tag, and stays re-resumable. The marker is a zero-import leaf so it is root-safe from both the .js-import (entwurf-control) and .ts-import (entwurf-v2-spawn-production) compilation worlds.

Added (0.11.0 entwurf v2 — v2-only mode)

  • PI_SHELL_ACP_V2_ONLY=1 — hard-refuse every v1 entwurf entrypoint ahead of v1 removal. A zero-import leaf helper (entwurf-v2-only.ts: isV2OnlyMode true only for exact "1", checkV1EntwurfAllowed pure return, assertV1EntwurfAllowed throw wrapper) is the single source of truth; when PI_SHELL_ACP_V2_ONLY=1, each of the 10 v1 entrypoints (9 surface groups — /entwurf tool + command count as two) refuses before any side effect through its own existing hard-refusal channel (tool throw, command notify, isError result, RPC respond(false), startup error report, MCP textErr). The MCP entwurf_resume handler and the control RPC spawn_async_resume are both guarded so the socket path cannot bypass the MCP guard. v1 code is neither deleted nor unregistered — invocation refusal only; the 11-scenario v2 replacement and v1 removal are the 0.12 lane. check-entwurf-v2-only (28 checks) proves the helper contract, all 10 guard sites, the double guard, and that the entwurf_v2 core stays flag-clean.

Added (0.11.0 entwurf v2 — fact surface)

  • listEntwurfFacts / entwurf_peers — the entwurf fact surface (4-value liveness). A pure PeerFact core (garden citizens from meta-records + record-less control sockets, each with alive / dead / indeterminate / unsupported liveness) assembled bottom-up with socket-axis hardening (symlink / dir-read / malformed diagnostics). It reports facts, never routing verbs — the dispatch decision is computed later by the v2 contract, not here.

Added (0.11.0 meta — delivery authority on v2 identity + trust)

  • Meta delivery authority cut to v2 identity + state store (3D-2 … 3D-4). A v2 identity-normalization gate, a dual-read identity seam with delivery-agnostic consumers, the live mailbox receipt dual-write to the state store, and backend capability sourced from the registry rather than a const. The entwurf_v2 contract surface was frozen and gated before the verb was wired (step 4-pre).
  • project_trust handler + inherited-trust preflight (Trust 2층). The pi trust preflight carries inherited-trust evidence and a deny formatter (F5a/N3b); a project_trust handler adds an inherited-distrust escape.

Added (0.11.0 entwurf v2 — SE active-receiver deliverability)

  • The SE-1/SE-2 deliverability seam — honest pi-native + meta-self replyability. A meta-receiver presence marker, MCP + pi-native entwurf_send mailbox-fallback gates, a mailboxConversationalDeliverable predicate, a guarded mailbox-enqueue wrapper, and the required v2 active-receiver deliverability seam, so a send to a deliverable self-fetch meta citizen (an active-receiver claude-code session) with no live socket can fall back to the mailbox transport, while direct-inject (pi) and inactive-receiver targets reject without leaving mailbox garbage instead of falsely succeeding.

Fixed (0.11.0 entwurf v2 — deliverability honesty, consumer-visible)

  • entwurf_send / entwurf_self stop reporting false delivery to unreachable targets. Previously a send to a citizen with no live control socket, or a replyable: true self-report, could be emitted even when the target could not actually receive — a socketless / record-less pi session, an inactive (human-terminated) meta receiver, or a direct-inject (pi) target — leaving a ✓ delivered signal and stray .msg mailbox files behind. Now such targets are rejected honestly (no enqueue, no mailbox garbage), and entwurf_self reports replyable: true only when a real inbound path exists. This is an observable change for entwurf_send / entwurf_self consumers, but it is a honesty bug-fix, not a behavior regression — a previously "successful" send to a dead/inactive target was always a false positive. Gates: check-entwurf-deliverability, check-entwurf-mailbox-guard, check-entwurf-self-address.
  • entwurf_v2 now reaches a record-less but live pi control socket (socket-only fire-and-forget target). entwurf_peers lists a live pi --entwurf-control session as alive whether or not it has a meta-record, but the v2 production resolveTarget previously rejected any record-less gid as bad-target — so an entwurf_v2 to an operator-greeted live pi peer failed while a legacy entwurf_send to the same gid succeeded (a fact-surface ↔ dispatch contract gap; the bad-target was masked whenever a caller fell back to v1). v2 now accepts a record-LESS gid whose canonical control socket is a confirmed non-symlink socket as a socket-only pi endpoint: a PROBE-FREE presence hint (one lstat, no connect — the shared socket-discovery classifier, so listing and dispatch cannot drift) promotes it to a fire-and-forget control-send target only. It is never an owned citizen — with no record there is no cwd/launch authority, so owned-outcome is refused before any lock and spawn-bg can never open into it; a record-less dormant resume stays out of scope (0.11.1). Gates: check-entwurf-v2-decider (socket-only fire-and-forget execute / owned-outcome pre-lock bad-target / dormant·indeterminate honest reject, never a spawn), check-entwurf-v2-production (record-absent + live socket → execute; symlink/absent → bad-target).
  • Mailbox payload guidance is now explicit at the tool schema boundary. entwurf_send and entwurf_v2 message fields carry a broad hard cap (maxLength: 16000) plus guidance to send one compact atomic message; larger reviews/logs should be written as a file/artifact and sent as a path plus digest. This avoids encouraging multi-part chat sends on a self-fetch mailbox whose doorbell is edge-triggered and may coalesce. The paired smoke-meta-mailbox regression now proves the honest residual guarantee: if multiple .msg bodies do queue up, one entwurf_inbox_read drains the whole backlog in order, stamps one read receipt, archives every body, and re-read is empty. This is drain-scoped evidence, not a claim that every rapid send gets its own doorbell wake.

Changed (0.11.0 — entwurf surface affordance, consumer-visible)

  • entwurf_v2 is now the canonical garden-id delivery surface; entwurf_send is demoted to a lower-level direct-send compat tool (tool descriptions reworded). A live incident exposed a surface affordance bug, not a function bug: when an agent holds a garden id and both entwurf_send and entwurf_v2 are visible, the name "send" pulls it to entwurf_send — but a bare garden id does not reveal whether the target is a live pi session or a Claude Code meta-session, so the agent picks the wrong transport and fails (it poked a live-socket path at a meta-session that needed the mailbox). The fix is at the affordance layer: the entwurf_v2 tool description (MCP + pi-native) now leads with "canonical delivery surface for garden ids — when unsure which transport, use entwurf_v2", and the entwurf_send description (MCP + pi-native) now leads with "lower-level direct control-socket compat tool; for garden-id delivery prefer entwurf_v2; use this only with a known live pi socket / for get_message·clear debug". README's ACP-backed tool list now includes entwurf_v2 + entwurf_inbox_read and states the rule plainly: send/reply → entwurf_v2, create → v1 entwurf. No runtime dispatch behavior changed — this is description/affordance + docs only (no deterministic gate snapshots the description text; re-validated by pnpm check). The deeper convergence (folding entwurf_send delivery into entwurf_v2, keeping only its debug actions, or unregistering v1 send under v2-only mode) is a 0.11.x / entwurf-repo lane.

Changed (0.11.0 — dependencies)

  • pi floor bumped to 0.79.4 (runtime parity). Requires pi >= 0.79.4 (@earendil-works/pi-ai / pi-coding-agent / pi-tui peer floor >= 0.79.4; devDep pins, lockfile, the check-pi-runtime-version FLOOR constant, and the check-pack-install peer pins all moved to 0.79.4 so the repo-local deterministic gates and the operator pi runtime stop diverging). 0.79.4 was confirmed to carry no deterministic regression vs 0.79.3 (probe + forced spawn A/B); the floor bump is a transport/API-compatibility statement, not a claim that 0.79.4's model-in-loop behavior is fully deterministic — Claude Sonnet's MCP-vs-Bash autonomous tool-selection remains non-deterministic and is tracked in the release-gate BEHAVIOR lane (below). The substrate carries typecheck-fence and runtime guards that keep the .ts / .js dual-import worlds honest under node --experimental-strip-types.
  • release-gate split into a two-tier MUST + BEHAVIOR summary. The single flat must-pass list was divided so that v1/v2 function (deterministic + programmatic gates: pnpm check, smoke-async-resume, smoke-entwurf-resume, check-native-async, check-bridge, the v2 matrix/spawn-resume LIVE gates, the resident-garden-guard negative/id-safety + /gnew zero-token half, …) stays release-blocking (MUST, owns the exit code), while the v1 behavior probes that depend on the model autonomously driving the MCP entwurf surface — sentinel (6-cell diagonal spawn) and resident-garden-guard positive (post-/gnew entwurf_self identity turn + positive garden model turn) — move to an advisory BEHAVIOR lane that is surfaced but never blocks the cut. This is a v1 behavior advisory, not "v1 unsupported/broken": the v1 tools themselves remain proven by the MUST programmatic gates. The S7 Bash-bypass detector (sentinel-runner.sh) stays a hard FAIL inside the BEHAVIOR lane — a Bash/Terminal/pi-CLI bypass is never relabelled a pass, only made non-blocking. A residual bypass after prompt strengthening is a 0.11.x usability item, not a 0.11.0 blocker. Rationale: the BEHAVIOR lane is new 0.11-era coverage (sentinel entered the release-gate 6592c5f 5/29; resident-garden-guard/T3 440afba/7d45346 6/4) — it did not regress, it newly measured a v1 model-in-loop weakness the older gate never looked at.

Deprecated

  • plugins/openclaw is deprecated and unmaintained (2026-06-10). The OpenClaw plugin layer no longer has a reason to exist: Claude and Gemini now support ACP natively (Claude on a credit basis from 2026-06-15), so routing them through an OpenClaw wrapper buys nothing. The pi-shell-acp bridge itself is unaffected and continues to support Claude / Codex / Gemini directly — only the OpenClaw adapter context changed. The npm package @junghan0611/openclaw-pi-shell-acp@0.0.1 is marked deprecated on the registry (not unpublished); the source under plugins/openclaw is frozen for reference. No further ClawHub / npm publishing. (The @junghanacs ClawHub publisher handle was separately recovered via clawhub#2346 and is retained for other uses.)