Skip to content

feat: keep replay session active on request - #1554

Merged
thymikee merged 4 commits into
mainfrom
feat/replay-keep-session
Aug 2, 2026
Merged

feat: keep replay session active on request#1554
thymikee merged 4 commits into
mainfrom
feat/replay-keep-session

Conversation

@thymikee

@thymikee thymikee commented Aug 2, 2026

Copy link
Copy Markdown
Member

Summary

Add replay --keep-session and SDK keepSession support for native .ad replay.

  • Native close-less scripts already retain their session; this option suppresses exactly an authored terminal close so an agent can continue interactively.
  • Interior closes retain authored behavior, and the existing replay-repair lifecycle shares the same terminal-close seam.
  • test and Maestro YAML reject the option because those runtimes own cleanup/lifecycle.
  • Scope: 16 files across contracts, CLI projection/help, replay runtime/tests, flag classification, and website docs. No platform backend changes.
  • SkillGym was not updated because that harness is absent on current main; command help and user docs carry the planning guidance.

Validation

  • pnpm check (5,105 unit tests and 35 smoke tests passed; 3 live lanes skipped by environment selectors)
  • Focused replay/runtime and CLI projection tests pass within the full suite
  • Counterfactual removal of terminal-close suppression fails the dedicated runtime regression test
  • No live-device run: the change only suppresses daemon dispatch of a terminal lifecycle action and does not alter platform behavior

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-02 16:35 UTC

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.93 MB 1.93 MB +2.3 kB
JS gzip 618.1 kB 618.8 kB +705 B
npm tarball 736.4 kB 737.2 kB +774 B
npm unpacked 2.58 MB 2.59 MB +2.4 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 29.4 ms 28.9 ms -0.4 ms
CLI --help 68.2 ms 68.9 ms +0.7 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/session.js +1.4 kB +429 B
dist/src/registry.js +607 B +191 B
dist/src/cli-help.js +262 B +85 B

@thymikee

thymikee commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Review verdict: not merge-ready at 1133030b5.

[P1] Missing required provider-backed integration coverage. The new replayKeepSession device-observable flag is classified in scripts/integration-progress-model.ts, but there is no provider-backed scenario owning keepSession. The PR-head Integration Tests job fails deterministically with missing Provider-backed integration workflow flag coverage: replayKeepSession; this is owner-action, not an infrastructure flake.

Please add a provider scenario through the real daemon router/session store—test/integration/provider-scenarios/active-session-script-publication.test.ts is the natural home—that replays a native .ad ending in close with keepSession, asserts the response reports sessionActive: true and the session survives, proves a subsequent command works, then explicitly closes and verifies cleanup. Run the provider-integration/progress gate and restore green CI.

The functional implementation otherwise looks coherent: the public projections are complete, only a terminal close is suppressed, interior/close-less behavior is covered, and test/Maestro lifecycle ownership is preserved. No live-device evidence is needed beyond the missing provider-route proof because no platform backend behavior changes.

@thymikee

thymikee commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Addressed in 5dacea4a9.

Added provider-backed coverage through the real SDK → daemon route for a native .ad script with an authored terminal close and keepSession: true. The scenario verifies that replay reports an active session, a subsequent snapshot succeeds for agent takeover, and an explicit close then cleans up the session.

Validation passed locally: pnpm check, the full provider-integration suite (147 tests), and the deterministic progress gate (67/67 device-observable flags covered). CI is now rerunning on the new head.

@thymikee

thymikee commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Addressed the follow-up review in 1eaec722b:

  • terminal-close suppression is resolved once per native replay plan and now means the last executable action, so trailing non-executable replay markers cannot tear down the requested handoff;
  • --keep-session is enforced as a postcondition: a completed run with no live session now fails explicitly instead of returning silent success;
  • replayed now counts dispatched executable actions and excludes the suppressed close;
  • raw test requests use one declarative replay-only flag policy, structurally checked against the replay/test CLI schema difference (with only client-side out waived); --force is covered and explicit false booleans normalize consistently;
  • the sessionActive contract comment now describes explicit terminal-close suppression accurately.

Counterfactual proof: with the old raw-final-index logic and no postcondition check restored locally, both new regressions fail — the trailing-marker case dispatches close, and the missing-session case returns ok: true.

Validation passed: full pnpm check (5,109 tests), full provider integration (147 tests), progress coverage (67/67 device-observable flags), focused replay/repair/schema/help suites, production-export and Fallow gates. CI is rerunning on the new head.

@thymikee

thymikee commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Re-review verdict: code-clean at 1eaec722b; the prior provider-route finding is resolved.

The real SDK → daemon scenario proves sessionActive, successful post-handoff use, and explicit cleanup. Latest regressions correctly resolve the terminal executable close once, enforce the live-session postcondition, count only dispatched actions, and keep raw test policy aligned with the CLI schema. Required replay/provider/static/coverage/Fallow checks are green.

The only red check is an unrelated Android emulator capture stall (wait_capture_stalled while waiting for landscape), so CI needs a clean rerun before merge/readiness labeling.

@thymikee

thymikee commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Thermo-nuclear review verdict: changes requested for structure.

[P1] The new replay-terminal lifecycle policy must be extracted before merge. This PR adds the keep-session/repair terminal-close decision, the postcondition, and the dispatched-action count to session-replay-runtime.ts, taking an already oversized 864-line implementation to 901 lines. Its matching test file grows from 566 to 719 lines. That violates this repository’s explicit rule to extract before adding behavior beyond 500 lines, and leaves lifecycle policy entangled with parsing, execution, diagnostics, repair, and response formatting.

There is a direct code-judo move here: introduce a focused replay-terminal-lifecycle module that owns terminal executable-action resolution and its outcome (suppressed close index, live-session requirement, and executed-action count). Keep session-replay-runtime.ts as orchestration only, and mirror the new unit tests in a session-replay-terminal-lifecycle.test.ts file. This removes the new mode-specific plumbing from the busy runtime and gives the keep-session + repair seam one canonical home instead of extending the existing god module.

I found no second high-conviction structural issue in the public projections, raw test guard, or provider-route coverage. The prior functional findings remain resolved; this is the remaining merge blocker.

…#1554 review)

session-replay-runtime.ts was already over the 500-line extract-before-adding-behavior
tripwire before this PR; the keep-session/repair terminal-close decision, its
live-session postcondition, and the dispatched-action count pushed it further past
budget. Move that policy into a focused session-replay-terminal-lifecycle.ts
(isExecutableReplayAction, resolveSuppressedTerminalCloseIndex,
countExecutedReplayActions, requireLiveSessionForKeepSession) so the runtime file
stays orchestration-only, and mirror its PR-added unit tests into
session-replay-terminal-lifecycle.test.ts. Pure extraction: no assertions changed.
@thymikee

thymikee commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Extracted the keep-session/repair terminal-close policy into src/daemon/handlers/session-replay-terminal-lifecycle.ts (01f284a), as requested. Pure extraction — no assertion or behavior changes.

What moved (verbatim, function extraction only):

  • isExecutableReplayAction — the dispatchable-step predicate (nested replay markers are plan metadata and never dispatch)
  • resolveSuppressedTerminalCloseIndex — the keep-session/repair terminal-close decision
  • countExecutedReplayActions — the dispatched-action count
  • requireLiveSessionForKeepSession — the --keep-session live-session postcondition (previously inlined in completeReplayRun)

session-replay-runtime.ts now imports these four and calls them from prepareReplayPlan/executeReplayActions/completeReplayRun; it no longer owns the policy itself, only orchestrates it.

LOC

File Before (this PR's head) After
session-replay-runtime.ts 901 869
session-replay-runtime.test.ts 719 566
session-replay-terminal-lifecycle.ts (new) 65
session-replay-terminal-lifecycle.test.ts (new) 170

For reference, session-replay-runtime.ts/session-replay-runtime.test.ts were 864/566 lines on main before this PR — so the runtime file now sits at +5 lines over the pre-PR baseline (orchestration wiring) instead of +37, and the test file is back to its exact pre-PR size, with all six new --keep-session unit tests now living in the new module's test file (mirroring the existing session-replay-runtime-plan.ts/.test.ts sibling pattern, including its shared session-replay-runtime.fixtures.ts builders).

Gates (all green, foreground, on 01f284a):

  • pnpm typecheck
  • pnpm lint
  • pnpm format:check
  • npx fallow audit --base origin/main ✓ (no issues in 25 changed files)
  • npx vitest run src/daemon ✓ (203 files, 1703 tests passed)
  • pnpm check:replay-compat

Generated by Claude Code

@thymikee
thymikee merged commit 2c2df03 into main Aug 2, 2026
33 checks passed
@thymikee
thymikee deleted the feat/replay-keep-session branch August 2, 2026 16:35
thymikee added a commit that referenced this pull request Aug 2, 2026
… into the ad-replay engine

Rebasing p5/extract-ad-replay onto main pulled in #1554's --keep-session
feature, which had grown its own daemon-side terminal-close-suppression
predicate (session-replay-terminal-lifecycle.ts's
resolveSuppressedTerminalCloseIndex/countExecutedReplayActions) independently
of this branch's own engine-side one (step-loop.ts's
isRepairArmedTerminalCloseAction). Both are the same decision family — replay
--keep-session and an active --save-script repair now share ONE structural
resolution (resolveSuppressedTerminalCloseIndex, generalized to "terminal
among EXECUTABLE actions" rather than the old physical-last-index check) and
one suppression check inside runAdReplay, gated on keepSession OR
runtime.isRepairArmed(). AdReplayRunRequest grew a keepSession field; the
neutral 'replayed' count in AdReplayRunOutcome is now computed inline in the
loop instead of the daemon's old actions.length - entryIndex approximation.

requireLiveSessionForKeepSession (the --keep-session live-session
postcondition) stays daemon-side, inlined into session-replay-runtime.ts,
since it inspects SessionStore state the engine never sees. The daemon-only
session-replay-terminal-lifecycle.ts this arrived with is deleted entirely —
its isExecutableReplayAction was a duplicate of the engine's own.

runReplayScriptFile's Maestro-format routing (including the new --keep-session
Maestro rejection) was extracted into routeMaestroReplay to keep the function
under fallow's complexity threshold after re-threading keepSession through it.

Added packages/ad-replay/src/internal/__tests__/step-loop.test.ts covering the
unified suppression decision (both keepSession and repair-armed) directly
against runAdReplay, including the terminal-among-executable-actions case with
a trailing nested replay marker. The daemon-level integration tests (6 tests
in session-replay-terminal-lifecycle.test.ts, exercising the same behavior
through runReplayScriptFile) and the SDK provider-scenario test
(active-session-script-publication.test.ts) needed no changes and pass
unmodified.
thymikee added a commit that referenced this pull request Aug 3, 2026
… into the ad-replay engine

Rebasing p5/extract-ad-replay onto main pulled in #1554's --keep-session
feature, which had grown its own daemon-side terminal-close-suppression
predicate (session-replay-terminal-lifecycle.ts's
resolveSuppressedTerminalCloseIndex/countExecutedReplayActions) independently
of this branch's own engine-side one (step-loop.ts's
isRepairArmedTerminalCloseAction). Both are the same decision family — replay
--keep-session and an active --save-script repair now share ONE structural
resolution (resolveSuppressedTerminalCloseIndex, generalized to "terminal
among EXECUTABLE actions" rather than the old physical-last-index check) and
one suppression check inside runAdReplay, gated on keepSession OR
runtime.isRepairArmed(). AdReplayRunRequest grew a keepSession field; the
neutral 'replayed' count in AdReplayRunOutcome is now computed inline in the
loop instead of the daemon's old actions.length - entryIndex approximation.

requireLiveSessionForKeepSession (the --keep-session live-session
postcondition) stays daemon-side, inlined into session-replay-runtime.ts,
since it inspects SessionStore state the engine never sees. The daemon-only
session-replay-terminal-lifecycle.ts this arrived with is deleted entirely —
its isExecutableReplayAction was a duplicate of the engine's own.

runReplayScriptFile's Maestro-format routing (including the new --keep-session
Maestro rejection) was extracted into routeMaestroReplay to keep the function
under fallow's complexity threshold after re-threading keepSession through it.

Added packages/ad-replay/src/internal/__tests__/step-loop.test.ts covering the
unified suppression decision (both keepSession and repair-armed) directly
against runAdReplay, including the terminal-among-executable-actions case with
a trailing nested replay marker. The daemon-level integration tests (6 tests
in session-replay-terminal-lifecycle.test.ts, exercising the same behavior
through runReplayScriptFile) and the SDK provider-scenario test
(active-session-script-publication.test.ts) needed no changes and pass
unmodified.
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.

1 participant