Status: done
Added a shared policy-driven EventBatchExecutor used by direct and dual modes. It provides bounded parallelism, conflict serialization, per-event/batch timeout handling, stable ordered results, failure isolation, scoped replay/deduplication, terminal-last execution, and generic policy aggregation for silent, template, LLM, and terminate outcomes. Final full verification: 372 passed, one existing warning.
Execute flat multi-event batches through one shared scheduler in both invocation modes, then apply plugin-declared result policies without any event-name branches.
Add a generic EventBatchExecutor that owns batch mechanics around an EventKernel:
EventDefinition.timeout_seconds;The executor remains generic and never checks built-in event names.
Runtime derives one continuation decision from ordered event results and definition metadata:
TERMINATE success: emit any deterministic sibling confirmations, finish the turn/session, and suppress another ChatAgent call.SILENT_SUCCESS success: keep the already streamed first acknowledgement and do not call ChatAgent again.TEMPLATE_FOLLOW_UP success: emit a plugin-owned deterministic user message, append it to active history, and do not call ChatAgent again unless another result requires LLM synthesis.LLM_FOLLOW_UP success: continue ChatAgent exactly once with compact results.Plugins own deterministic template text through generic definition metadata or a stable payload field; Runtime does not format by event name.
asyncio.gather in EventAgent and direct Runtime with the same batch executor.dual_agent; it resolves arguments with deterministic-first/LLM fallback and returns ordered tool replies.EventAgentParams for shared batch configuration:
max_parallel_events default 4, constrained to 1..16;batch_timeout_seconds default 15, positive and bounded.session.terminate: farewell first, accepted siblings finish or time out, termination executes last, no second ChatAgent call, reusable session emits terminal completion.device.volume.adjust: successful action has no second answer; failure gets one corrective answer.calendar.schedule.create: successful action emits a deterministic confirmation without a model call.knowledge.web.search: first answer, search, then one distinct ChatAgent follow-up grounded in compact results.BaseException/cancellation as an ordinary event failure.src/agent_lab/application/events/batch.pysrc/agent_lab/application/events/models.pysrc/agent_lab/application/events/__init__.pysrc/agent_lab/application/contracts.pysrc/agent_lab/application/tools.pysrc/agent_lab/application/event_agent.pysrc/agent_lab/application/runtime.pysrc/agent_lab/application/events/builtin_plugins.pytests/test_event_batch.pytests/test_event_agent.pytests/test_debug_runtime.pytests/test_builtin_event_plugins.pytests/test_websocket_api.py for config validation/defaults if neededAdd 48.x rows for any review-driven correction. Mark complete only when both modes share the scheduler and all four built-in result policies work without name-specific Runtime logic.
UV_CACHE_DIR=.uv-cache uv run pytest tests/test_event_batch.py tests/test_event_agent.py tests/test_debug_runtime.py tests/test_builtin_event_plugins.py -qUV_CACHE_DIR=.uv-cache uv run pytestUV_CACHE_DIR=.uv-cache uv run python -c "from agent_lab.main import app; print(app.title)"git diff --check