Skip to content

fix(engine): turn-end delivery seal + deaf-sweep escalation - #1

Merged
davidhs26 merged 215 commits into
mainfrom
fix/turn-end-delivery-seal
Aug 21, 2026
Merged

davidhs26 merged 215 commits into
mainfrom
fix/turn-end-delivery-seal

Conversation

@davidhs26

Copy link
Copy Markdown
Owner

Qué

Sello de entrega al final de cada turno + barrido de rooms al escalar el tripwire de sordera del registry. Cierra de raíz la clase "el turno terminó en el journal pero el teléfono nunca ve el mensaje final" (3 incidentes: 2026-08-18 ×2, 2026-08-21).

Por qué

El sync a upstream v0.2.17 retiró el tripwire escalado de PR zeronsh#7 ("superseded por gap-repair/delivery durable"). La auditoría del incidente del 21/08 mostró que la apuesta era parcialmente falsa — quedaron tres huecos:

  1. PROBE_QUIET_DEFAULT = 900s: un socket chat2 sordo justo al final de un turno deja las filas finales sin ackear hasta 15 min.
  2. El checkpoint (HTTP, independiente del WS; la única cura para un lector con cursor retenido sobre un row gap — y lo que un restart del engine aplicaba "de casualidad" vía bootstrap) nunca se postea al finalizar un turno.
  3. El redial del tripwire del registry jamás barría los chat rooms.

Cómo

  • DocHost::seal_turn — en cada finalize (todo Done + quiesce-park con segmento visible): probe() inmediato (sordo → deadline 10s → redial + flush) y checkpoint "turn-end" salvo room probadamente cubierto (server_known && checkpoint_size>0 && cursor<=checkpoint_seq && pending_pushes==0).
  • DeafEscalationHook — el redial del tripwire spawnea (nunca inline bajo los locks room/presence; lección AB-BA de la review de PR 申请 alpha 内测资格 —— eda91371@gmail.com 提示账户不存在 zeronsh/zeron#7) probe_open_chats(). Captura Weak del grafo: la versión strong ciclaba el grafo y local_first::online_runtime_shutdown_stops_edge_workers_and_retires_the_graph lo cazó.
  • Tail sidecar — el PUT loguea rechazo/error (antes let _ = silencioso).
  • docs/chat2-sync.md C3 actualizado.

Tests

cargo test -p zeron-engine -p zeron-sync: 196 passed, 0 failed (el leak test falló contra la primera versión del wiring y validó la corrección a Weak).

Review adversarial independiente con Kimi K3 en curso; findings se aplican antes del merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GyJydy2tozB5X1X8ZvbdKQ

wing-anara and others added 30 commits August 15, 2026 04:20
On Linux the window had no minimize/maximize/close at all wherever the
compositor ends up in client-side-decoration mode (GNOME/Mutter has no
SSD support; gpui's WindowControlArea hit-testing is Windows-only, so
the Windows caption cluster never applied). Meanwhile leaving
window_decorations unset requested SERVER decorations, which stacked a
compositor titlebar on top of zeron's own chrome under sway/labwc.

- Request client decorations on Linux (WindowOptions.window_decorations)
  to match the unified-titlebar design; the compositor can still
  override via xdg-decoration and the shell re-resolves every frame.
- Draw zeron-styled caption buttons (24px cluster rhythm, hand-drawn
  linear glyphs) with explicit handlers - minimize_window / zoom_window /
  remove_window - since the native control-area hit-test is inert on
  Linux. Mounted at the root like the Windows cluster so they survive
  gates and the splash.
- Follow the desktop's button layout (GNOME button-layout via
  cx.button_layout, observe_button_layout_changed): right side by
  default; "close:..." layouts put buttons on the left and the app
  cluster shifts past them at the shared 2px rhythm. Controls the
  compositor can't do (per wm_capabilities) drop out; close always
  stays. Server decorations draw nothing - no double controls.
- Reserve titlebar right padding for the caption strip (tabs strip,
  changes-pane header, settings bar) via titlebar_right_pad.
- Give gate/splash pages a real drag strip on Linux/Windows
  (titlebar_drag_region - the WindowControlArea::Drag surface alone is
  dead on Linux).

Rig-verified under labwc (CSD: all three buttons work - minimize
unmaps, zoom maximizes and flips to the restore glyph, close exits;
close hover shows the red wash) and sway (SSD: no double controls);
left-side layout verified end-to-end through xdg-desktop-portal-gtk
with button-layout "close:".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s-platform-fix

linux: window controls under client-side decorations
* feat: adopt external pi sessions into workspace (ID01-420)

Add pi_adopt module that discovers pi harness sessions created outside
the Zeron engine (CLI runs, pi-worker) and surfaces them as regular
workspace chats so every client (iOS included) can list and open them.

Discovery reads two sources:
- pi-acp session map (~/.pi/pi-acp/session-map.json)
- pi agent session store (~/.pi/agent/sessions/*/*.jsonl) as fallback

Each session becomes a Chat row with deterministic UUIDv5 id, resolved
space (created if needed), and idle Session row. Rows are idempotent —
re-scans never duplicate or rewrite existing rows.

Feature flag: ZERON_ADOPT_PI_SESSIONS=1 (off by default). With the flag
absent, the binary behaves identically to upstream.

Includes integration tests (2 passing) and proper CancellationToken-based
shutdown.

* docs: notas de implementación y verificación de pi_adopt

* fix(pi-adopt): post-review fixes — skip adoption without space, downgrade map warn to debug

- If resolve_space returns None (create_space transient error), skip
  adoption for this scan instead of writing an invisible chat row
- Downgrade recurring session-map warning to debug (45s spam)
- Update NOTAS with adversarial review findings

* fix(pi-adopt): don't guess a dangling space_id after create_space

Review (kimi-coding/k3): if create_space no-ops on an existing
(device,path) and the re-read fails, the derived UUIDv5 would
dangle and SpacesSync's orphan sweep would delete the chat.

Also: run the scan in spawn_blocking, log skip reasons at debug,
and document the spec-5 warning→debug deviation.

---------

Co-authored-by: David <davidhsutton@gmail.com>
…only

Los workflows heredados apuntan a la infra de zeron.sh (R2/releases) y no
aplican al fork; quedan en workflow_dispatch. El gate del fork es ci.yml:
cargo check --workspace + tests del engine. (ID01-424)
Five to the top row, four to the reverse row, avatars mirrored into
assets/tweets. Marquee durations scaled (72s->117s, 80s->126s) so the
longer tracks keep the same scroll speed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resurrect the pre-ACP claude driver (ca05336^) and modernize it against
CLI 2.1.228, replacing the claude-agent-acp adapter for HarnessId::ClaudeCode:

- Permissions ride the stdio control channel (--permission-prompt-tool
  stdio, validated live: can_use_tool requests arrive and allow/deny
  responses are honored). Tools auto-allow (unattended parity);
  AskUserQuestion round-trips through the engine input bridge.
- Done is the CLI's own result frame, eagerly — background subagents
  never hold the turn. The CLI's native wake turn (second init on the
  same session id + second result, live-verified) flows through the same
  stream; the engine's existing parked-session resume path turns it into
  the done→Working→done wake with no engine changes.
- Subagent frames (top-level parent_tool_use_id) are wrapped in the new
  AgentEvent::Subagent variant instead of dropped — never folded into
  the parent feed (they'd split contiguous text around phantom tool
  calls, and their message boundaries must not rotate the parent's
  assistant message id). The parent doc fold ignores them; routing to
  per-subagent docs lands with the subagent-visualization commit.
- --thinking-display summarized (newer models emit no readable thinking
  otherwise) alongside --verbose/--include-partial-messages.

Tests: fake-CLI scenarios (init dedupe, tagging, eager-done+wake, ask-
user round-trip, steering, interrupt escalation, error mapping), a
verbatim live-captured 2.1.228 background-subagent frame replay, and an
ignored live-CLI smoke.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ends at state

Three cold-open wire cuts for thin links (450kbps target):

- iOS caps background room dials at 8 (attention order). All 46 sessions
  still hydrate from disk instantly; sidebar status rides the registry
  room; an undialed chat connects the moment it's opened. Foreground and
  path-recovery kick sweeps skip dial-held stores — no more 46-socket
  stampede on every flap.
- Both clients send the rows request BEFORE the checkpoint HTTP fetch and
  stream the backfill in parallel with the download, buffering rows until
  the import lands (order pinned by test). The download used to push the
  whole join back by its own duration.
- Both clients flush pending pushes right after the state answer instead
  of after full convergence — a message typed offline now lands ~2 RTTs
  after the socket does, not after checkpoint + backfill (server batchId
  dedupe makes replays no-ops; deadlock-shaped regression test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stry-first dial gating

On-device NLC (Edge profile) round 2 fixes:

- ChatRoomClient: one checkpoint download per chat that OUTLIVES socket
  generations (a saturated link trips the silence lease mid-download —
  pongs queue behind the blob — and the redial used to discard the bytes
  and start over, looping a fresh-chat open forever). Partial downloads
  Range-resume across attempts; download progress defers the silence
  lease (120s backfill deadline stays as the backstop); a completed blob
  applies even if its socket died (CRDT import + max-cursor are socket-
  independent), so the next session joins RowsOnly.
- RegistryClient.kick: don't redial while a handshake is in flight —
  foregrounded() fires on scene activation at launch and killed the dial
  mid-TLS every cold open, doubling the spinner (6.9s → measured single
  dial 4.5s on Edge). Mirrors ChatRoomClient's existing guard.
- AppModel.preloadSessions: warm chat dials wait for the registry to
  connect (10s cap) — the sidebar's own handshake+state gets the thin
  pipe to itself; open views still dial instantly via releaseDial.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ancement

The airplane-wifi fix. The WS needs 4+ serial round trips (DNS, TCP, TLS,
upgrade, hello) before byte one, and hostile middleboxes strip the upgrade
entirely — the user-reported 'never connects on the plane'. A plain HTTPS
GET is 1 RTT on a warm connection and indistinguishable from a webpage to
any proxy. So: push/pull/poke.

Edge (worker + DOs):
- GET /registry/{org}/rows?since= — the WS hello's exact delta answer
  (full/gcFloor rules shared with handleHello); ?beat=1 doubles as a
  presence beat. POST /registry/{org}/push — the WS push twin via a shared
  applyPushBatch core (validate, atomic apply, rows broadcast to live
  sockets); LWW clocks make replays no-ops.
- GET /chat2/{id}/rows?after= — one request collapsing connect→hello→
  state→rowsReq→backfill: u32-LE length-prefixed frames (state with
  frontier payload, rows, rowsDone), byte-identical encoding to the WS.
  POST /chat2/{id}/rows?batchId= — push twin, batchId-deduped.

Desktop (Rust): RegistryTransport/ChatTransport seams; with a transport,
construction resolves immediately (local-first), an HTTP pull bootstraps
in ~1 RTT while the socket dials, and every backoff cycle pushes pending +
pulls over HTTPS — a WS-hostile network degrades to ≤30s polling instead
of silence. Engine wires both via the existing bearer/reqwest plumbing
(registry: URL derived from the WS provider; chat: EdgeChatTransport).

iOS (Swift): WorkspaceStore pull-first bootstrap + 20s poll-while-down +
HTTP flush of pending writes; the header spinner now means 'server state
reached this session' (flips on the ~1-RTT pull, not the 4-RTT socket).
ChatRoomClient.pullSync: HTTP push of pending sends (permanent rejections
retire, mirroring the error-frame path), framed rows pull applied through
the exact socket path, checkpoint fetch shared via fetchInFlight.

Validation: new edge/scripts/pullpush-check.mjs green against wrangler dev
(push/ack/no-op replay, delta-at-cursor, presence beat, framed pull,
excludeOwn, batchId dedupe); existing smoke + chat2-check (65) + reset
suites green; zeron-sync 34, engine+rpc 180; iOS sim build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…shes only on edge verdicts

Pre-merge audit findings:
- With immediate-ready construction, the host's bootstrap heal could read
  all-zero placeholder stats 3s after construction (server state not yet
  known on a slow link) and upload a spurious full-doc checkpoint. New
  ChatStatsSnapshot.server_known gates it; the heal waits (bounded 2min)
  for a real state answer.
- iOS pullSync retired pending batches on ANY http 400/413 — a captive
  portal answering a POST with junk would silently drop a real message on
  exactly the networks this path serves. Retirement now requires a parsed
  edge error code (bad_push/too_large/empty), mirroring the WS error-frame
  discipline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resurrect the pre-ACP codex driver and modernize it against codex-cli
0.146.1 (the app-server API is experimental — the pin is noted in the
module header), replacing the codex-acp adapter for HarnessId::Codex:

- Done stays turn/completed (turn.status == "failed" now maps to an
  errored Done even without a turn.error; the error notification's
  nested params.error.message shape is accepted alongside the old flat
  one — both live-verified).
- Approvals ride the app-server request RPCs as before (wire policy
  "never" for unattended parity, stray requests round-trip as yes/no
  questions); the experimental item/tool/requestUserInput content
  questions now reach the input bridge and answer keyed by wire id.
- Subagents are full child threads. A registered child's notifications
  route through an EXPLICIT table (route_child_notification), pure and
  tested: item lifecycles AND message/reasoning deltas become tagged
  AgentEvent::Subagent events (live capture shows child threads DO
  stream deltas on this wire — token-level live transcripts without
  touching the rollout files), child turn/thread bookkeeping is
  consumed so it can never settle the parent turn, and unknown methods
  fall through to the parent path — fail open, never silent loss (the
  reference implementation shipped two bugs from a catch-all swallow:
  a child error left the agent running forever, and a swallowed
  serverRequest/resolved stuck approvals). Registration comes from
  subAgentActivity items on the parent thread; the root's own activity
  marker never registers (registering the root as its own child
  intercepted its turn/completed and hung the thread Working).

Live-validated: basic turn end-to-end (marker file written, clean
turn/completed) and a two-child fan-out capture archived under
tests/fixtures/codex/. Ignored live smoke included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
davidhs26 and others added 28 commits August 19, 2026 23:13
…eronsh#30)

* feat(engine): drain session-safe en shutdown (ID01-459)

C1: flag `draining` (AtomicBool en SessionsEngine) seteado por
EngineCore::drain_for_restart ANTES de cualquier interrupt y de
server.abort(); rechazo de despacho de runs nuevos con el texto exacto
'engine is draining for a restart' en DOS costuras: RPC QueueCommand
(frame de error estándar) y dispatch_inner (frames en vuelo). Steer a un
run vivo steerable y RPCs de observación siguen permitidos.
C2: wait AGGREGATE (poll del total de live runs, no por chat) con
deadline 120s default, env ZERON_DRAIN_TIMEOUT_SECS (>=0; 0=interrupt
inmediato). Vencido el deadline cae al teardown interrupt existente.
Logs greppeables: waiting N live runs / all completed / timeout fallback.
SPEC: ~/factory/SPEC-ID01-459-session-safe-deploys.md

* test(engine): suite drain — natural completion, rechazo QueueCommand, fallback interrupt, StopEngine (ID01-459)

Los 4 tests del criterio 1 del spec:
- sigterm_waits_for_natural_completion: el turno completa natural
  durante el drain (journal Done{Completed}, sin stamp aborted);
- queue_rejected_while_draining: error exacto por RPC y por dispatch
  seam + WatchSessions sigue streamando durante el drain;
- drain_timeout_falls_back_to_interrupt: turno colgado + deadline corto
  via ZERON_DRAIN_TIMEOUT_SECS -> stamp aborted + Done{Interrupted},
  teardown bounded;
- stopengine_shares_drain_path: daemon headless real, StopEngine ->
  exit 0 <=5s (mismo camino post-select que SIGTERM).

* fix(engine): drain gate rejects only commands that dispatch a new run (ID01-459 review)

QUEUE_COMMAND during drain: Run always rejected; Steer only without a
live steerable run (its fallback dispatches); Interrupt and RespondInput
pass (accelerate/destraban the in-flight turn). The dispatch seam in
sessions.rs still re-checks. Test: interrupt_and_respond_input_pass_while_draining.

* test(engine): drain fallback uses explicit deadline, drops env mutation (ID01-459 review)

The SAFETY comment on set_var was false — the StopEngine daemon test in
the same binary also reads ZERON_DRAIN_TIMEOUT_SECS. drain_for_restart
already takes the timeout explicitly, so the suite no longer touches the
global env at all.

* test(engine): rename drain natural-completion test, document SIGTERM gap (ID01-459 review)

The old name (sigterm_waits_for_natural_completion) over-claimed: the
test drives drain_for_restart directly, no real SIGTERM with a live run.
The known gap is now stated in the doc comment.

* fix(engine): sign-out skips the restart drain (ID01-459 review)

The spec freezes the drain-wait for SIGTERM + StopEngine only. A signed-
out runtime has nothing restarting: waiting up to the drain deadline (or
answering commands with 'for a restart') buys nothing — go straight to
the interrupting teardown.

* fix(engine): prefer SIGTERM drain over same-tick sign-out (ID01-459 review)

biased select so a deploy signal wins if sign-out is also ready.

---------

Co-authored-by: David <davidhsutton@gmail.com>
…nsh#32)

* feat(proto,engine): RunRequest.utility additive marker; title runs set it (ID01-491 C1+C2)

- RunRequest.utility: bool con #[serde(default)] + skip-if-false (misma
  convención que attachments/worktree): wire viejo parsea a false, JSON de
  runs normales no cambia (campo omitido). Test de round-trip.
- 35 literales RunRequest { del workspace rellenados con utility: false
  (cero cambio semántico; diff = inserciones únicamente).
- titles: title_run_request() extraído y testeable — utility: true,
  sandbox ReadOnly, reasoning Minimal, resume None. El prompt/prefijo
  NO cambian (congelados: pi_adopt matchea TITLE_PROMPT_PREFIX).

* feat(harness): ACP child de un run utility exporta ZERON_UTILITY=1 (ID01-491 C3)

- compose_child_command() extraído y unit-testeado: program+args+PATH+cwd
  + marker env. utility:true ⇒ ZERON_UTILITY=1 en el child (herencia
  estándar harness→pi-acp→pi; cubre steer/followUp del run, child por-run).
  utility:false/ausente ⇒ la var NO se inserta ni se blanquea (herencia
  intacta). Sin RPC nuevo, sin _meta, sin patch de pi-acp.

* feat(extensions): utilityRun = env ZERON_UTILITY=1 OR prefijo (ID01-491 C4+C5)

- utility-guard + gate de subagents: el marker estructural se lee de
  process.env al instalar/session_start (guardo) y en el primer input
  (subagents); el sniff de prefijo QUEDA como fallback pre-swap.
- Guard: session_start re-evalúa el env (un proceso de utilería lo es
  en toda sesión); tool_call pre-input también cubierto.
- Tests: env-only sin prefijo bloquea/deniega spawn, '0' no activa,
  session_start respeta env; anti-drift cross-repo contra titles.rs
  (prefijo corto + frase fija del template). 12/12 + 85/85 verdes.

* fix(harness): env_remove ZERON_UTILITY on non-utility ACP children (ID01-491 review)

A leaked parent export would otherwise mark every session as utility
and silently block all tools.

---------

Co-authored-by: David <davidhsutton@gmail.com>
…nsh#33)

* feat(tools): forward custom messages como agent_message_chunk (ID01-502)

Patch pi-acp 0.0.33 por-parte (steering + ZERON-CUSTOM-FORWARD), idempotente,
con gate+flatten en módulo puro testeado (18 tests node --test). Solo tools/.

* fix(tools): --check sin extensión .js + test array sucio (ID01-502 review)

---------

Co-authored-by: David <davidhsutton@gmail.com>
fix(acp): support Grok Build model switching
Fix terminal rendering after sidebar reopen
…ap-rendering

Fix decorated ranges at soft-wrap boundaries
…erminal-scrollbar

Fix selection edge scrolling and terminal scrollbar
v0.2.16 report: the percent loader showed for ~50-100ms then fell to the
dot loader. The percent was real but measured the wrong leg — it covered
only the UI staging bytes to the LOCAL engine (disk-speed), and the leg
that actually takes time, the engine pushing chunks over the relay to
the host, reported nothing.

Engine: push_attachments publishes per-upload raw-byte progress to a
watch channel — an entry appears when a file's chunks start moving,
updates per landed 45KB chunk, and a Drop guard retires it on every
exit (commit, timeout, refusal), so a dead attempt falls back to the
spinner and the retry re-publishes from zero.

RPC: new WatchTransfers subscription (mirrors WatchConnectivity —
current set first, then a snapshot per landed chunk).

UI: the sending thumbnail resolves its pending://{uploadId}/… ref
against the stream — percent sources in order: the attachment's own
relay transfer, else the send-wide staging percent, else the
indeterminate spinner (staged-but-waiting, retry backoff, or
committed-awaiting-rewrite). The ring never shows a number that isn't
a real transfer position.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Attachment thumbnail percent ring tracks the real relay transfer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…p (ID01-435) (zeronsh#34)

* feat(tools): check proactivo de capability steering del adapter pi-acp (ID01-435)

node tools/check-pi-acp-steering.mjs → exit 0 si el dist tiene
_session/steering + _meta.steering.supported; exit 1 con remedio
(patch_piacp.mjs) si un update del adapter borró la capability.
Acepta path como argv[2] para testear otros dists.

* fix(tools): pin check-pi-acp-steering to engine 0.0.33 (ID01-435 review)

---------

Co-authored-by: David <davidhsutton@gmail.com>
…h#172 port)

The Swift ChatRoomClient carried the identical root cause of the random
new-session forever-hangs: cursor = max(cursor, seq) let a live broadcast
row (mid-join race) jump the cursor over a hole; the skipped rows'
dependents park invisibly in loro's pending buffer, so the doc reads
empty under a cursor that claims convergence — and every later pull asks
after=cursor and gets nothing, surviving restarts.

- Contiguity rule at every advance (live row, socket ack, HTTP pull row,
  HTTP push ack): the cursor may walk, never jump. A gap applies the
  bytes, holds the honest cursor, and flags a repair.
- Gap repair: rowsReq from the held cursor, 3/session, then redial for a
  full catch-up. The HTTP pull path needs no send — its next cycle
  re-requests from the held cursor.
- Cursor amnesty extended to checkpoint-less rooms (first join clamps a
  suspicious cursor to 0, bounded by the checkpoint threshold policy) —
  heals every already-wedged chat on disk, no migration.
- The catch-up plan's `after` now assigns the cursor in both directions
  (new Delegate.setCursor) — without the raise, a contained checkpoint's
  backfill reads as a false gap.
- Retry re-issues dead attempts (phone half of zeronsh#172's recovery): a
  Run/Steer whose message never landed and whose command is rejected /
  expired / pending-past-TTL gets a fresh command id with the same
  payload and messageId (the host's user-entry pre-write dedupes); one
  re-issue per message, skipped while a live attempt is queued.
- Status strip narrates the escort's REAL relay transfer as
  "Uploading… N%" (the zeronsh#185 progress-truth analog).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agent calls were folded into the same "Called N tools" group as ordinary
Reads and Runs, so a running subagent disappeared behind the chevron.
Split them into their own always-open row.

Co-authored-by: Cursor <cursoragent@cursor.com>
The 30px header sat in a 30px border-box, so gpui clipped 2px off the
bottom and icons/labels read high. Size the row to the inner height
and pin labels to the same 18px box as the tiles.

Co-authored-by: Cursor <cursoragent@cursor.com>
…separately

Keep agent spawn chips visible outside the tool-group collapse
…sh#35)

* feat(harness): pin pi-acp to the davidhs26 fork by commit SHA (ID01-435)

NpmPin now understands 'github:owner/repo#<sha>' pins: name is the repo's
last segment (pi-acp), version is the full SHA (immutable install dir
~/.zeron/adapters/pi-acp/<sha>), and spec() keeps the whole github string
so npm installs from the fork, never the stale registry release. Existing
name@version pins (grok, cursor SDK) are unchanged.

pi_spec() pins github:davidhs26/pi-acp#d7f9cb2428c992c62aa759919c799c5619a9b10b
(includes _session/steering). check-pi-acp-steering.mjs no longer assumes
semver-only installs: prefers the SHA dir, then 0.0.33, then whatever
exists; argv override intact.

No npm publish, no engine swap; PI_ACP_EXECUTABLE override still wins.

* fix(harness): re-pin pi-acp SHA after adding prepare (ID01-435 review)

Git deps only run prepare; prepack left dist/ empty. Fork now has
prepare; pin 7a8548c empirically installs dist/index.js + steering.

* fix(harness): drop stale short SHA from pi-acp install_hint

---------

Co-authored-by: David <davidhsutton@gmail.com>
Squash de nuestro delta sobre upstream/main (983e23d). 18 archivos en
conflicto resueltos con politica declarada:
- upstream gana: Cargo/gpui rev, workspace_host (TRIPWIRE RETIRADO
  definitivamente + wiring en lib.rs — superseded por gap-repair/delivery
  durable), doc_host (transfers), crates/ui, DeviceRelayClient (PeerLiveness,
  zombie-link teardown), WorkspaceStore, HomeView (badges Queued/Failed).
- union: exports DRAINING_ERROR, spawn_agent con stderr_tail (upstream) +
  utility marker (nuestro), tests de ambos lados, SessionView =
  sendState upstream + chip de to-dos ID01-503.
- fixes: utility:false en RunRequest del test helper de sessions.rs;
  feature v5 de uuid restaurada (la usa pi-adopt).
- CI hosted -> workflow_dispatch only (facturacion Actions /mnt/c/Users/David/commit_sync17.sh; el gate es
  local).
- Gotcha ambiental documentado: la captura shell-env de upstream
  (bash -l -i) queda STOPPED por job control en WSL sin tty y cuelga
  tests/runtime pese a su timeout — mitigado con ZERON_NO_LOGIN_SHELL=1 en
  ~/.zeron/env (hatch de upstream); defecto a reportar upstream.

Validacion: build release OK - engine lib 140/140 - harness lib 99/99 -
extensiones 85+12+34 - todo sin skips.
…se (review k3, blocker de compilacion Swift)
Merge del arbol del branch sync/upstream-v0217 (squash upstream+delta).
Detalle, validacion y review k3 en el PR zeronsh#36.
…abort (ID01-519) (zeronsh#37)

* fix(engine): zombie turn — quiesce window on user-turn resume + zombie prompt abort (ID01-519)

Bug 3: track WHY the last park happened (resume_arms_short_window). A
quiesce-parked USER turn (missing harness Done) keeps the LONG window on
resume — it is the same turn continuing — instead of arming the 20s
self-continued window that re-parked it ~24s later (2026-08-18 incident).
Done-parks (and self-continued quiesces) still arm the short window. The
quiesce WARN now logs the window that actually fired, not the knob.

Bug 2: dispatch/steer refuse to route a prompt as a steer into a
Working run silent past ZERON_ZOMBIE_PROMPT_MS (default 8s, 0 disables)
with nothing in flight (the watchdog's own fold gate) — they interrupt
the zombie and dispatch fresh (TTFT 435s in the incident). Open tools,
pending inputs and parked runs still steer as before.

Tests: user_turn_quiesce_resume.rs, zombie_prompt.rs (new binaries);
turn_quiesce + self_continued_quiesce stay green.

* test(engine): pin ZERON_ZOMBIE_PROMPT_MS=0 always-steer (ID01-519 review)

Sibling binary because env knobs are process-global; silence past the
default 8s window must keep the same run id.

---------

Co-authored-by: davidhs26 <davidhs26@users.noreply.github.com>
Co-authored-by: David <davidhsutton@gmail.com>
…D01-529) (zeronsh#38)

* fix(engine): a finished subagent no longer pins the in-flight gate (ID01-529)

A spawn chip closes on LIFECYCLE, not on `resolved`. The adapter mints two
tool ids per subagent: the spawn call (`call-parent`, settled eagerly by its
own ToolResult) and the chip that indexes the nested transcript
(`call-parent:agent-1`), which no ToolResult ever carries — it reaches its
terminal state through `Subagent { Done }`, i.e. through `subagent_status`.

`fold_inflight` read `resolved` alone, so every finished subagent left one
phantom in-flight part in the fold. That gate guards BOTH silence paths —
the quiesce watchdog park and dispatch's zombie abort — so a single spawn
disarmed both for the rest of the chat.

Incident 2026-08-20, chat 9d321b30 (Pi via pi-acp, 4 subagents): the provider
stream died mid-turn at 20:43:47 and the turn read Working until pi's own
socket timeout fired 35 minutes later. No park (ZERON_TURN_QUIESCE_MS=300000
never armed), and a user prompt would have queued as a steer instead of
aborting the zombie.

A RUNNING subagent still pins the gate — a spawn legitimately runs silent for
many minutes (an 18.5-minute implement agent in that same chat), so this is a
lifecycle read, not an exemption.

Tests: subagent_chip_inflight.rs covers both halves (a settled chip releases
the watchdog, a live one holds it). Both fail against the pre-fix gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M5BJv4bnBHcCUUXdvhTnTN

* fix(engine): Running chip releases in-flight gate when resolved (review ID01-529)

---------

Co-authored-by: David <davidhsutton@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Incidente 2026-08-21 (3ra falla de la clase "entrega a la app"): un turno
termina Done en el journal pero el mensaje final nunca llega al telefono;
reiniciar el engine lo curaba porque el rejoin postea un checkpoint. El sync
v0.2.17 retiro el tripwire escalado de PR zeronsh#7 apostando a que gap-repair +
delivery durable lo superseded; la auditoria encontro tres huecos:

- el probe quiet de los rooms chat2 es de 15 min (PROBE_QUIET_DEFAULT=900s):
  un socket sordo al final del turno deja las filas finales unacked hasta
  15 min;
- el checkpoint (HTTP, independiente del WS y unica cura para un lector
  con el cursor retenido sobre un row gap) solo se postea por threshold,
  bootstrap o server-reset — nunca al finalizar un turno;
- el tripwire de sordera del registry redialea solo el registry y nunca
  barre los chat rooms (la mitad retirada de PR zeronsh#7).

El fix, sobre primitivas existentes:

- DocHost::seal_turn(chat): en cada finalize de turno (Done de cualquier
  status + quiesce-park con segmento visible) probea el socket del room
  (sordo → PROBE_DEADLINE 10s → redial + flush de pending) y postea un
  checkpoint "turn-end" salvo room probadamente cubierto (server_known,
  checkpoint >= cursor, sin pending);
- DeafEscalationHook en workspace_host: el redial del tripwire spawnea
  (nunca inline bajo los locks room/presence — leccion AB-BA de la review
  de PR zeronsh#7) un sweep probe_open_chats() de todos los rooms abiertos;
  captura Weak del grafo (un strong DocHost en el slot del workspace
  ciclaba el grafo y el shutdown leak test local_first lo cazo);
- el PUT del tail sidecar loguea rechazos y errores (antes let _ =
  silencioso: el fallback de lectores thin fallaba sin dejar señal);
- docs/chat2-sync.md C3 documenta el trigger nuevo.

Tests: zeron-engine + zeron-sync completos en verde (196 passed), incluido
online_runtime_shutdown_stops_edge_workers_and_retires_the_graph que cazo
el ciclo de Arcs de la primera version del wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GyJydy2tozB5X1X8ZvbdKQ
…wn nothing-streamed + warn en sweep sin runtime

MAJOR costo por turno documentado como tradeoff deliberado (no se salta el checkpoint con probe vivo: no cura al lector wedgeado). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@davidhs26
davidhs26 merged commit cc2d570 into main Aug 21, 2026
@davidhs26
davidhs26 deleted the fix/turn-end-delivery-seal branch August 21, 2026 04:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants