Repository navigation
feat(autonudge): wall-clock runtime budget for monitoring loops - #2116
Conversation
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
Opus 5 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo blocking findings. FINDING — src/kiro_crew/autonudge_authz.py:62 — added comment carries review-round narration, [OPUS-REVIEWED] be6d0b4 |
Design Review (Fable 5, fork) — ✅ PASSAdvisory design-level review of Design-Verdict: PASS Sound, restart-proof time bound with symmetric revival semantics; [DESIGN-REVIEWED] be6d0b4 |
b95d363 to
170125b
Compare
170125b to
e9a4639
Compare
e9a4639 to
3c4c355
Compare
3c4c355 to
f8f93fe
Compare
f8f93fe to
9a9b22f
Compare
9a9b22f to
498a115
Compare
…itoring loops A cycle cap alone cannot bound COST: a loop with slow turns or a long idle gap can run for days within its cycle budget. This adds an opt-in second terminal bound - max_runtime_secs (0 = unlimited, schema-capped at 7 days) - measured from the persisted created_ts so a gateway restart re-arms the loop without resetting its clock. - NudgeLoop/add/update carry the budget; _timer enforces it via the shared runtime_budget_exceeded predicate, checked after the cycle cap and before the fire so a spent budget never buys one more unattended turn - Terminal treatment matches the cap: deactivate + 'expired' event; the notifier words the notification per bound with the same predicate - Budget-stopped loops are revivable via monitor_update (raise the budget above elapsed), and the paused-loop denial names the bound that stopped it - Plumbed through both authz chokepoints (audited; non-integral or overflowing input is 400, incl. 1e309 -> inf), POST/PATCH /api/autonudge, monitor_start/monitor_update MCP tools (MONITOR_*_SCHEMA bounds 0-604800), and the directive applier's spent-budget guard
498a115 to
be6d0b4
Compare
…itoring loops (kirodotdev#2116) A cycle cap alone cannot bound COST: a loop with slow turns or a long idle gap can run for days within its cycle budget. This adds an opt-in second terminal bound - max_runtime_secs (0 = unlimited, schema-capped at 7 days) - measured from the persisted created_ts so a gateway restart re-arms the loop without resetting its clock. - NudgeLoop/add/update carry the budget; _timer enforces it via the shared runtime_budget_exceeded predicate, checked after the cycle cap and before the fire so a spent budget never buys one more unattended turn - Terminal treatment matches the cap: deactivate + 'expired' event; the notifier words the notification per bound with the same predicate - Budget-stopped loops are revivable via monitor_update (raise the budget above elapsed), and the paused-loop denial names the bound that stopped it - Plumbed through both authz chokepoints (audited; non-integral or overflowing input is 400, incl. 1e309 -> inf), POST/PATCH /api/autonudge, monitor_start/monitor_update MCP tools (MONITOR_*_SCHEMA bounds 0-604800), and the directive applier's spent-budget guard
What
Adds an opt-in wall-clock runtime budget (
max_runtime_secs, 0 = unlimited, capped at 7 days) to AutoNudge monitoring loops, alongside the existingmax_cyclescap.Why
A cycle cap alone cannot bound cost: a loop with slow turns or a long idle gap can run for days within its cycle budget. A wall-clock budget gives unattended loops a hard TIME bound ("babysit this for at most 2 hours") regardless of turn duration or idle gap.
How
created_ts(not arm time), so a gateway restart re-arms the loop without resetting its clock. A store entry with nocreated_tsnever trips (no anchor — guessing one could kill a healthy loop on its first post-upgrade cycle)._timervia a sharedruntime_budget_exceededpredicate, checked after the cycle cap (both exhausted → the cap wins) and before the fire, so a spent budget never buys one more unattended turn.expiredevent;_notify_nudge_expiredwords the user notification per bound using the same predicate — a budget-stopped loop is never mistaken for a manual Stop or a cap-out.max_cycles:POST/PATCH /api/autonudge(up-front int coercion)monitor_start/monitor_updateMCP tools (MONITOR_*_SCHEMAbounds 0–604800)monitor_updatewhose new budget is at/below the loop's elapsed age (would deactivate without another fire), mirroring its cycle-cap guardautonudge_statebroadcasts include the new field (additive, backwards-compatible).learn-cron-dashboard.md) updated in the same commit per AGENTS.md.Tested
test_autonudge.py: 80 passed (7 new: predicate contract incl. boundary/no-anchor, spent-budget deactivation+expired+no-extra-fire, unspent fires normally, 0 = unlimited, persistence round-trip of budget AND clock anchor, update semantics/clamping)test_autonudge_stop_auth.py: new MCP pass-through tests + applier spent-budget guard test; existing payload assertion updated for the new fieldtest_autonudge_dashboard_fire,test_autonudge_remove_offload_425,test_validation,test_session_directive,test_slack_gateway,test_workflows_nudge_wiring(fake svc double extended),spec_builder/tests/test_routes,test_mcp_core*,test_dashboard_chat*— all green (~1,450 tests)Not in scope
Token budgets (needs turn-level accounting) and a Goal-popover UI field for the budget — both follow-ups. The REST/MCP/WS surfaces already expose the field for the UI to adopt.
Why this fits Kiro Crew
TENETS.md #1 puts safety first: "every action is gated, auditable, and reversible." An unattended loop that re-injects turns is the highest-leverage action the system takes without a human present, and today its only bound is a cycle count — which the codebase itself documents as insufficient (
_MONITOR_DEFAULT_MAX_CYCLESexists because "observed loop stores show that is unreliable: real babysit loops ran to 24/24 and 20/20 cycles"). A wall-clock bound completes the containment story for the dimension users are actually billed in: time. It changes nothing for anyone who doesn't set it (0 = unlimited, default).