# Todo 48.1 Batch Canonical Replay and Terminal Safety Plan **Status:** done ## Result Added canonical normalized keys, atomic in-flight sharing, bounded/releasable scopes, terminal grace after sibling timeout, off-event-loop sync handlers, deterministic fallback farewell, and dedicated direct/dual web-search two-answer tests. ## Goal Close review-confirmed batch correctness, lifecycle, timeout, and user-visible termination gaps while keeping the scheduler and policy layer generic. ## Canonical Dedupe and Replay - Split canonical request identity from event ID: - batch coalescing uses event name, source, and deterministically normalized request arguments, excluding event ID; - replay identity additionally includes caller scope and event ID. - Apply definition normalizers before forming provider/structured request keys. Text name-only events may coalesce within one batch, but Runtime scopes must include round so parser-local IDs cannot replay stale results across rounds before resolution. - Clone coalesced/replayed `EventResult` correlation fields for each requested event ID while retaining explicit deduplication provenance. - Add an in-flight future map guarded atomically so concurrent identical replay keys execute one handler. - Add explicit scope release and a defensive capacity bound; Runtime/EventAgent release round scopes after results are delivered. ## Terminal Deadline - Non-terminal siblings use the configured batch deadline. - After siblings finish or become timeout results, terminal events receive a short independent grace window bounded by their own timeout. - Mark `deadline_exceeded=True` for the sibling phase while still allowing terminal execution. - Preserve terminal-last order and failure isolation. ## Sync Handler Timeout - Run synchronous handlers off the event loop in async kernel execution so event/batch timeout can return promptly and unrelated events/session work remain responsive. - Keep `execute_sync()` behavior unchanged. - Do not swallow cancellation/BaseException. - Document/test that timeout stops awaiting the worker; production side-effect ports should remain cancellation/idempotency aware. ## Farewell and Search Acceptance - Give `session.terminate` a plugin-owned deterministic farewell factory. - When a successful terminal batch has no visible ChatAgent content, emit that deterministic farewell before ending one-shot/reusable sessions. - If ChatAgent already streamed a farewell, do not duplicate it. - Add dedicated `knowledge.web.search` two-answer regressions for both modes: first visible response, search/tool execution, one grounded second ChatAgent response. ## Files - Modify: `src/agent_lab/application/events/batch.py` - Modify: `src/agent_lab/application/events/kernel.py` - Modify: `src/agent_lab/application/events/models.py` - Modify: `src/agent_lab/application/events/builtin_plugins.py` - Modify: `src/agent_lab/application/tools.py` - Modify: `src/agent_lab/application/event_agent.py` - Modify: `src/agent_lab/application/runtime.py` - Modify: `tests/test_event_batch.py` - Modify: `tests/test_event_kernel.py` - Modify: `tests/test_debug_runtime.py` ## Requirement Execution Loop ### Plan 1. Add RED normalized-equivalence, different-ID coalescing, cross-round stale replay, concurrent in-flight replay, and scope cleanup tests. 2. Add RED sibling-timeout/terminal-grace and blocking-sync-handler timeout tests. 3. Add RED tool-call-only terminate farewell and direct/dual search two-answer tests. 4. Implement canonical keys, atomic in-flight sharing, bounded cleanup, terminal grace, async sync-handler dispatch, and policy-owned farewell. 5. Run focused/full verification and renewed bounded reviews. ### Observe / Update Close Todo 48 only when no Critical/Important findings remain and the named direct/dual user-visible flows are permanent regressions. ## Verification - `UV_CACHE_DIR=.uv-cache uv run pytest tests/test_event_batch.py tests/test_event_kernel.py tests/test_debug_runtime.py -q` - `UV_CACHE_DIR=.uv-cache uv run pytest` - `git diff --check`