Skip to content

fix(runtime): retired sessions start fresh after restart - #522

Draft
vincentkoc wants to merge 1 commit into
mainfrom
fix/exact-session-retirement
Draft

fix(runtime): retired sessions start fresh after restart#522
vincentkoc wants to merge 1 commit into
mainfrom
fix/exact-session-retirement

Conversation

@vincentkoc

Copy link
Copy Markdown
Member

Related: openclaw/openclaw#130601
Related: openclaw/openclaw#107487

What Problem This Solves

Fixes an issue where an embedding host could retire an ACPX session locally, restart, and then accidentally resume the same backend session because the exact ACPX record was never durably marked for reset.

Credit to @MoerAI for the original restart-boundary report and patch direction in OpenClaw #130601.

Why This Change Was Made

This adds the upstream ACPX runtime operation prepareFreshSession(handle). It uses handle.acpxRecordId ?? handle.sessionKey, so generated one-shot records and persistent records are retired by exact identity rather than inferring a one-shot record from a bare session key.

The manager now:

  • requests cancellation and waits for an active turn's finalization before retirement;
  • strictly flushes, clears, and closes a retained exact-record owner, propagating failures;
  • persists closed: true plus acpx.reset_on_next_ensure: true only after owner finalization succeeds;
  • rejects a missing exact record, matching the existing close()/requireRecord() contract;
  • never calls remote session/close for this operation.

No ACP protocol, persistence schema, configuration, sidecar, or fallback path changes are included. No OpenClaw code is changed in this PR.

User Impact

Embedding hosts can durably retire an exact ACPX persistent or generated one-shot record. After the operation resolves, the next ensure creates a fresh backend session even after a process restart.

OpenClaw integration remains pending until this ACPX change is merged and published in a new ACPX version; OpenClaw #130601 must then consume that version and pass the exact runtime handle.

Evidence

Exact tested head: 865a79ec00f81946421c72b87326a8d6a6b2b379

Tests and Gates

  • Test-first proof: before implementation, pnpm run build:test failed with TS2339 because prepareFreshSession did not exist.
  • Focused exact-head retirement suite: 6 passed.
  • Full runtime suite: 112 passed.
  • pnpm run check: passed.
    • full suite: 941 passed;
    • coverage suite: 136 passed;
    • statements: 94.72%;
    • branches: 88.83%.
  • pnpm run check:mutation: passed at 90.80% (385 killed, 39 existing survivors in unchanged src/cli/flags.ts).
  • Fresh Codex autoreview:
    • accepted and fixed active-turn lost-update and late-finalization findings;
    • the remaining { sessionKey } API-shape finding was not applied because this upstream PR intentionally establishes the reviewed exact-handle contract required for one-shot identity. The provisional downstream OpenClaw wrapper must adapt after publication.

Manager coverage includes:

  • persistent exact record ID with a distinct session key;
  • generated one-shot exact record ID with a persistent decoy;
  • retained owner flush/close before durable retirement;
  • active owner cancellation and full finalization before durable retirement;
  • immediate ensure after active retirement without stale-turn overwrite;
  • missing-record rejection;
  • retained-owner finalization failure propagation;
  • real FileSessionStore permission failure without false retirement;
  • reset-on-next-ensure through a new manager/process.

Three-Process FileSessionStore Proof

The committed test harness runs seed, retire, and ensure in separate OS processes over one real temporary FileSessionStore.

{"phase":"seed","backendSessionId":"stale-backend-session"}
{"phase":"retire"}
{"phase":"ensure","backendSessionId":"38678e0d-b705-4596-80dc-2db3ce129cf0","acpxRecordId":"process-retirement"}

The third process receives a different backend session ID while retaining the exact ACPX record identity.

Codex Restart Contract

Personally inspected sibling openai/codex at a6e63f9f32525d171676ac49c4e87ab00d76f0ce:

  • codex-rs/app-server-protocol/src/protocol/v2/thread.rs:321-336 defines resume by stored thread ID.
  • codex-rs/app-server/src/message_processor.rs:1128-1148 dispatches thread resume.
  • codex-rs/app-server/src/request_processors/thread_processor.rs:3542-3650 reloads stored thread state during cold resume.

This confirms that leaving the stale backend ID reusable can resume it after a host restart.

LOC

  • Production source: +54/-6 (+48 net).
  • Tests and test harness: +390/-0.
  • Changelog: +2/-0.

The positive production delta is the new public runtime capability plus the lifecycle barrier and strict failure-propagating owner finalizer. The compression pass kept the existing reset marker and close machinery, with no duplicate storage or protocol path.

Residual Risk

prepareFreshSession() waits for active-turn finalization. If an ACP agent ignores cancellation, completion is bounded by the existing turn timeout/finalization behavior rather than falsely acknowledging retirement.

AI-assisted: yes. The change was implemented test-first, independently reviewed, and validated against the real file store and OS process boundary.

@clawsweeper

clawsweeper Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Aug 27, 2026
@clawsweeper

clawsweeper Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed August 26, 2026, 11:25 PM ET / August 27, 2026, 03:25 UTC.

ClawSweeper review

What this changes

The PR adds an optional runtime method that locally retires an exact persisted ACPX session record so the next ensure creates a new backend session after restart.

Merge readiness

⚠️ Ready for maintainer review - 3 items remain

Keep open for explicit maintainer acceptance of a new public runtime contract. The implementation is coherent and targets ACPX’s backend session-lifecycle boundary; the linked OpenClaw work supplies motivation but is not an ACPX implementation dependency.

Priority: P2
Reviewed head: 865a79ec00f81946421c72b87326a8d6a6b2b379
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) A focused, well-covered runtime change with no blocking source-level finding; the remaining gate is maintainer acceptance of the new public contract.
Proof confidence 🌊 off-meta tidepool Not applicable: This MEMBER-authored PR is exempt from the external-contributor proof gate; its body nevertheless supplies a committed three-process FileSessionStore proof and reported exact-head validation.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Not applicable Not applicable: This MEMBER-authored PR is exempt from the external-contributor proof gate; its body nevertheless supplies a committed three-process FileSessionStore proof and reported exact-head validation.
Evidence reviewed 6 items New local-retirement path: The proposed manager operation waits for any active turn to finalize, finalizes a retained owner, then persists the reset marker; it does not call backend session close.
Existing fresh-ensure mechanism: Current main already treats reset_on_next_ensure as non-reusable, so the proposed durable marker is consumed by the established ensure path.
Focused lifecycle coverage: The PR adds coverage for exact generated one-shot identity, active-turn finalization, finalization failure, and missing-record handling.
Findings None None.
Security None None.

Live Verification

Command: pnpm run test

Result: FAIL (failed) — step 1 run pnpm run test: previous terminal command was still running after 30 seconds: "pnpm run test"

› acpx@0.13.1 test /tmp/clawsweeper-live-proof-522-bwrddE/target
› pnpm run build && pnpm run build:test && node --test dist-test/test/*.test.js


› acpx@0.13.1 build /tmp/clawsweeper-live-proof-522-bwrddE/target
› tsdown src/cli.ts src/flows.ts src/runtime.ts --format esm --dts --clean --platform node --target node22 --no-fixedExtension

ℹ tsdown v0.22.14 powered by rolldown v1.2.0
ℹ config file: /tmp/clawsweeper-live-proof-522-bwrddE/target/package.json
ℹ entry: src/cli.ts, src/flows.ts, src/runtime.ts
ℹ target: node22
ℹ tsconfig: tsconfig.json
ℹ Build start
ℹ Granting execute permission to dist/cli.js
ℹ dist/cli.js                             116.67 kB │ gzip:  24.77 kB
ℹ dist/runtime.js                          79.74 kB │ gzip:  17.64 kB
ℹ dist/flows.js                             0.44 kB │ gzip:   0.23 kB
ℹ dist/live-checkpoint-BSIrfgVo.js.map    526.99 kB │ gzip: 110.76 kB
ℹ dist/output-BZQE0gI1.js.map             363.47 kB │ gzip:  73.91 kB
ℹ dist/live-checkpoint-BSIrfgVo.js        246.63 kB │ gzip:  54.51 kB
ℹ dist/cli.js.map                         232.85 kB │ gzip:  47.97 kB
ℹ dist/output-BZQE0gI1.js                 169.25 kB │ gzip:  36.25 kB
ℹ dist/runtime.js.map                     166.12 kB │ gzip:  35.37 kB
ℹ dist/flows-fzH0GKYF.js.map              143.48 kB │ gzip:  31.10 kB
ℹ dist/flows-fzH0GKYF.js                   68.15 kB │ gzip:  15.54 kB
ℹ dist/flags-Ds6FzmHG.js.map               30.63 kB │ gzip:   7.71 kB
ℹ dist/cli-_7Sei6dD.js.map                 16.09 kB │ gzip:   4.55 kB
ℹ dist/flags-Ds6FzmHG.js                   15.63 kB │ gzip:   4.16 kB
ℹ dist/cli-_7Sei6dD.js                      8.43 kB │ gzip:   2.59 kB
ℹ dist/runtime.d.ts.map                     3.78 kB │ gzip:   1.33 kB
ℹ dist/flows.d.ts.map                       2.65 kB │ gzip:   0.88 kB
ℹ dist/session-options-DwRDODlr.d.ts.map    1.90 kB │ gzip:   0.70 kB
ℹ dist/client-CxNllqui.d.ts.map             1.74 kB │ gzip:   0.76 kB
ℹ dist/cli.d.ts.map                         0.21 kB │ gzip:   0.18 kB
ℹ dist/runtime.d.ts                        21.02 kB │ gzip:   5.48 kB
ℹ dist/flows.d.ts                          10.70 kB │ gzip:   2.55 kB
ℹ dist/cli.d.ts                             0.72 kB │ gzip:   0.34 kB
ℹ dist/session-options-DwRDODlr.d.ts        9.22 kB │ gzip:   2.78 kB
ℹ dist/client-CxNllqui.d.ts                 8.51 kB │ gzip:   2.37 kB
ℹ 25 files, total: 2.25 MB
[INEFFECTIVE_DYNAMIC_IMPORT] src/cli/output/render.ts is dynamically imported by src/cli/command-handlers.ts but also statically imported by src/cli-core.ts, sr
c/cli/status-command.ts, dynamic import will not move module into another chunk.

✔ Build complete in 2465ms

› acpx@0.13.1 build:test /tmp/clawsweeper-live-proof-522-bwrddE/target
› node -e "require('node:fs').rmSync('dist-test',{recursive:true,force:true})" && tsc -p tsconfig.test.json

✔ built-in command displays stay synchronized with structured argv (3.254711ms)
✔ resolveAgentCommand maps known agents to commands (0.580826ms)
✔ resolveAgentCommand returns raw value for unknown agents (0.215005ms)
✔ resolveAgentCommand maps factory droid aliases to the droid command (0.216308ms)
✔ resolveAgentCommand prefers explicit alias overrides over built-in alias mapping (0.239341ms)
✔ trae built-in uses the standard traecli executable (0.228561ms)
✔ kiro built-in uses kiro-cli-chat directly (0.219123ms)
✔ fast-agent built-in runs the ACP entrypoint through uvx (0.236987ms)
✔ grok-build built-in runs the Grok Build ACP entrypoint (0.361151ms)
✔ mux built-in runs the coder/mux ACP stdio bridge through npx (0.35535ms)
✔ pool built-in runs the Poolside ACP entrypoint (0.580295ms)
✔ zeroclaw built-in launches the native ZeroClaw ACP server (0.289577ms)
✔ listBuiltInAgents preserves the required example prefix and alphabetical tail (1.355889ms)
✔ default agent is codex (0.138622ms)
✔ claude built-in uses the current ACP adapter package range (0.205618ms)
✔ npm-backed built-ins use current adapter package ranges (0.
… output truncated …

Assertions:

  • FAIL expect_output: prepareFreshSession survives seed, retire, and ensure across OS processes

How this fits together

ACPX’s embedded runtime maps host session handles to persisted records and retained ACP client connections. This change retires one exact record, then makes the next session ensure create a fresh backend session.

flowchart LR
  Host[Embedding host] --> Handle[Exact session handle]
  Handle --> Runtime[ACPX runtime]
  Runtime --> Manager[Session manager]
  Manager --> Owner[Active or retained owner]
  Manager --> Store[Persisted session record]
  Store --> Ensure[Next session ensure]
  Ensure --> Backend[Fresh backend session]
Loading

Decision needed

Question Recommendation
Should ACPX establish prepareFreshSession(handle) as an optional public runtime contract for local exact-record retirement without backend session/close? Adopt the explicit retirement contract: Accept the optional exact-handle operation as the backend-friendly local reset path for embedding hosts.

Why: The patch is technically focused, but it adds a lasting public API convention and the repository policy requires deliberate scrutiny of new runtime surface.

Before merge

  • Resolve merge risk (P1) - The additive optional runtime method becomes a durable public contract, so its exact-handle and local-only retirement semantics need maintainer approval before release.
  • Complete next step (P2) - A maintainer must choose whether this optional public runtime method is an accepted long-term ACPX convention.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Runtime and test delta runtime +54/-6; tests +390 The added public lifecycle capability is backed by substantially more focused regression coverage than production code.

Merge-risk options

Maintainer options:

  1. Decide the mitigation before merge
    Adopt one narrowly documented, opt-in local-retirement operation if maintainers want embedding hosts to reset a record without requiring ACP backend session-close support.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Technical review

Best possible solution:

Adopt one narrowly documented, opt-in local-retirement operation if maintainers want embedding hosts to reset a record without requiring ACP backend session-close support.

Do we have a high-confidence way to reproduce the issue?

Yes, source reproducibly shows that current main’s reset marker forces a fresh ensure, while the new committed three-process harness exercises durable local retirement across restart boundaries; this read-only review did not execute it.

Is this the best way to solve the issue?

Yes, subject to contract approval: an explicit opt-in exact-handle operation is narrower than changing existing close semantics or inferring identity from a session key.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against d4c16ab32154.

Labels

Label changes:

  • add P2: This is a bounded backend session-lifecycle improvement with a concrete downstream integration need.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🌊 off-meta tidepool and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Not applicable: This MEMBER-authored PR is exempt from the external-contributor proof gate; its body nevertheless supplies a committed three-process FileSessionStore proof and reported exact-head validation.

Label justifications:

  • P2: This is a bounded backend session-lifecycle improvement with a concrete downstream integration need.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🌊 off-meta tidepool and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Not applicable: This MEMBER-authored PR is exempt from the external-contributor proof gate; its body nevertheless supplies a committed three-process FileSessionStore proof and reported exact-head validation.

Evidence

What I checked:

Likely related people:

  • Onur: Introduced the persisted reset-on-next-ensure behavior that the new operation reuses. (role: introduced reset-marker behavior; confidence: high; commits: bdd0f7ae1691; files: src/runtime/engine/reuse-policy.ts, src/types.ts)
  • Peter Steinberger: Current main attributes the close and active-owner lifecycle area to the v0.13.1 history boundary; prior history also includes active-owner lifecycle work. (role: recent session-lifecycle contributor; confidence: medium; commits: 43013ead1ab8, 2d735cf18220; files: src/runtime/engine/manager.ts)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant