feat(v2): A1 reconnect + history recovery (V1.1 — Item 1)#2
Merged
Conversation
5 tasks
V1.1 Item 1 from `~/.openclaw/wiki/main/_boards/runbooks/platform/benchagi-v2-cloud-brain-pickup.md`:
keep the CLI alive across network drops; replay missed events on
reconnect; warn the user when seq numbers gap.
## Transport (`src/v2/transport/local-gateway.ts`)
- Differentiate user-initiated close from network-initiated close.
User close drains the events() iterator; network close keeps it
alive across reconnect.
- New `setReconnectListeners({ onDisconnected, onReconnecting,
onReconnected })` lifecycle.
- Auto-reconnect loop with backoff sequence (1s, 2s, 5s, 10s, 30s,
cap at 30s) per `src/v2/transport/backoff.ts:nextBackoffMs`.
- Pending requests at disconnect time are still rejected so callers
can decide whether to retry. The events() iterator does NOT exit.
## Router (`src/v2/render/event-router.ts`)
- New optional `onSeqGap(runId, prevSeq, nextSeq)` handler. Fires
exactly once per gap, per runId, when an agent-payload seq jumps
by more than 1. Existing dedupe runs first, so out-of-order replay
events are filtered before gap-detection runs.
## Chat-runner (`src/v2/chat-runner.ts`)
- Tracks the highest `EventFrame.seq` per session.
- On reconnect, calls `chat.history { sessionKey, sinceSeq }` and
dispatches replay frames through the existing router. Dedupe key
`(prefix, runId, seq, stream, sub)` collapses live + replay events
to a single render. Gap-warning is suppressed during replay since
replay IS the recovery for the gap.
- Reconnect lifecycle is surfaced to the user as dim status lines
("connection lost — reconnecting…", "reconnect attempt N in Ns",
"reconnected").
## Tests (44 total, +10 from this PR)
- `src/v2/test/backoff.test.ts`: 6 cases for nextBackoffMs (sequence,
cap, non-finite/non-positive inputs, fractional flooring).
- `src/v2/test/event-router.test.ts`: +4 tests covering seq-gap
warning fires on jump, NOT on +1 advance, NOT on dedupe-replay,
AND chat.history replay does not double-render previously-seen
events.
## SPEC §13 coverage delivered
- "Reconnect: network drop backoff sequence" — backoff.test.ts
- "Renderer: seq gap warning" — event-router.test.ts
- "Reconnect: in-flight run recovery via chat.history" — covered at
the router level (replay-doesn't-double-render); the transport-
level integration test is deferred to a future PR that introduces
transport DI in ChatRunner.
## Verified
- `npm run build` clean
- `npm run test:v2` 44/44 green (was 34)
- `npm run lint` clean
- `benchagi doctor` smoke OK against brew openclaw 2026.5.2
(regression check; doesn't exercise reconnect path live)
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- **P1 (live-vs-history race)**: live event frames arriving while a chat.history replay is in flight are now buffered by `eventLoop` and drained in arrival order after replay completes. Without this, a fast gateway resuming streaming during chat.history could send the renderer events out of order and produce false seq-gap warnings. The replay flag is set BEFORE the chat.history request (not after) so frames during the request are also buffered. Drain loops to handle the tail case where new frames arrive during drain. - **P1 (stale-socket close)**: a prior socket's close event MUST NOT poison the active socket if a reconnect already swapped `this.ws` out. Added `if (this.ws !== ws) return` guard at the top of the close handler. Two remaining Codex findings deferred to V1.1 follow-up PRs (TODOs inline): - **P1 (chat.send acceptance race)**: re-issue chat.send with same idempotencyKey on reconnect, or confirm acceptance via chat.history/sessions.list before failing the send. - **P2 (history retry)**: retry chat.history with bounded backoff before giving up on the recovery window. - **P2 (shutdown-hint plumbing)**: feed `restartExpectedMs` from the shutdown event into `reconnectLoop`'s first-attempt floor so the reconnect UX respects the planned-restart hint. Tests: 44/44 still green. The race-fix behavior is straightforward to read; an integration-level test that exercises the buffer-drain flow needs the transport-DI refactor that's queued for a future PR. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
LightDriverCS
force-pushed
the
feat/v1.1-a1-reconnect
branch
from
May 6, 2026 18:04
24956ae to
69cb8bc
Compare
LightDriverCS
added a commit
that referenced
this pull request
May 6, 2026
Codex Anvil 4 finding: HOLD. Two P0s + two P1s + a missing-test
flag.
Most important: Codex couldn't find a readable local gateway path
that routes chat.send through cloud-brain when an agent's
deployment has runtime: 'remote-brain'. Without that dispatch
bridge, the smoke script will pass on local execution without
exercising cloud-brain at all. The bridge MAY be in W2 (#878)
which Codex couldn't see in full diff via the connector; needs
verification.
Other findings:
- P1: bench-cli's chat.history call expects events/frames but the
readable OpenClaw chat.history handler returns transcript
messages and ignores sinceSeq. V1.1 reconnect replay is
best-effort no-op until this contract is reconciled.
- P1: Firestore doc id format assumption in the smoke
(instances/{instanceId}/agentDeployments/{instanceId}_{agentId})
may not match real W1 backfill output if deploymentIds differ.
- P2: Firestore failures recorded as skips can produce false
exit-0 if no agents tested.
- P2: smoke assumes repo-root cwd.
Stronger smoke proposed: assert directive artifacts in Firestore
(relayDirectives doc with directiveType: llm_turn), assert directive
reaches completed, AND assert CLI saw normal chat/agent.lifecycle
frames. That proves both halves of ADR-006 transparency.
Doesn't change the smoke script in this commit — the structural
concern (missing dispatch bridge) needs resolution first. The
smoke script is preserved as-is (P0 #2 acknowledged as a review
finding) so Cory can decide which path to take.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
LightDriverCS
added a commit
that referenced
this pull request
May 6, 2026
…n (Switch 2-CLI) (#9) * feat(v2): cloud-brain smoke script (Phase B prep) Writes `scripts/cloud-brain-smoke.mjs` per V2 runbook §"Validation script". Exercises the cloud-brain end-to-end path once Phase 1B merges (BenchAGI #872 W1 + #874 W4 + #878 W2 + #988 relay + openclaw#24 W3) and an agent's deployment is flipped to runtime: 'remote-brain'. What the script does: - Lists known agents from the local openclaw gateway. - Queries Firestore (admin REST + gcloud token per the documented recipe) for `agentDeployments/{instanceId}_{agentId}.runtime`. - For each agent with `runtime === 'remote-brain'`, spawns `benchagi --agent <name> --liveness off "respond: smoke-ok"` with stdout captured and a 60s timeout. - Asserts: exit 0, non-empty stdout, no error markers, latency under 60s. - Emits JSON summary; exits 0 only if all tested agents passed. Required env: INSTANCE_ID. Optional: GCP_PROJECT (default benchagi-8ea90), SMOKE_AGENT_FILTER, SMOKE_PROMPT, SMOKE_TIMEOUT_MS. Gated on cloud-brain Phase 1B merging + a deployment flipped to remote-brain. Until then, the script reports "no remote-brain agents found — Phase 1B may not be merged + flipped yet" and exits 0 (not a failure). Stays as a draft PR until Cory merges Phase 1B and flips at least one deployment, at which point we run the smoke + capture the transcript for ANVIL-5. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * docs(v2): add ANVIL-4-REVIEW (cloud-brain readiness) Codex Anvil 4 finding: HOLD. Two P0s + two P1s + a missing-test flag. Most important: Codex couldn't find a readable local gateway path that routes chat.send through cloud-brain when an agent's deployment has runtime: 'remote-brain'. Without that dispatch bridge, the smoke script will pass on local execution without exercising cloud-brain at all. The bridge MAY be in W2 (#878) which Codex couldn't see in full diff via the connector; needs verification. Other findings: - P1: bench-cli's chat.history call expects events/frames but the readable OpenClaw chat.history handler returns transcript messages and ignores sinceSeq. V1.1 reconnect replay is best-effort no-op until this contract is reconciled. - P1: Firestore doc id format assumption in the smoke (instances/{instanceId}/agentDeployments/{instanceId}_{agentId}) may not match real W1 backfill output if deploymentIds differ. - P2: Firestore failures recorded as skips can produce false exit-0 if no agents tested. - P2: smoke assumes repo-root cwd. Stronger smoke proposed: assert directive artifacts in Firestore (relayDirectives doc with directiveType: llm_turn), assert directive reaches completed, AND assert CLI saw normal chat/agent.lifecycle frames. That proves both halves of ADR-006 transparency. Doesn't change the smoke script in this commit — the structural concern (missing dispatch bridge) needs resolution first. The smoke script is preserved as-is (P0 #2 acknowledged as a review finding) so Cory can decide which path to take. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * feat(v2): Firebase token attachment + WS frame tracing for cloud-brain (Switch 2-CLI) Pairs with the BenchAGI mono cloud bridge endpoint + the OpenClaw gateway bridge to enable transparent cloud-brain dispatch from the CLI. The CLI side is small: attach the user's Firebase ID token to chat.send so the gateway can authenticate to the cloud bridge. ## What's new - `src/v2/auth/firebase-token.ts` — loads Firebase Direct creds from the existing keychain. Refreshes ID token using the public Firebase Identity Toolkit when one of these is set: BENCHAGI_FIREBASE_API_KEY NEXT_PUBLIC_FIREBASE_API_KEY FIREBASE_API_KEY - `src/v2/chat-runner.ts` — sendMessage now resolves the Firebase token (best-effort) and adds `cloudAuth.firebaseIdToken` to the chat.send request. If no creds are available, chat.send fires without the field and the gateway falls back to local dispatch per ADR-006's transparency contract. - `src/v2/transport/local-gateway.ts` — accepts the cloudAuth field in chat.send param shape; passes through unchanged. - `src/v2/cli.ts` — adds raw WS frame tracing for diagnostics: --trace-frames <path> BENCHAGI_TRACE_FRAMES=<path> Each WS frame the CLI receives is appended as a JSONL line to the file. Useful for correlating CLI events to relayDirectives during the smoke test. ## Verification - `npm run build` — clean - Existing test suite still passes (no behavior change for the default-local path; only adds the cloudAuth field when a token is available). ## Limitations / follow-ups - chat.send acceptance race (V1.1 ANVIL-2 P1 deferred) — still not fixed in this PR. Re-issuing chat.send on reconnect with same idempotencyKey is a separate cycle. - Token refresh path silently no-ops if no Firebase API key env var is set. CLI users who don't have one configured will fall through to local-only dispatch even on remote-brain agents (which is the safe default). - Frame trace file is append-only; no rotation. Each smoke run should use a fresh path. ## Pairs with - BenchAGI mono PR [#1016](BenchAGI/BenchAGI_Mono_Repo#1016) — cloud bridge endpoint - OpenClaw fork PR [#26](BenchAGI/openclaw#26) — gateway-side dispatch + polling --------- Co-authored-by: LightDriverCS <[email protected]> Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
V1.1 Item 1 from the benchagi-v2-cloud-brain-pickup runbook: keep the CLI alive across network drops, replay missed events on reconnect, warn on seq gaps.
Stacks on #1 (V1.0-beta). Diff shown is just the A1 changes.
Transport (
src/v2/transport/local-gateway.ts)events(); network close keeps it alive.setReconnectListeners({ onDisconnected, onReconnecting, onReconnected })lifecycle hook.src/v2/transport/backoff.ts:nextBackoffMs.events()does NOT exit.Router (
src/v2/render/event-router.ts)onSeqGap(runId, prevSeq, nextSeq)handler. Fires once per gap per runId. Existing dedupe runs first so out-of-order replay events do NOT cause spurious gap warnings.Chat-runner (
src/v2/chat-runner.ts)EventFrame.seqper session.chat.history { sessionKey, sinceSeq }and dispatches replay frames through the existing router. Dedupe key(prefix, runId, seq, stream, sub)collapses live + replay events to a single render. Gap-warning is suppressed during replay since replay IS the recovery for the gap.Tests (44 total, +10)
src/v2/test/backoff.test.ts: 6 cases fornextBackoffMs(sequence, cap, non-finite/non-positive inputs, fractional flooring).src/v2/test/event-router.test.ts: +4 tests — seq-gap fires on jump, NOT on +1 advance, NOT on dedupe-replay; chat.history replay does not double-render.SPEC §13 coverage
backoff.test.tsevent-router.test.tsevent-router.test.ts; transport-level integration test deferred to a future PR that introduces transport DI in ChatRunnerAnvil Handoff
Per the runbook's V1.1 Item 1 Anvil prompt:
Codex Anvil will fire on the
anvillabel.Test plan
npm install && npm run build && npm run test:v2→ 44/44 pass from clean clonenpm run lint→ silent successbenchagi doctoragainstbrew openclaw 2026.5.2→ regression check passesbenchagiREPL, kill openclaw gateway withlaunchctl stop, observe "(connection lost — reconnecting…)" + backoff status lines, restart gateway, observe "(reconnected)" + history replay🤖 Generated with Claude Code