feat(kernel): proactive V2 — event-driven proactive architecture (#786)#793
feat(kernel): proactive V2 — event-driven proactive architecture (#786)#793
Conversation
) - Restructure proactive module: proactive.rs → proactive/judgment.rs - Add ProactiveSignal enum with 5 signal kinds - Add ProactiveConfig (bon::Builder + Deserialize, no Default) - Add ProactiveFilter with quiet hours, cooldown, and rate limiting - Add KernelEvent::ProactiveSignal variant + constructor - Add structured context pack builder for Mita - Reformat heartbeat message as structured context pack - Wire handle_proactive_signal + deliver_proactive_to_mita in kernel
) - IdleCheck: emit SessionIdle for sessions idle >30 min - handle_scheduled_task: emit TaskFailed on spawn failure - TurnCompleted: emit SessionCompleted for worker sessions - Processor 0 scheduler: compute next MorningGreeting/DailySummary from work_hours + timezone config - Add ProactiveConfig to MitaConfig + wire to KernelConfig - Update kernel AGENT.md with proactive V2 section Closes #786
Code Review — PR #793: Proactive V2 Event-Driven SignalsFiles reviewed: 10 files, +922 / -6 lines FindingsP0 - Critical(none) P1 - High
P2 - Medium
P3 - Low
Architecture AssessmentStrengths:
Concerns:
Verdict: 3 issues to fixP1 #2 (SessionCompleted feedback loop), P1 #3 (per-kind cooldown blocking multi-session idle signals), P2 #5 (hardcoded idle threshold) should be addressed before merge. The remaining P2/P3 items can be follow-up work. |
- Fix SessionCompleted feedback loop: skip Mita child sessions - Fix per-kind cooldown: use session-scoped cooldown keys so idle detection works across multiple sessions independently - Move idle threshold from hardcoded 30m to ProactiveConfig field - Replace .unwrap() with .expect() in context.rs - Add warning logs for invalid config values - Fix should_pass doc comment about state mutation - Add std::sync::Mutex rationale comment Closes #786
Fixes Applied
|
Code Review — Re-review after fixes (PR #793)Files reviewed: 10 files, +995 / -6 lines Verification of Previously Reported Issues
All 7 previously identified issues are correctly resolved. New FindingsP0 — Critical(none) P1 — High(none) P2 — Medium(none) P3 — Low
Architecture Assessment
Verdict: Clean Two P3 cosmetic suggestions, neither blocking. The previously reported issues are all properly fixed. The implementation is well-structured with clear separation of concerns and appropriate safety guardrails. |
- Rename _turn_failed → turn_failed (variable is now used)
- Build SessionContext in handle_proactive_signal from process table
so Mita receives session name and idle duration in context pack
- Embed session_key in SessionIdle/SessionCompleted signal variants,
removing the separate session_key parameter from filter API
- Cache parsed timezone/time values in ProactiveFilter::new() to
avoid re-parsing IANA timezone on every signal check
- Evict stale cooldown entries in maybe_reset_hourly_window to
prevent unbounded last_fired HashMap growth
- Hoist SessionKey::deterministic("mita") outside emit_idle_signals loop
- Deduplicate truncate() — shared via proactive/mod.rs
- Add ProactiveConfig::validate() for startup-time config validation
- Refactor build_heartbeat_context_pack to sections style with
shared AVAILABLE_ACTIONS constant
Closes #786
Round 2 Fixes Applied
Design change: Embedded |
crrow
left a comment
There was a problem hiding this comment.
SessionCompleted 信号的触发时机有问题
当前 SessionCompleted 在 has_result_tx 分支(worker/child session turn 完成时)触发,但这不符合"对话自然结束"的语义。
问题:普通用户对话结束时不会触发 SessionCompleted,只有子 session 完成才会。
建议:SessionCompleted 应该基于用户沉默时间判断,和 SessionIdle 统一在 emit_idle_signals 中处理:
SessionCompleted— 用户 10 分钟没说话(session_completed_secs: 600),对话视为结束,Mita 可做总结/归档SessionIdle— 用户 1 小时+ 没说话(idle_threshold_secs: 3600),长时间空闲,Mita 可提醒/跟进
具体改动:
- 删除
has_result_tx分支里的SessionCompletedemit 逻辑 - 在
ProactiveConfig中新增session_completed_secs: u64(默认 600) - 在
emit_idle_signals中同时检查两个阈值,按last_activity统一判断 - cooldown 各自独立(
session_completed和session_idle分别有自己的 cooldown key)
SessionCompleted previously fired when a worker/child session finished (has_result_tx branch). Now it fires from emit_idle_signals() when a user conversation is idle for session_completed_secs (default 600s), detected alongside SessionIdle with a two-threshold approach: - idle >= completed_threshold AND < idle_threshold → SessionCompleted - idle >= idle_threshold → SessionIdle (not both) Closes #786
Read the last user message from the session tape when building the proactive context pack for SessionIdle and SessionCompleted signals, so Mita has conversation context for its judgment. Closes #786
Add [Mita History] section to both signal and heartbeat context packs so Mita can see its recent actions and avoid redundant behavior. - Add MitaHistory struct to proactive::context - Wire build_mita_history() into handle_proactive_signal and handle_mita_heartbeat - Extract last 5 tool calls from the Mita tape as recent action lines - Add tests for history inclusion, empty-history omission, and heartbeat variant Closes #786
#786) Add a signal_judgment module that pre-filters proactive signals with a cheap LLM call before routing them through a full Mita agent turn. This prevents noise from low-value signals (e.g. idle sessions with no actionable context). - New SignalJudgment enum (ShouldAct/ShouldDrop) with YES/NO parsing - Optional judgment_model field in ProactiveConfig (backward compatible) - Wired into handle_proactive_signal after context pack is built - Defaults to ShouldDrop on LLM errors (fail-safe for silence) - 6 unit tests for parse function Closes #786
Add a Mita-exclusive tool that allows dynamic updates to the proactive filter configuration (quiet hours, cooldowns, rate limits). The tool reads/writes config_dir()/mita/proactive.yaml. Closes #786
Replace hardcoded AVAILABLE_ACTIONS with dynamic tool list from Mita's agent manifest when available, falling back to the static const. Closes #786
…786) - Add SAFETY comment to proactive_filter Mutex field - Update AGENT.md: signal judgment layer, signal_judgment.rs, idle-based SessionCompleted, new invariants for judgment bypass and ProactiveConfig - Add addendum to design doc covering LLM judgment, Mita History, idle-based SessionCompleted, mita_update_proactive_config tool, dynamic actions Closes #786
- Add ReloadProactiveConfig kernel event so update-proactive-config tool syncs in-memory filter immediately after writing to disk - On startup, prefer runtime proactive.yaml over main config to resolve config source-of-truth conflict - Populate SessionCompleted.summary with session name instead of empty string - Validate session_completed_secs < idle_threshold_secs in ProactiveConfig Closes #786
Summary
Implement event-driven proactive architecture (Proactive V2) replacing the polling-only heartbeat mechanism. Three layers:
ProactiveSignalvariants inKernelEvent(SessionIdle, TaskFailed, SessionCompleted, MorningGreeting, DailySummary)ProactiveFilterwith quiet hours, per-signal cooldowns (session-scoped), and global hourly rate limiting. Zero LLM cost.Heartbeat demoted from sole driver to fallback patrol — now also uses the structured context pack format.
Type of change
enhancementComponent
coreCloses
Closes #786
Test plan
cargo check -p rara-kernelpassescargo clippy -p rara-kernel --all-targets --all-features --no-deps -- -D warningspassescargo test -p rara-kernelpasses (16 tests, including 14 proactive-specific)