Skip to content

feat(session_store): add session_store_flush option for eager mirroring#905

Merged
qing-ant merged 1 commit intoanthropics:mainfrom
lorenzojb:feat/session-store-eager-flush
May 3, 2026
Merged

feat(session_store): add session_store_flush option for eager mirroring#905
qing-ant merged 1 commit intoanthropics:mainfrom
lorenzojb:feat/session-store-eager-flush

Conversation

@lorenzojb
Copy link
Copy Markdown
Contributor

Summary

Adds ClaudeAgentOptions.session_store_flush: Literal["batched", "eager"] (default "batched") so callers can opt into near-real-time SessionStore.append() delivery instead of waiting for the end-of-turn flush.

Today the TranscriptMirrorBatcher buffers transcript_mirror frames and only flushes when the result message arrives (or on 500-entry / 1 MiB overflow). That keeps adapter latency off the streaming hot path, but it means an external store can't observe a turn until it's finished — a problem for live-tailing UIs, cross-process resume, or crash-durability use cases that want the mirror to track the on-disk JSONL closely.

With "eager", build_mirror_batcher() zeroes both pending thresholds so every enqueued frame schedules a background drain. The drain still runs off the read loop via asyncio.ensure_future, so a slow adapter does not stall message streaming — it just sees frames coalesced while it's busy. Append ordering is preserved by the existing batcher lock.

Also exports the SessionStoreFlushMode type alias for callers that thread the value through their own config.

API

ClaudeAgentOptions(
    session_store=my_store,
    session_store_flush="eager",  # default: "batched"
)

Tests

6 new tests in tests/test_transcript_mirror.py:

  • TestBuildMirrorBatcherFlushMode::test_flush_mode_sets_thresholds[default|batched|eager] — parametrized: omitted/"batched" keep MAX_PENDING_* defaults, "eager" zeroes both
  • TestBuildMirrorBatcherFlushMode::test_eager_mode_flushes_per_frame — two enqueues → two separate append() calls without an explicit flush()
  • TestBuildMirrorBatcherFlushMode::test_options_default_is_batchedClaudeAgentOptions() default
  • TestReceiveLoopFramePeeling::test_eager_flush_mode_appends_per_frame_before_result — end-to-end through query(): with session_store_flush="eager" and a transport that yields between frames, the store sees one append() per frame before the AssistantMessage is yielded (vs. a single coalesced batch in the default mode)

_make_mock_transport() gains a yield_between kwarg so the integration test can model the await on real stdout I/O between frames.

Test plan

  • python -m ruff check src/ tests/
  • python -m ruff format src/ tests/
  • python -m mypy src/
  • python -m pytest tests/ (734 passed, 4 skipped)

Adds ClaudeAgentOptions.session_store_flush ("batched" | "eager",
default "batched"). With "eager", build_mirror_batcher() zeroes the
TranscriptMirrorBatcher pending thresholds so every transcript_mirror
frame schedules a background flush, delivering entries to
SessionStore.append() in near real time instead of coalescing until the
end-of-turn result message. Appends remain serialized in enqueue order;
a slow adapter does not stall the read loop (frames coalesce while it
is busy).

Exports the SessionStoreFlushMode type alias.
@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@b512f25). Learn more about missing BASE report.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #905   +/-   ##
=======================================
  Coverage        ?   88.24%           
=======================================
  Files           ?       23           
  Lines           ?     3904           
  Branches        ?        0           
=======================================
  Hits            ?     3445           
  Misses          ?      459           
  Partials        ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@qing-ant
Copy link
Copy Markdown
Contributor

qing-ant commented May 3, 2026

/sdk-e2e-proof — PR #905

Manual end-to-end proof against the real CLI for ClaudeAgentOptions.session_store_flush.

Setup: in-memory SessionStore recording (timestamp, len(entries)) on every append(); observed append-call counts mid-stream vs after ResultMessage.

Case A: query() eager — PASS

  • append() calls before ResultMessage: 3; total append() calls: 3; total entries: 8; per-message snapshots (msg#, appends): [(1, 0), (2, 2), (3, 3), (4, 3)]

Case B: query() batched — PASS

  • append() calls before ResultMessage: 0; total append() calls: 1; total entries: 8; per-message snapshots (msg#, appends): [(1, 0), (2, 0), (3, 0), (4, 1)]

Case C: client eager multi-turn — PASS

  • append() calls per turn: [3, 3]; grand total append() calls: 6; total entries: 13

Overall: PASS

Eager mode produced multiple append() calls before the result message (near-real-time mirroring); batched mode coalesced into an end-of-turn flush as expected.

@qing-ant qing-ant enabled auto-merge (squash) May 3, 2026 17:57
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.

3 participants