v0.7.6
Async-resume regression repair. Closes the most awkward intermediate state on the entwurf surface — short spawn defaulting async (0.7.0, ad4413e) while long resume blocked the parent turn (Phase 0.5, agent-config e5aa5a1, 2026-04-24) — and restores the pre-Phase-0.5 native pattern across both the native pi tool surface and the MCP bridge surface that pi-shell-acp Claude (and any other replyable pi-session caller) actually uses. Verified live across Claude, Codex, and Gemini (Hard Rule #7), plus a backend-agnostic handler-level proof + the external rejection path.
Changed
-
entwurf_resumenative default flippedsync→async(Phase A). Restores the pre-Phase-0.5 behavior where long-running resumes (review / research / build) detach and deliver completion as a followUp message instead of blocking the parent turn. The async branch (pi-extensions/entwurf.ts:929-1137at the time) was already alive and unchanged — only the schemadefaultand theparams.mode ?? "..."runtime fallback were inverted. This completes the 0.7.0 axis symmetry that left resume on the Phase 0.5syncdefault — producing the inverted state where short spawn (often <5s) detached while long resume blocked. Sync stays available asmode="sync"opt-in for short status-check resumes (<5s). -
MCP
entwurf_resumeexposesmodewith conditional default + replyable gate (Phase B Step 3). The user-facing path: pi-shell-acp Claude and any other replyable pi-session caller of the MCP bridge now get async by default; external MCP hosts (Claude Code standalone, Codex CLI, Gemini CLI) stay on sync because they cannot receive followUp delivery. The discriminator is the existingbuildSendSenderEnvelopereplyable status — the same gateentwurf_sendalready uses forwants_replyrejection. A staticdefault: "async"would have inverted external-host UX; the conditional default closes that. Explicitmode='async'from an external host is rejected with the canonicalENTWURF_RESUME_ASYNC_REJECT_REASONtext, mirroring thewants_reply=truerejection pattern. Implementation note: the MCP handler does NOT clone the async launcher — it delegates back into the parent pi session via a newspawn_async_resumeentwurf-control RPC (Step 2), so completion delivery stays in the pi extension layer where it belongs ("this bridge is not a second harness" invariant). -
entwurf_resumecwdis sync-only at the MCP surface (silent-ignore guard). The async launcher uses the saved session header cwd as authority (#9); the previous (pre-0.7.6) MCP schema acceptedcwdbut the async path silently dropped it. Now the handler rejectseffectiveMode='async' + cwdexplicitly with a canonical reject reason so callers do not believe their override took effect when it didn't. The replyable check still fires first when both could apply — callers see the more fundamental wiring break before the cwd detail.
Added
-
pi-extensions/lib/entwurf-async.ts— shared async resume launcher + state (Phase B Step 1). Hoists the async resume body (the 235-line block formerly atpi-extensions/entwurf.ts:929-1137) plusactiveEntwurfs,AsyncEntwurfInfo,findEntwurfSession,isProcessAlive, andENTWURF_ENTRY_TYPEinto a single library module. Both the nativeentwurf_resumetool and the new entwurf-controlspawn_async_resumeRPC import the samespawnEntwurfResumeAsync(...)launcher and read/write the sameactiveEntwurfsMap —/entwurf-statussees every async task regardless of which surface spawned it (SSOT). The launcher accepts callbacks (appendActiveEntry/deliverCompletion) rather than depending on ExtensionAPI directly, so the lib stays platform-neutral and each callsite supplies its own parent-session notification surface.shellQuotesource parity now spans three sites (addedpi-extensions/lib/entwurf-async.tstocheck-shell-quoteSOURCE_SITES). -
spawn_async_resumeentwurf-control RPC (Phase B Step 2). New command in the entwurf-control dispatcher:{ type: "spawn_async_resume", taskId, prompt, host? }→ respond with{ taskId, originalTaskId, sessionFile, pid, text }on success, propagate the launcher's throws (Identity Preservation Rule, missing session file, missing cwd authority) verbatim as RPC errors. Lets the MCP bridge surface dispatch replyable async resumes by delegating into the parent pi session's extension context instead of cloning the launcher. -
check-async-resume-gatedeterministic gate (Phase B Step 4). Newscripts/check-async-resume-gate.tsexercises the conditional-default + cwd-silent-ignore logic — 16 assertions across 6 mode-resolution cases (replyable/external × {omit, sync, async} combinations) + 7 invariants (canonical reject text shape, no-silent-downgrade regression guard, defensivereplyable: undefinedhandling, cwd guard ordering). Resolution logic lives inmcp/pi-tools-bridge/src/resume-mode.tsso the gate can import it without triggering the MCP server'smain()side effect. Wired intopnpm checkbetweencheck-plugin-prompt-formatandcheck-models. No spawn, no socket, no API cost. -
smoke-async-resumelive gate (Phase B Step 5, three-backend axis). Newscripts/smoke-async-resume.sh(432 lines, pattern fromsession-messaging-smoke.sh). Six cases:- A.async.{claude,codex,gemini} — disposable tmux pi session per backend, prompt the backend procedurally (no identity claims) to chain MCP
entwurfsync-only spawn →entwurf_resume(mode='async'), assert "Resume spawned (async)" ack AND "🏁 resume … completed" followUp in the pane. This proves the replyable MCP → control-RPC → native async launcher path on all three backends. The omitted-mode conditional default itself is pinned bycheck-async-resume-gate; the live gate uses explicitmode='async'so model prompt-following cannot hide the async branch. - D.direct_stdio_async_handler — handler-level proof independent of any backend's prompt-following: sets PI_SESSION_ID/PI_AGENT_ID env directly, stdio-calls the MCP bridge to spawn an entwurf and then
entwurf_resume(mode='async'). Asserts the async ack text returns. Separates "handler correctness" from "backend prompt-following capability." - B.external_async_reject — external (no PI_SESSION_ID) + explicit
mode='async'→ reject with canonical text. - C.external_autosync_shape — external + mode omitted → auto-sync path reached (asserts via
session_not_foundon a synthetic taskId).
Reference baseline run (2026-05-27, artifact
/tmp/smoke-async-resume-20260527-194013.json): 6 PASS / 0 FAIL / 0 SKIP, three-backend equality closed, strict fail-closed (❌ resumeis FAIL, only🏁 resume … completedis PASS). The final smoke also captured a useful boundary lesson: user-role identity assertions like "you ARE replyable" are rightly rejected by Claude as prompt-injection-shaped; the smoke prompt is therefore procedural and lets the MCP env decide replyability. Cost for the final full run was ~$0.11 (overall Phase B evidence formation ~$0.55). Required before every release that touches the entwurf surface; not in the deterministicpnpm checkchain because it spends ACP turns. - A.async.{claude,codex,gemini} — disposable tmux pi session per backend, prompt the backend procedurally (no identity claims) to chain MCP
Verification
pnpm check— 13 deterministic gates pass (check-mcp,check-shell-quote17 (3 source-parity sites + reference body + 13 behavior cases),check-plugin-empty-final-recovery34,check-plugin-prompt-format22,check-async-resume-gate16,check-models3 passes,check-backends136,check-registration8,check-dep-versions6,check-sdk-surface0 unannotated casts,check-pack52-file invariant — was 48 in 0.7.5, +4 forpi-extensions/lib/entwurf-async.ts,mcp/pi-tools-bridge/src/resume-mode.ts,scripts/check-async-resume-gate.ts,scripts/smoke-async-resume.sh)../run.sh smoke-async-resume— 6 PASS / 0 FAIL / 0 SKIP across Claude + Codex + Gemini (final strict fail-closed baseline reference run above).- Three-backend equality (Hard Rule #7) — Claude, Codex, Gemini all GREEN with live evidence.
Repository
- Commit chain (
main):ff85fa9Phase A native default flip →4b89b81Step 1 lib extraction →0107ce4Step 2 control RPC →684c97bStep 3 MCP mode + conditional default →69ff04bStep 4 deterministic gate →b28d1bbcwd silent-ignore fix →b6ef765Step 5 live smoke →24ee1290.7.6 release docs/version →b98774bpre-release surface-doc alignment + fail-closed smoke →d198da0procedural smoke prompt. - AGENTS.md, README, VERIFY.md, NEXT.md, and MCP/source comments updated to the final 0.7.6 surface: MCP
entwurfspawn remains sync-only; MCPentwurf_resumeis conditional-default async for replyable pi-session callers and sync/reject for external non-replyable hosts. - NEXT.md — async-resume repair moves out of active state. Phase A and Phase B complete; next focus returns to Asymmetric Mitsein / session-continuity hygiene.