Skip to content

refactor: route babysit through structured monitors - #5186

Open
kyleseaman wants to merge 2 commits into
token-monitors-dashboardfrom
token-monitors-babysit
Open

refactor: route babysit through structured monitors#5186
kyleseaman wants to merge 2 commits into
token-monitors-dashboardfrom
token-monitors-babysit

Conversation

@kyleseaman

@kyleseaman kyleseaman commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Stacked change: PR 7 of 8

Stack: #5180#5181#5182#5183#5184#5185#5186#5305
Base: #5185
Next: #5305

Problem / Motivation

The shipped babysit guidance teaches prompt-owned polling, voluntary completion detection, and manually managed progress state for GitHub pull requests.

Why it matters

That guidance spends a full agent turn on unchanged state and empirically tends to stop at its cap instead of stopping because the objective was actually observed.

What changed (motivation → approach → change)

  • Replace GitHub polling instructions with one bounded monitor_watch call, later authoritative monitor_inspect calls, and explicit monitor_update re-arming.

  • Document conductor self-patrol as a first-class monitor use while keeping the compatibility watcher finite and secondary.

  • Document zero-turn unchanged probes, one wake per actionable fingerprint, finite budgets, completed-turn accounting, durable stop, and explicit restart.

  • Persist pre-probe budget exhaustion before disarming the live monitor, keeping memory and restart state aligned when a terminal write fails.

  • Route Webex sessions through the finite legacy path because structured wake delivery and completion correlation are unavailable there.

  • Keep a clearly labeled finite legacy fallback for unsupported targets and readiness definitions that depend on generic PR comments or advisory findings outside the typed provider facts.

  • Add packaged monitoring guidance and executable lifecycle scenarios using the real tool-to-directive-to-controller path.

  • Scope the legacy tool advertisement to public GitHub at this layer and to the same evidence boundary: structured review readiness is preferred only when typed provider facts fully determine it.

  • Route requests that require a final report or notification through the finite legacy path, because terminal structured success deliberately consumes zero model turns.

  • Deliver gated legacy Slack terminal notifications with durable at-least-once acknowledgement: terminal state is persisted before delivery, startup replays retained undelivered notifications, and the exact monitor generation is marked only after emission; audit unsupported evidence scopes separately from unsupported session bindings.

  • Render terminal notifications from the stored stop reason: state closed-unmerged PRs directly, distinguish merged from ready-for-review success, name known provider/delivery blockers, and identify the watched PR URL even for channel-bound watches. Unknown reasons retain a safe details fallback; unavailable conversations point to a new watch from an active conversation.

  • Scope the finite-budget policy to the legacy MCP tools: shared REST/workflow AutoNudge helpers retain 0 = unlimited, with the seven-day ceiling applying to positive runtime grants.

Tests

  • Eight executable babysit lifecycle scenarios
  • Tool schema and authoritative application boundaries
  • No-change, actionable deduplication, success, blocked, budget, BUSY-expiry, user-stop, and restart behavior
  • Fresh instruction pressure samples using the exact bounded tool schema
  • Policy contracts for comment-only and advisory review evidence outside the provider, plus the terminal-report boundary

Previous notification verification: all 32 focused notification/observer tests pass, including 12 reason-specific regressions and target identification for dashboard and channel-bound watches. Merged success alone says no action is needed; review readiness asks the operator to review the PR, and only a recorded closed-unmerged reason offers reopening. Focused Linux-platform mypy, Black, flake8, isort, docs lint, brand, and diff checks pass. The notification-only follow-up does not change budgets, delivery, or terminal-wake policy. The overall PR does change legacy MCP defaults: 24 cycles and 4 hours, with explicit zero/unlimited caps rejected. Long-interval watches need an explicitly larger runtime (up to 7 days); the runtime limit can stop the watch before all 24 cycles are used. Shared REST/workflow helpers retain zero/unlimited compatibility. Fresh CI and UX review on the pushed head are pending.

Previous recipe follow-up: the finite legacy recipe explicitly sets gate: false, so unchanged typed provider facts cannot suppress cycles that must inspect generic comments or advisory feedback. The executable recipe regression failed with the old omitted flag and passes with the correction; a fresh reader also includes the opt-out. All 22 focused babysit/ack tests pass. The specification now accurately distinguishes positive MCP budgets from REST/workflow zero/unlimited compatibility. Docs lint, Black, flake8, brand, and diff checks pass. Screenshot bytes and runtime implementation are unchanged by this follow-up; fresh CI is pending on the restacked head.

Previous security follow-up: redact the completed terminal notification body, including retained targets, through the shared URL and credential redactors before dashboard persistence. Three regressions cover credential-shaped GitHub paths, encoded credentials in URLs, and malformed retained plain credentials; all three fail without the fix. All 54 focused notification, gateway-initialization, babysit, and acknowledgement tests pass on this layer. Black, isort, flake8, docs lint, and diff checks pass. The composed top also passes all 187 focused tests and Linux-targeted handler mypy. Screenshot bytes are unchanged; fresh CI and review on 5c975cb4467f15bdde12340785f4680908ec5fe3 are pending. No manual CI rerun.

Latest main-restack verification on c3169529b90019775a65cecdfd7429556ba01926: all 112 focused monitor, babysit, acknowledgement, directive, dashboard-handler, and Slack notification tests passed. Docs lint and diff checks also passed. The documentation conflict preserves main's shared loop-by-id accessor invariant while retaining this layer's structured-monitor contract. Screenshot bytes are unchanged. Fresh CI and reviews are pending; no manual CI rerun was made.

Manual verification

N/A — executable lifecycle scenarios cover runtime outcomes, and a focused policy contract pins the skill's structured-versus-legacy evidence boundary.

Related Issues

N/A — completes the migration specified in #5180 and depends on #5185.

Checklist

  • Commit count satisfies the stacked PR hygiene limit
  • Existing tests pass and new tests cover the new behavior
  • Self-review completed; code follows project style guidelines
  • Documentation updated where applicable
  • No secrets, credentials, or internal references in the diff

Contribution License Agreement

N/A — the repository template does not yet supply final CLA wording.

Current review/CI follow-up on a46b9635bc317874b4a28d8d274da9b9e3cecbad: restore the documentation tail lost during conflict reconstruction; use inferable pull-request URLs in both monitor examples; distinguish Webex's stop-and-recreate path from monitor_update; and align the banner regression with this layer's intentional four-hour finite default while continuing to require the banner field to be absent. All 300 focused monitor, babysit, acknowledgement, directive, and registry tests pass. Docs lint, Black, flake8, and diff checks pass. Screenshot bytes are unchanged. Fresh CI and reviews are pending; no manual CI rerun was made.

Current main-restack verification on c0056fdc0b4457f148876457c528985aeae0b1d9: this layer is directly based on #5185 at 85e0baa2c4e24b322e9fa1c1af8e08a21fe6589a. Terminal notification delivery is durable and replay-safe for the exact monitor generation. The composed stack passes all 2,430 changed backend tests and all 101 focused sidebar/popover frontend tests; TypeScript compilation and the production frontend build also pass. Screenshot bytes are unchanged. Fresh CI and reviews are running; no manual CI rerun was made.

Current durability/restack verification on 3b5f883a3721d35179b1b7d7caeb14c8df6dd317: this layer is directly based on #5185 at c5688956f5159c509895e7e1a96a3aad96ef1bfb. Terminal notification persistence is awaited successfully before the exact generation is marked delivered, so failed writes remain replayable. The composed open stack passes all 446 focused controller, provider, AutoNudge, and Slack gateway tests; focused Linux mypy, flake8, and docs lint pass. Screenshot bytes are unchanged. Fresh CI and reviews are running; no manual CI rerun was made.

Current main-restack verification on b2e22498966e2c2aa4f016e85426a5facb616c6f: this two-commit layer is directly based on #5185 at d3dfa8d704b8e48b90bb5d60a49b432115fb2728. The test overlap retains dashboard directive-delivery assertions and the structured babysit evidence-scope contract. The composed open tail passes all 373 focused backend tests and all 218 focused frontend tests; TypeScript compilation, focused Black, and flake8 pass. Screenshot bytes are unchanged. Fresh CI and reviews are running; no manual CI rerun was made.

Current review-fix/restack verification on e8ddb34e3211e8c417dabfa0b543773d0bec72a9: this two-commit layer is directly based on #5185 at 0085af85de0c715529148d61fea3eb4c6f87791e. The introduced test/test_monitor_mcp.py formatting failure is corrected in its owning commit; Black leaves the file unchanged and all 19 monitor MCP tests pass. Screenshot bytes are unchanged. Fresh CI and reviews are running; no manual CI rerun was made.

Current main-restack verification on 7e2a294b73b34eb5e9300a8a5755c03ea0884c1f: this two-commit layer is directly based on #5185 at 12eecef59dcb20f1f973969d91aea2df0c4ec1cf. The sole conflict preserves main’s concurrent skill-catalog scan contract alongside this layer’s structured babysit recipe. The composed top passes 125 focused frontend tests, TypeScript compilation, all 19 i18n gates against origin/main, 117 focused monitor/provider tests, and docs lint. Screenshot bytes are unchanged. Fresh CI and reviews are running; no manual CI rerun was made.

Current review-fix/restack verification on edc01d9: this two-commit layer is directly based on #5185 at 5866cc1; its notification-persistence commit is patch-equivalent to the prior head, while the first commit inherits #5185's raw-quarantine specification. The composed open tail passes 284 focused backend tests and 160 focused frontend tests, plus TypeScript, targeted ESLint, all 19 i18n gates, and docs lint. Fresh CI and reviews are running; no manual CI rerun was made.

Current hygiene-clean verification on 51f975e: this PR is exactly two commits and is directly based on #5185 at 6d4630a. The composed top tree remains byte-for-byte identical to the already verified pre-fold tree. No CI job was manually rerun.

Current inherited-fix restack verification on 03cb97f: this two-commit layer is directly based on #5185 at 5afa6d3 and inherits its non-finite quarantine fix without changing this layer’s own patch. The composed open tail passes all 285 focused monitor, MCP, GitHub, and source-provider tests. Fresh CI and reviews are running; no manual CI rerun was made.

Current main-restack verification on a3284eb: this two-commit layer is directly based on #5185 at 47fede7, preserving the linear stack on main at 8aef8fe. Its own patch is unchanged; the composed top passes TypeScript, the analyze build, the 812-chunk bundle gate, and all 19 base-aware i18n checks. Fresh CI and reviews are running; no manual CI rerun was made.

Current main-restack verification on e444ec9: this two-commit layer is directly based on #5185 at e677cac. Its structured-monitor and durable terminal-notification patches replay cleanly on main at 6309382. The composed top passes 1,013 focused backend tests, 145 focused dashboard/WebSocket tests, TypeScript, all 19 i18n gates, docs lint, focused flake8 and Linux mypy, the analyze build, and the 812-chunk bundle gate. Fresh CI and reviews are running; no manual CI rerun was made.

Current guidance-fix verification on 2ea5aa6: the shipped monitor_start instructions again include a concrete full GitHub pull-request URL that the real subject inference accepts, so copied legacy examples activate observation gating. The regression reproduced on the prior head and all 3 guidance-gate tests now pass; all 92 focused guidance, babysit, controller, and monitor-MCP tests plus docs lint and diff checks pass. This layer remains exactly two commits on #5185 at e677cac. #5305 was restacked with an explicit lease. Fresh CI and reviews are running; no manual CI rerun was made.

Current source-path review fix on 3389378: babysit scenario fixtures now derive repository files from Path(__file__).resolve(), clearing the current GPT blocking finding without changing runtime behavior. All 502 focused babysit and monitor tests pass, along with docs lint. This layer remains exactly two commits directly on #5185; #5305 was restacked and pushed bottom-up with explicit leases. Fresh CI and reviews are running; no manual CI rerun was made.

Current source-path gate fix on f904536: babysit scenario fixtures derive repository files from an absolute resolved root and the touched test is Black-clean. All 502 focused babysit and monitor tests pass, plus focused Black and docs lint. This layer remains exactly two commits directly on #5185; #5305 was restacked and pushed bottom-up with explicit leases. Fresh CI and reviews are running; no manual CI rerun was made.

Current main-restack verification on fc34b5d: this two-commit PR is directly based on #5185 at e993142. The prepare-pr guidance resolution preserves main’s current babysit verification wording and this layer’s finite runtime/cycle budget, explicit monitor_update re-arm, and gate:false fallback requirements. #5305 was restacked on this head with an explicit lease. The composed top passes 314 focused backend tests, the production website build, all 19 base-aware i18n gates, Black, subprocess encoding, brand, and docs lint. Fresh CI and reviews are running; no manual CI rerun was made.

Current inherited main-restack verification on 950ee12: this two-commit PR is directly based on #5185 at 8c0de58, preserving the linear open tail. Its babysit patch is unchanged. The composed top passes the 3,628-test selected backend run, all 1,344 focused frontend tests, the production website build, all 19 base-aware i18n gates, Black, subprocess encoding, brand, and docs lint. Fresh CI and reviews are running; no manual CI rerun was made.

Current inherited main-restack verification on 950ee12: this two-commit PR is directly based on #5185 at 8c0de58, preserving the linear open tail. Its babysit patch is unchanged. The composed top passes the 3,628-test selected backend run, all 1,344 focused frontend tests, the production website build, all 19 base-aware i18n gates, Black, subprocess encoding, brand, and docs lint. Fresh CI and reviews are running; no manual CI rerun was made.

Current terminal-delivery review fix on 91ce5df: the Slack observer is installed before service startup, so a terminal transition during startup cannot escape both live delivery and replay. Delivery acknowledgements now persist the exact terminal generation on the stable outer loop record, preserving opaque future-version monitor payloads while preventing repeat notices after restart. Both regressions failed on the prior head and pass after the fix; all 348 focused AutoNudge, persistence, and Slack gateway tests pass, plus 410 broader Slack/babysit tests. Focused Black, subprocess encoding, flake8, Linux mypy, docs lint, brand, and diff checks pass. Screenshot bytes are unchanged. #5305 was restacked and pushed bottom-up with explicit leases. Fresh CI and reviews are running; no manual CI rerun was made.

Current inherited main-restack verification on 0de1af3e4bdb993b3137478b9db6276f4e8277cb: this two-commit layer is directly based on #5185 at 8cb33d195d3e91dbf18b595f038e5238641b26fa. All 426 focused AutoNudge, monitor, directive, and terminal-notification tests pass. #5305 was restacked and force-pushed above it with an explicit lease. Screenshot bytes are unchanged and any immutable screenshot URL now names the current composed top. Fresh CI and reviews are running; no CI job was manually rerun.

Current inherited dashboard review fix on e2a3efceed983ede5ded0333052e17b461569943: this two-commit layer is directly based on #5185 at 9f55d44ad993f46c63b9517aa7775eec077cc5ee and inherits its live legacy-editor reseed and full-registry removal invalidation. Git range-diff confirms both babysit patches are unchanged. #5305 was restacked and force-pushed above it with an explicit lease. Screenshot bytes are unchanged. Fresh CI and reviews are running; no CI job was manually rerun.

Current inherited main-restack verification on fbd19a2b70e0d1ae647dc9a2c45fd90e7e3baf3b: this two-commit PR is directly based on #5185 at 2534f95361c6e1e8f9a833cb2e199925a9887aea; both of its patches replayed without conflicts. #5305 was restacked above it. The composed top passes 876 focused backend tests and 275 focused frontend tests, TypeScript, targeted ESLint, Black, subprocess encoding, all 19 base-aware i18n gates, docs lint, and brand lint. Screenshot bytes are unchanged. Fresh CI and reviews are running; no CI job was manually rerun.

Current final main-restack verification on be1a2de18a0bd15a3afa51abba6b3f9e4edaeff7: this two-commit PR is directly based on #5185 at ee3e3a232d5ef7687c227a839aa6bd1d340c5869. The AutoNudge resolution preserves main’s trusted crew/member self-arm and visible arm outcome together with this layer’s channel-specific monitor guidance; closing-slot admission retains the authoritative _closing fence. The composed top passes 1,264 focused self-arm, directive, provider, dashboard, and Slack tests with 2 platform skips, plus TypeScript, i18n, Black, isort, flake8, Linux-targeted mypy, docs, brand, and repository gates. Fresh CI and reviews are running; no CI job was manually rerun.

Current main-restack verification on f93c40c10f01327fad422a3a35c53ba2e3c8bc01: this two-commit PR is directly based on #5185 at bc9105c4497d976920b384b8ea00928112c26ca4, preserving the linear open tail; #5305 was restacked on this head with an explicit lease. The composed top passes 1,265 focused self-arm, directive, provider, dashboard, and Slack tests with 2 platform skips, plus focused Black, isort, flake8, Linux-targeted mypy, docs, subprocess-encoding, and brand gates. Fresh CI and reviews are running; no CI job was manually rerun.

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of f93c40c10f01327fad422a3a35c53ba2e3c8bc01 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] f93c40c

False positive or not applicable? A repository writer can comment:
/ai-review override gpt f93c40c10f01327fad422a3a35c53ba2e3c8bc01: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — 🟡 CONCERNS

Design-level review of f93c40c10f01327fad422a3a35c53ba2e3c8bc01 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: CONCERNS

Sound migration aimed at a measured harm, but it deliberately reverses the pinned "explicit 0 = unlimited" contract — a human should confirm that reversal.

Watch

  • test_monitor_start_explicit_zero_cap_stays_zero pinned "an explicit 0 means the caller really wants unlimited — 0 stays 0"; this PR rewrites it to test_monitor_start_rejects_unbounded_zero_limits and caps any single arm at 7 days with a 4-hour runtime default. A long-interval or "watch until it merges, however long it takes" legacy loop now terminates mid-watch (default 4h can stop before 24 cycles) and needs an explicit restart — a capability removal for callers relying on the old contract, not just a new default. The description owns it as policy, and REST/workflow helpers retain 0 = unlimited, but the pin was a named decision.
    Clears when: a human reviewer (or the stack spec in feat: RFC for token-efficient monitors #5180) explicitly confirms rejecting explicit-unlimited on the MCP surface, not just the changed default.
  • Webex gets monitor_start but not monitor_update; the prescribed stop-and-recreate resets the wall-clock budget anchor ("measured from when the loop was first armed"), so the finiteness story is weaker on the one surface that must recreate to re-arm.
    Clears when: the Webex guidance or spec states that recreate resets the runtime budget and that this is accepted.

Suggestions

  • The reason→wording map in gateway.py:6990 mixes MONITOR_STOP_* constants with three raw provider-reason literals; sourcing all keys from monitoring.models constants keeps a renamed reason from silently degrading to the generic fallback.

[DESIGN-REVIEWED] f93c40c

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of f93c40c10f01327fad422a3a35c53ba2e3c8bc01 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All evidence gathered and counted. Final review:

First-Principles-Verdict: CONCERNS

Two shipped surfaces have no reader: the inner terminal_notification_delivered bit duplicates the outer delivery pair, and _monitor_context_refusal's error= has zero callers.

Not justified as shipped

  • 4 — duplicate: one writer sets the inner bit and the outer (outcome, stopped_at) pair together; the inner-bit path is unreachable.
  • 9 — undeclared: the closing-slot arm guard appears nowhere in the description and ships no reproduction.
  • 10 — zero consumers: 8 call sites, none passes error=.

What this change ships

Intent: stop pull-request babysitting from spending a model turn per poll and coasting into caps, by routing it through the structured monitor with finite budgets — an ADDITION (migration step 7/8), not a fix.

  1. Babysit skill collapses to one bounded monitor_watch recipe; pr_watch cron, stall tripwire, and host-verdict guidance deleted — justified
  2. monitor_start/monitor_update reject 0 = unlimited; runtime now defaults to 4 hours — justified
  3. Terminal monitor outcomes raise one redacted, reason-specific dashboard notice, replayed once after restart — justified
  4. That delivery is persisted twice: outer (outcome, stopped_at) pair AND inner monitor bit — duplicate of the outer pair (autonudge.py:643)
  5. Exhausted budgets persist a terminal stop before another probe or wake — justified
  6. New shipped monitoring.md user doc plus index rows — justified
  7. monitor_watch ack now says application is pending; inspection deferred to a later turn — justified
  8. Conductor and prepare-pr recipes pin explicit finite budgets; prepare-pr adds gate=False — justified
  9. Arming a loop is refused while its slot is closing (autonudge_authz.py:757) — undeclared
  10. _monitor_context_refusal gains an error SEL-code parameter — zero consumers (grepped _monitor_context_refusal(: 8 call sites, 0 pass it)

More than 10: Webex refusal-text alignment (verified against binding_key_for, which already accepts webex:), token_usage_known in inspection, structured autonudge_stop wording, prompt.md routing rewrite — all checked, justified.

Watch

  • The description says the delivery generation is "retained for compatibility", but both fields are introduced in this same PR against base feat: show bounded monitors in the dashboard #5185 — the only records the inner bit is compatible with are earlier drafts of this unmerged stack. Clears when: the inner bit, the terminal_notification_delivery_matches fallback, and the load-time inner→outer copy (autonudge.py:1050) are deleted, or a record source that writes the inner bit alone is named.
  • The description claims it "audit[s] unsupported evidence scopes separately from unsupported session bindings", yet no call supplies a distinct error= code — the audit distinction is not implemented. Clears when: the parameter is deleted or a caller passes a non-default code.
  • Item 9 has no linked defect and no test in this diff. Clears when: a base-failing reproduction or issue names the arm-time close race.
  • The title type is refactor, but FieldSpec("max_cycles", int, min_val=1) (was 0) rejects previously valid input and item 3 is a new capability. Clears when: the type reads feat.

Subtractions

  • Drop MonitorState.terminal_notification_delivered, the fallback in terminal_notification_delivery_matches (autonudge.py:643), and the repair copy (autonudge.py:1050-1056) — the outer pair alone decides every reachable case (1 writer: mark_terminal_notification_delivered, autonudge.py:2591).
  • Drop the error keyword from _monitor_context_refusal (control.py:1206) — 0 of 8 call sites pass it; restore the literal.

[FIRST-PRINCIPLES-REVIEWED] f93c40c

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed f93c40c10f01327fad422a3a35c53ba2e3c8bc01 — this comment is updated in place on each push.

Review details

I have independently verified the subtlest new machinery: the notification-persistence future is captured synchronously before any yield (push→sink→deliver sets last_notification_persist with no intervening await); the controller's pre-probe budget stop (controller.py:162) sits after the wake_in_flight short-circuit so a DISPATCHED wake is never cancelled; opaque future-version monitors record delivery on the outer NudgeLoop fields so their raw payload survives; and structured-monitor loops are created (autonudge.py:1549) with max_cycles/max_runtime_secs unset (=0), so the legacy _timer never fires a parallel "expired" alongside the controller's terminal "updated". The validation min_val 0→1 tightening and the _closing guard are intended, non-regressing changes. No groundable defect survives at the required bar.

No findings.

[OPUS-REVIEWED] f93c40c

Verdict parsed from the review's SHA-scoped output markers for commit f93c40c10f01327fad422a3a35c53ba2e3c8bc01.

False positive or not applicable? A repository writer can comment:
/ai-review override fable f93c40c10f01327fad422a3a35c53ba2e3c8bc01: <one-sentence reason>

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 23, 2026
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 23, 2026
@kyleseaman
kyleseaman force-pushed the token-monitors-babysit branch from 6ce882d to 5f93b09 Compare August 23, 2026 15:33
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Aug 23, 2026
@kyleseaman
kyleseaman force-pushed the token-monitors-babysit branch from 5f93b09 to cf12433 Compare August 23, 2026 15:47
@kyleseaman
kyleseaman force-pushed the token-monitors-babysit branch from cf12433 to 4e7bf86 Compare August 23, 2026 15:55
@kyleseaman
kyleseaman force-pushed the token-monitors-babysit branch from 4e7bf86 to 59ba569 Compare August 23, 2026 16:17
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 24, 2026
@kyleseaman
kyleseaman force-pushed the token-monitors-babysit branch from 109e3e5 to 9d9b230 Compare August 24, 2026 12:17
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Aug 24, 2026
@kyleseaman

Copy link
Copy Markdown
Collaborator Author

Addressed the current review finding in 3478ea9: structured watches are now documented and enforced for dashboard, Slack, and Discord only; Webex routes to the finite legacy loop. The stateless tool and authoritative consumer both refuse structured Webex creation before persistence. Focused verification: 38 monitor/babysit tests passed.

bolichen97
bolichen97 previously approved these changes Aug 26, 2026
bolichen97
bolichen97 previously approved these changes Aug 27, 2026

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

bolichen97
bolichen97 previously approved these changes Aug 28, 2026

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: route babysit through structured monitors

Reviewed origin/token-monitors-dashboard...origin/token-monitors-babysit (15 files, +775/−682) at 94f4c00df. Findings were verified by executing the real tool → directive → applier → controller path, plus mutation testing against a scratch copy. The full surface passes on the PR head (928 tests via -k "monitor or autonudge or babysit or directive", 459 skills/docs tests, mypy clean on both changed Python modules, scripts/docs-lint.sh all checks passed), so every finding below is invisible to CI. The working tree was never modified.

The headline is finding 1: the PR's central behaviour change is unreachable on the default path. Everything else is downstream of that.

Blocking

1. config/prompt.md:106 — the always-on system prompt still hard-routes every babysit request to legacy monitor_start and never mentions monitor_watch, and the rewritten skill cannot be trigger-delivered at all. prompt.md is the installed system prompt (agent.py:354 registers it as the prompt file:// URI) and is in context for every session. Line 106 reads verbatim **Long task or "keep an eye on it" / "babysit" / "monitor":** use monitor_start.; line 113 lists the trigger phrases; line 119 gives the exact PR-polling recipe. It is not among the diff's 15 files. Meanwhile the skill is on-demand only — and worse, unreachable by trigger: the diff adds inject_on_trigger: false but the skill has no triggers: field, and SkillsLoader.get_triggered_skills (skills.py:4033-4035) continues on an empty triggers. Verified with max_triggered=5: "babysit PR 247 until it is review-ready" triggers prepare-pr, "monitor this pull request" triggers ops-mission-control, babysit never appears. Net: a user saying "babysit this PR" gets a legacy full-turn poll loop, and the RFC exit criterion "The supported pull-request path contains no repeated polling turns" is not met by default. prompt.md also still claims monitor_start "Works from dashboard chat, Slack threads, and Discord DMs" (no Webex, contradicting the descriptor this diff changed) and still offers HEARTBEAT.md at line 128 — the anti-pattern whose evidence-backed warning this diff deleted. Relatedly, the new spec sentence at memory-skills-hooks.md:912 ("an on-demand, pointer-on-trigger recipe") describes a delivery mode the code cannot produce.

2. builtin_skills/kirocrew-dev/babysit/SKILL.md:73 — the structured→legacy transition the skill instructs in three documented cases silently DELETES the active structured monitor and its retained evidence. An agent arms monitor_watch, then follows SKILL.md:55-57 / 66-69 / 17-19 ("use the finite legacy path") and calls monitor_start. _monitor_startauthorize_and_add_nudge_add_unserialized hits existing = self._find_by_slot(slot_key) and, with the default replace_existing=True and no wake in flight, calls self.remove_sync(existing.id, persist=False, emit=False) (autonudge.py:1128-1137). Executed through the full path: watch applied -> Structured monitor ba3862b3 started, then legacy applied -> Monitor loop 51405ff4 started, then loops in memory: [('51405ff4', False)], persisted loops: [('51405ff4', False)], STRUCTURED RECORD SURVIVED? False — no terminal outcome, no stopped_reason, no dashboard event (emit=False), and the applied message never mentions what it destroyed. This directly contradicts src/kiro_crew/docs/monitoring.md, added by this same diff: "Stopping is durable: the dashboard and agent inspection keep a user_stop outcome rather than deleting the evidence." The reverse direction clobbers a legacy loop the same way. A second route in: same-turn monitor_inspect returns {"enabled":true,"monitor":null} — a success, indistinguishable from "no monitor exists" — which reads as SKILL.md:41's warning coming true and drives exactly this fallback; and because /api/autonudge/session-monitor returns the same null once a legacy loop is bound, the next turn's inspect still says null, so the loss is undiscoverable. SKILL.md:43's "The agent cannot inspect after ending that same turn" reads as the opposite of its intent and makes the same-turn call more likely.

3. monitoring/controller.py:138 — the new BUSY-branch budget stop bypasses record_monitor_dispatch_busy's _accepted_monitor_turns.pop, leaving a terminal BUDGET record with wake_in_flight=True that blocks re-arming the session with HTTP 409 for up to 2h1m. record_monitor_dispatch_busy pops the accepted-turn entry (autonudge.py:2198-2199) before applying the budget stop, forcing _retain_accepted_terminal_completion to see accepted=False and clear the claim. Routing the same transition through stop_monitor_if_budget_exhausted skips that pop, so the claim is retained and completion_evidence_deadline = now + 7260 is written. _add_monitor_locked (:1132-1136) then raises MonitorUpdateConflict('existing monitor cannot be replaced while a wake is in flight') → 409 unconditionally, with no exemption for terminal records. Reachable in production: _accept_dashboard_turn (slack/gateway.py:6017-6022) calls mark_monitor_turn_accepted and only then _settle_admission(DISPATCHED), a no-op if a BUSY settle already resolved the future — and mark_monitor_turn_accepted's guard permits wake_delivery is BUSY. Driven through the real _timer/_firing/tick path: decisions: [STOP_BUDGET], terminal record: active=False outcome=BUDGET wake_in_flight=True evidence_deadline_in=7260s, then monitor_watch again -> MonitorUpdateConflict. The superseded route in the identical state gave wake_in_flight=False and re-arm -> SUCCEEDED. stop_monitor cannot clear it; only remove() or a gateway restart does. The same record also carries next_due_ts ~2h in the future on a terminal row — a shape the loader itself flags as corrupt (_store_dirty, :633-638) and the WS broadcasts verbatim.

4. monitoring/controller.py:142 — the pre-existing BUSY-branch budget stop is now unreachable dead code, and it is the more correct of the two. stop_monitor_if_budget_exhausted resolves self._loops[monitor_id], which is the loop tick holds, and re-runs monitor_budget_reason(state, now=now) — the identical predicate on the identical object with the identical now. Proven by mutation: replacing lines 142-148 with raise AssertionError and running all 20 monitor/autonudge/babysit test files gives 661 passed, zero hits. Independently proven by spying: on the budget tick, record_monitor_dispatch_busy calls total: 1 — only the one from _dispatch_claimed; line 143 never runs. Not cosmetic: the dead branch is the one that popped _accepted_monitor_turns and called _sync_terminal_completion_timer — i.e. findings 3 and 5. The diff silently substitutes a less-correct stop while leaving the correct one in place for future readers to maintain. The minimal fix that achieves the stated goal without new code is to MOVE lines 142-148 above the if now < state.next_probe_at guard and delete the new call. Note the new placement also changes behaviour no test pins: a BUSY monitor now budget-stops on an early tick (now=121 < next_probe_at=125 -> STOP_BUDGET) where it previously returned NO_CHANGE, dropping the claimed wake's last redelivery attempt.

5. autonudge.py:1723stop_monitor_if_budget_exhausted ends with a bare _cancel_timer(loop.id) where all five sibling terminal paths use _sync_terminal_completion_timer, destroying the only timer that can expire an accepted in-flight claim. _apply_monitor_budget_stop_retain_accepted_terminal_completion (:2052-2073) deliberately keeps wake_in_flight=True and sets a finite future completion_evidence_deadline/next_due_ts precisely so a timer can later expire it. Direct call in that state: outcome=BUDGET wake_in_flight=True completion_evidence_deadline=7385.0 next_due_ts=7385.0 waits_for_terminal_completion=True timer_registered=False accepted entry leaked: fp-1. The identical state via stop_monitor gives timer_registered=True. So record_monitor_completion_evidence_unavailable can never fire, the accepted turn's tokens are never charged (agent_turns: 0 total_tokens: 0), and the missing-evidence retirement never runs. Masked in production only because the sole caller sits inside _timer, whose finally (:2571-2576) independently re-derives _waits_for_terminal_completion and re-arms — but the method is a public _Service protocol member, so any future caller (a dashboard "stop if spent" endpoint, a start() budget sweep) reintroduces it. This is the sixth copy of the deepcopy → monitor_budget_reason → _apply_monitor_budget_stop → _persist_staged_monitor_locked → timer → _emit shape (:1687, 1938, 1991, 2213) and the only one that diverges from the shared epilogue; one extracted _publish_staged_monitor_locked(loop, staged) would make the divergence impossible.

6. autonudge.py:1726 — every structured-monitor terminal outcome is user-silent: all paths emit only "updated", never "expired", so _notify_nudge_expired never fires. The gateway observer calls it only for event == "expired" (slack/gateway.py:6202), and every _emit("expired") site (:2600/2616/2640/2760) sits in the legacy fire branch that structured monitors return before reaching (_timer returns at 2577). Executed: STOP_SUCCESS(review_ready), STOP_BLOCKED(pull_request_closed), STOP_BLOCKED(provider_authentication) and STOP_BUDGET(runtime_budget) all produce events=['updated'] expired_notify=False; the legacy cycle_cap case for contrast produces ['updated','expired'] expired_notify=True. So: Slack user says "babysit this PR and tell me when it's ready"; 4h later the runtime budget is spent with the PR still red, the monitor stops, and the Slack thread receives nothing. _notify_nudge_expired's own docstring names this: "the only signals were a log line and an active=False state change that looks identical to a manual Stop … the most confusing failure mode of the babysit feature." The skill documents only the terminal-SUCCESS silence (SKILL.md:55-57) and says nothing about budget/blocked/auth outcomes. The zero-cost mechanism the fix needs already exists (NotificationBus + dashboard_state.notify, with system.cron/system.agent precedents).

7. builtin_skills/.../babysit/SKILL.md:66review_ready SUCCESS can fire — zero turns, monitor deactivated — over an unanswered advisory review comment, and that routing rule is enforced only by prose. Executed against the real GitHubPullRequestProvider with a fake gh runner: on a PR whose advisory lanes post CONCERNS in a top-level comment while their own checks exit SUCCESS, the provider issues only gh pr view --json … and the reviewThreads GraphQL query — never comments or reviews — and returns status=SUCCESS reason_code=review_ready with blocking_review: none, unresolved_review_threads: 0. End to end: monitor_watch ACCEPTED with no evidence gate, decision=STOP_SUCCESS, outcome=SUCCESS, stopped_reason=review_ready, agent_turns=0, wake_count=0. Nothing refuses a comment-dependent objective — MONITOR_WATCH_SCHEMA's objective allowlist has exactly one value, so the boundary is the model's judgement about the user's private definition of readiness. The sibling Webex rule is code-gated (structured_monitor_binding_key_for returns None for webex:), making the asymmetry a same-PR inconsistency; the existing objective enum at validation.py:1148 is the seam a code-enforced version would use. Worse, src/kiro_crew/docs/monitoring.md — the only doc a user reads, indexed by this same diff — never states the caveat: its "Unsupported targets use the costly legacy loop" section lists only "Tickets, deployments, other forges, and custom objectives" (grep for comment/advisory/thread returns one unrelated hit), while the fact is stated five times in agent-facing files.

Should fix

8. builtin_skills/.../babysit/SKILL.md:63 — the skill instructs monitor_stop({"reason": …}) but the reason is discarded end to end. session_directive_apply.py:165-166 is elif kind == "monitor_stop": result = await _monitor_stop(session_key) — the only monitor directive dispatched without its args (monitor_watch, monitor_update and autonudge_stop all receive them). control.monitor_stop faithfully encodes the reason; _monitor_stop never reads it; stop_monitor hardcodes MONITOR_STOP_USER; _audit records no payload. Executed: payload {'reason': 'Merged by the release manager; watch no longer needed.'} → applied "Structured monitor 748a929f stopped and retained for inspection." → stopped_reason: user_stop, reason text anywhere in the store: False. So an operator inspecting a retained user_stop row can never learn why the watch ended — the exact failure the deleted skill section ("autonudge_stop should carry a real sentence naming the exit condition") existed to prevent. The diff's own new test test_babysit_user_stop_uses_real_tool_and_retains_terminal_state passes a reason and asserts stopped_reason == "user_stop", baking the discard in without noticing it.

9. mcp_tools/control.py:947 — Webex was added to two model-facing strings and left out of three others, including the acknowledgement actually delivered TO a Webex session. The same diff makes the skill route every Webex babysit to monitor_start and advertises "Works from dashboard chat, Slack threads, Discord DMs, and Webex conversations" (:347), and updated autonudge_stop's refusal to "a dashboard, Slack, Discord, or Webex session" (:883). But :947 still refuses with "monitor_start only works from within a dashboard, Slack, or Discord session … For other contexts use cron_add or a HEARTBEAT.md task", :1171 says the same for monitor_update, and :990 — the acknowledgement returned to a Webex session that just armed a loop — says "only a live dashboard/Slack/Discord session can host a loop, so do NOT assume it armed". Executed with session_key='telegram:1': monitor_start and monitor_update give the three-surface message while autonudge_stop gives the four-surface one, so three strings in one file disagree about the allowlist binding_key_for (autonudge.py:214) resolves. Two further gaps: monitor_watch's refusal text contains neither "monitor_start" nor "legacy", so a Webex agent is refused without being steered to the prescribed fallback; and the skill's blanket claim covers only Webex, while binding_key_for returns None for the eight other _CHANNEL_KEY_PREFIXES namespaces (telegram, wecom, whatsapp, teams, weixin, imessage, feishu, unified) where both paths refuse — verified as a double refusal, with no third branch documented anywhere.

10. mcp_tools/control.py:173 — the new autonudge_stop descriptor promises the record is "retained for inspection", but the applied confirmation the model reads is the unchanged legacy "No further nudges will fire." The behaviour is correct — _autonudge_stop routes structured loops to authorize_and_stop_monitor (session_directive_apply.py:571-581), and executing it gives outcome=user_stop with the record retained. But that branch falls through to the shared legacy return string (:585-589): "Auto-nudge loop 623d4a71 stopped on this session (reason: …). No further nudges will fire." — no mention of retention or user_stop, in contrast to _monitor_stop's own "Structured monitor {id} stopped and retained for inspection." (:542). The skill tells the model the retained outcome is user_stop, and both monitor_start's and monitor_update's descriptions name autonudge_stop as the stop verb — so the path the tool descriptions steer models toward is the one whose confirmation describes removal. The agent reports the watch was removed, does not inspect the retained state, and contradicts itself next turn. The diff's new registry test asserts "structured"/"durable"/"retain" only in the description, never in the applied result.

11. builtin_skills/.../babysit/SKILL.md:85 — the legacy path is bounded by prose only: "All three limits must be nonzero" is unenforced for two of the three, and the always-present schema text says the opposite. monitor_start's handler is max_runtime_secs = int(args.get("max_runtime_secs") or 0) and MONITOR_START_SCHEMA sets no minimum on max_cycles/max_runtime_secs; the schema descriptions the model sees every turn say "0 = unlimited, the default" and "Pass 0 for unlimited". Executed: {'interval_secs':300,'max_cycles':0,'max_runtime_secs':0} → accepted, ack reads "…with NO cycle cap" and no wall-clock clause, armed loop max_cycles=0 max_runtime_secs=0; omitting max_runtime_secs entirely also yields 0. Only interval_secs is actually bounded. The structured path it defers to enforces the same idea in code (MonitorBudgets.__post_init__ rejects <=0). Meanwhile the diff deletes the measured justification (4 of 4 real loops ended exactly at their cap, 0 of 4 carried an agent-supplied stop reason, none set max_runtime_secs), the stall tripwire that was the legacy path's only no-progress exit, and the GitHub/GitLab/Bitbucket verdict protocol — leaving "perform the smallest safe status check" as the entire read guidance for the non-GitHub hosts legacy now exclusively serves. The replacement verification instruction is also unexecutable by the agent: /api/autonudge is in neither _STRICT_INTERNAL_API_PATHS nor _MIXED_INTERNAL_API_PATHS (verified by importing both sets), the old ~/.kiro/crew/autonudge.json file-read fallback was deleted, and the repo's purpose-built prepare-pr/scripts/monitor_armed.py is not mentioned.

12. autonudge.py:1718 — because the pre-probe stop bypasses apply_monitor_probe entirely, a terminal BUDGET record no longer records the decision that stopped it. apply_monitor_probe sets probe_count += 1, last_probe_at = now and last_decision for every decision (:1654-1656), so pre-diff a budget-stopped row always advertised last_decision='stop_budget'. All four fields are in MONITOR_PUBLIC_FIELDS and reach three consumers: the REST /api/autonudge/session-monitor response, website/src/monitoring/automation.ts:188,191 (latest.decision, usage.probes), and the agent's monitor_inspect projection. Executed: in-budget tick → probe_count=1 last_decision=record_only; next tick → decision=stop_budget outcome=budget stopped_reason=runtime_budget but probe_count=1 last_probe_at=120.0 last_decision=record_only. On a restart-shaped stop: outcome=budget probe_count=0 last_decision=Nonea budget-stopped monitor that reports it never made a decision. No test pins these on the new path. Related seam: start()'s monitor recovery (:640-666) never evaluates monitor_budget_reason either, so a monitor whose runtime elapsed while the process was down is restored active=True and rendered as "active" until its next persisted deadline.

13. docs/system-specs/features/babysit-pr-watch.md:177 — deleting the prompt-owned polling protocol leaves dangling references in five untouched files and orphans ~1,600 shipped lines with no reachable entry point. AGENTS.md § Specification management: "MUST update the spec in the SAME commit when an API, schema, or documented behavior changes" (CONTRIBUTING.md: "A PR that changes behavior and leaves its doc stale will be sent back"). babysit-pr-watch.md is not in the diff and is now false at line 5 ("offers two current monitoring modes"), line 21, line 177 ("the babysit skill's watch recipe copies the bundled pr_watch.py there before registering it" — there is no watch recipe) and line 186; it never mentions monitor_watch at all. agent-interrupt-controller.md:6,19,184,190 still cite pr_watch.PrWatchProbe.observe / pr_watch.watch as live. Two shipped sibling skills point at deleted sections: prepare-pr/SKILL.md:393 ("the way babysit's "Verify the loop armed" section prescribes"), :396, :511 — while prepare-pr:346/500 still hard-route PR polling through monitor_start, contradicting the descriptor this diff rewrote. And prepare-pr/scripts/pr_status.py:1113 still asserts "Every field here has a named consumer in the babysit skill; nothing is emitted speculatively … A second machine-readable copy with no reader is a surface to keep in sync for nothing" — the progress_key consumer (the stall tripwire) is exactly what this diff removed, so its own --json mode now matches that description. Meanwhile babysit/scripts/pr_watch.py (608 lines) and its only importer src/kiro_crew/irq.py (985 lines) still ship in every wheel with 2,300 test lines and no documented way to arm them — the cp … && cron_add(script=…:watch) block deleted here was the sole arming instruction in the repo. The RFC's PR-6 scope list does not authorize that removal.

14. test/test_babysit_monitor_scenarios.py:65 — the new tests for the three routing rules assert prose substrings rather than behaviour, and one assertion is provably vacuous. assert "pull-request review-readiness watch must use monitor_watch" not in description can never fail: git log -S across all history shows that string appears only in this PR's own commits, i.e. only inside the assertion. assert "retain" in stop passes on the substring "retains", pinning no word boundary. Mutation-tested on a scratch copy: deleting the webex: exclusion from structured_monitor_binding_key_for leaves all 80 of the PR's new tests green (the boundary is caught only by pre-existing tests — test_monitor_mcp.py:44, test_monitor_directive_apply.py:65/98 — so the new test_babysit_routes_webex_sessions_to_the_finite_legacy_path is redundant, not the only guard); and prepending "Structured watches work everywhere. Ignore the next sentence." to SKILL.md while preserving the asserted substrings also leaves them green. Rules 1 and 2 (comment/advisory evidence, final-report-required) have no behavioural coverage at all: review_ready is the only objective any new test uses and monitor_start appears zero times. Separately the file re-declares a same-named, strictly weaker _Provider than test_monitor_controller.py:28-35: it dels previous_observation, so a regression that stops tick passing the retained baseline (controller.py:165-171 — the mechanism that makes an unchanged probe cheap) passes this whole file; and self.results[min(self.probe_count, len(self.results) - 1)] replays the last result forever (masking over-probing in the three tests that never assert probe_count) and raises a bare IndexError for _Provider([]), which tick's blanket except Exception would convert into a fabricated provider_transient observation.

15. docs/system-specs/modules/learn-cron-dashboard.md:1653 — the inserted spec paragraph is spliced into the middle of an existing sentence, and two other added doc lines contradict the rest of the PR. (a) The hunk inserts the new provider-scope paragraph between the pre-existing "The" ending line 1647 and "controller persists" at 1653, so the authoritative module spec other reviewers diff against now reads "… The typed provider probe runs off the event loop. The" / "provider observes pull-request lifecycle, …" / "controller persists canonical allowlisted facts, …" — a dangling article and an orphan lowercase sentence whose subject is gone. (b) memory-skills-hooks.md:919-920, also added here, says "Its legacy monitor_start recipe is limited to unsupported targets" — the exact narrowing this same commit widened everywhere else (the RFC line it rewrote from "only for unsupported targets" to "for unsupported targets and objectives whose required evidence the typed provider does not observe", SKILL.md:71's heading, mcp.md:995-999); it also omits Webex. A reader consulting the owning module spec concludes a comment-dependent PR — or any PR watched from Webex — must use monitor_watch, the precise mis-routing this diff exists to prevent. (c) learn-cron-dashboard.md:1685 "Token enforcement uses the usage values the provider reports" overloads "the provider", which the same file defines 40 lines earlier as the typed GitHub probe; tokens come from monitoring/completion.py's AgentTurnUsage, and nothing in the GitHub provider reports tokens. SKILL.md:51 repeats the collision; only monitoring.md:31 disambiguates ("the model provider").


Execution-verified AI-assisted review (Claude Code) across 10 parallel angles plus a gap sweep, run against a local checkout at 94f4c00df; git status --porcelain was empty throughout, all scratch work lived in /tmp, and no other branch or worktree was examined. Checked and cleared, so please don't re-spend on them: no test double breaks on the _Service protocol change (every MonitorController( site passes a real AutoNudgeService); token_usage_known is correctly wired through MONITOR_PUBLIC_FIELDS, REST, WebSocket and automation.ts; the skill's monitor_watch example validates against every limit; Webex genuinely is refused at both claimed boundaries and the consumer boundary has pre-existing tests; not touching CHANGELOG.md is correct per AGENTS.md; and the persist-before-disarm ordering the PR claims for the pre-probe budget stop does hold under execution — the defects in that area are all in the lifecycle bookkeeping that runs after the write succeeds. Findings name the input that reproduces them, so please push back where one misreads intent.

@kyleseaman

Copy link
Copy Markdown
Collaborator Author

Addressed the current babysit-controller feedback and restacked this branch in e187edf. Pull-request babysitting now routes through the bounded structured monitor path without weakening the eight-PR stack. Focused controller/backend verification passes, and the composed monitor suite passes 234/234.

@kyleseaman

Copy link
Copy Markdown
Collaborator Author

Checked the current review finding against the exact PR head and diff: website/src/components/MarkdownRenderer.tsx is not changed by this PR, and the quoted shrink-0 opacity-75 class does not exist in the current repository tree. Restacked onto #5185 as 5e26230 to preserve the linear stack and trigger a current-head review.

@bolichen97

Copy link
Copy Markdown
Collaborator

Audit note — this PR is one slice of a declared branch stack, not a duplicate

A duplicate-detection sweep flagged #5184 / #5185 / #5186 / #5305 as overlapping at FULL coverage. That is a stacking artifact, and it is worth stating plainly so nobody acts on it: each branch physically contains the previous one, so the shared code is inherited and each PR's review diff overstates what it actually authored.

Proved mechanically, not by reading code:

The two PRs are also cleanly split by surface, which is why they are complementary rather than redundant. #5184 is backend-only: 26 files under src/kiro_crew/ (monitoring/controller.py, mcp_tools/control.py, validation.py, session_directive.py, dashboard/handlers/autonudge.py, slack/gateway.py, discord/gateway.py, ...) and ZERO files under website/. #5185's own delta (git diff pr/5184..pr/5185) is 55 files, +4328/-278, of which 44 are under website/ — the dashboard consumer for the six /api/monitors routes #5184 ships with no consumer (the exact point the first-principles review bot raised on #5184): new website/src/components/SessionAutomationPopover.tsx (+587), new website/src/monitoring/automation.ts (+400, exporting normalizeAutomationRecord/deriveAutomationStatus/automationForSlot), new website/src/monitoring/contract.json (+36), MonitorRadar.tsx (+25), useWebSocket.ts (+235), chatSlice.ts, i18n keys across 13 locale files, and ~1,300 lines of new frontend tests (SessionAutomationPopover.test.tsx +480, automationNormalizer.test.ts +238, monitorFixtures.ts +77).

The 11 non-website files in #5185's own delta are strictly ADDITIVE refinements in service of that frontend, not a competing design: monitoring/models.py gains monitor_frontend_contract() (which is what contract.json is generated from), a _public_github_observation() bounded projection over the public boundary, and two new MonitorState fields (last_observation_status, last_observation_reason_code) with their post_init validation and monitor_state_from_dict enum coercion; autonudge.py gains 4 lines populating those two fields; mcp_tools/control.py adds the same two keys to _compact_monitor_inspection's field tuple. Because #5185 sits downstream, it is structurally impossible for it to present an incompatible design for #5184's requirement — it edits #5184's own lines as a successor, not as an alternative.

Neither has landed: 550ea7b, fc82d95, 4c7b130 and 331c07d are all absent from origin/main; main carries monitoring/{init,completion,decision,github_pull_request,models,shadow}.py but NOT controller.py, and website/src/monitoring/ and SessionAutomationPopover.tsx do not exist on main. Ironically the first-pass record itself, the cached PR data, states in prose "Stacked descendant (PR 6). Its branch is #5184's head plus 4c7b130 + 331c07d; the shared code is inherited, which per the brief is a stacked branch, not duplicate" — the FULL/complete_coverage=true coding of that same relation is what the scan surfaced as a duplicate signal.

Proof of stacking: git merge-base origin/main pr/5184 == git merge-base origin/main pr/5186 == dc35c19, and git merge-base --is-ancestor pr/5184 pr/5186 returns YES. git log --oneline dc35c192f..pr/5186 is 7ce67a7, 04e9461, 331c07d, 4c7b130, fc82d95, 550ea7b -- the last two ARE #5184's entire branch (550ea7b "feat: expose session monitors to agents", fc82d95 "fix: preserve bounded monitor recovery"), and 4c7b130/331c07d53 are #5185's. So the merge-base-vs-main diff for #5186 (118 files, +14347/-1684) contains 100% of #5184 by construction. One REST call confirmed intent: #5186 is head token-monitors-babysit onto base token-monitors-dashboard, and its body declares "PR 7 of 8 / Base: #5185 / Next: #5305"; #5184's body declares "PR 5 of 8 / Base: #5183 / Next: #5185". GitHub therefore renders only #5186's own delta. The maintainer reviews it the same way: bolichen97's 2026-09-01 review on #5184 states it reviewed origin/token-monitors-github-probe...origin/token-monitors-tools -- the slice, not the range to main.

#5186's own delta (git diff 331c07d53..7ce67a772) is 25 files, +1158/-751, and none of it duplicates #5184: (1) babysit/SKILL.md rewritten by 744 lines to point at monitor_watch instead of prompt-owned polling -- a file #5184 never touches; (2) a new shipped user doc src/kiro_crew/docs/monitoring.md (+85) with its docs/README.md and docs/index.md rows; (3) src/kiro_crew/config/prompt.md guidance; (4) a behavioural policy change in validation.py banning unbounded loops -- MONITOR_START_SCHEMA and MONITOR_UPDATE_SCHEMA move max_cycles/max_runtime_secs from min_val=0 to min_val=1; (5) a new AutoNudgeService.stop_monitor_if_budget_exhausted plus its _Service Protocol row and the if await self._service.stop_monitor_if_budget_exhausted(loop.id, now=now): return MonitorDecision.STOP_BUDGET call site in MonitorController.tick; (6) an evidence_scope FieldSpec on MONITOR_WATCH_SCHEMA and monitor_start's demotion to "Legacy fallback for targets or objectives unsupported by monitor_watch"; (7) slack/gateway.py terminal-outcome notifications keyed on (loop.id, outcome, stopped_at) for SUCCESS/BLOCKED/BUDGET/TARGET_UNAVAILABLE; (8) a new test module test/test_babysit_monitor_scenarios.py (+476) plus four more test files #5184 does not touch.

Per-file judgment of the 14 shared paths: every one is a file #5184 creates or extends and #5186 then builds further on. src/kiro_crew/monitoring/controller.py does not exist on origin/main at all (git ls-tree of the monitoring package shows only init.py, completion.py, decision.py, github_pull_request.py, models.py, shadow.py) -- #5184 creates it, #5186 adds 9 lines to it. mcp_tools/control.py: #5184 introduces the monitor_watch schema and #5186 edits the schema #5184 authored. That is a build-on-top dependency, the opposite of two implementations of one behaviour.

Deciding question: if #5184 merged, would #5186 still carry real, wanted work? Yes -- the whole babysit-skill migration, the shipped monitoring doc, the unbounded-loop ban, the pre-probe budget stop, evidence_scope, and the Slack terminal notifications. The reverse direction is not a redundancy signal: #5186 "contains" #5184 only because it is branched off it.

The task premise that both are "open against main" is FACTUALLY WRONG for 5305. Its GitHub base ref is token-monitors-babysit (= 7ce67a7, #5186's branch head), not main. 5305 therefore cannot merge before its ancestors; there is no scenario in which it lands and strands 5184. Both bodies declare the same chain in prose: "Stack: #5180 -> #5181 -> #5182 -> #5183 -> #5184 -> #5185 -> #5186 -> #5305", with 5184 self-labelled "PR 5 of 8" / "Base: #5183" and 5305 "PR 8 of 8" / "Base: #5186". The remote branch chain matches exactly: token-monitors-github-probe (ca6fc25) -> token-monitors-tools (fc82d95 = pr/5184) -> token-monitors-dashboard (331c07d) -> token-monitors-babysit (7ce67a7) -> token-monitors-source-providers (9a6f511 = pr/5305).

On its OWN range (7ce67a7..9a6f511) 5305 is 77 files / +5,642 / -432 — matching GitHub's own 77 / +5605 / -432 for the PR. That range EXTENDS the four files 5184 created (src/kiro_crew/monitoring/controller.py, test/test_monitor_controller.py, test/test_monitor_directive_apply.py, test/test_monitor_mcp.py); it does not reimplement any of them. It also ADDS six modules 5184 has no counterpart for at all: monitoring/pull_request.py (the provider-neutral review-readiness contract), monitoring/targets.py (strict target parsers), monitoring/gitlab_merge_request.py, monitoring/azure_devops_pull_request.py, monitoring/bitbucket_pull_request.py, monitoring/provider_cli.py — plus dashboard/handlers/source_providers.py and a whole frontend surface (SessionAutomationPopover.tsx +626, monitoring/automation.ts +455, MonitorRadar.tsx) across 12 locale catalogs.

That is a capability 5184 STRUCTURALLY CANNOT DELIVER: 5184's MonitorController drives only the single GitHub probe already on main (monitoring/github_pull_request.py, landed by the stack's merged #5183 = dc35c19, which is 5184's merge base). GitLab / Azure DevOps / Bitbucket are different addressing schemes and different providers. Conversely 5184 is the substrate 5305 consumes — MonitorController, the monitor_watch/inspect/update/stop MCP tools, the owner-gated REST routes, format_monitor_wake, MonitorDispatchResult. Neither can be closed without destroying real, wanted work: closing 5184 deletes the controller 5305 imports; closing 5305 deletes four provider probes, the neutral readiness contract, and the entire multi-provider dashboard surface.

The maintainer agrees in practice. bolichen97 reviewed each slice on its own per-slice range — 5184 as origin/token-monitors-github-probe...origin/token-monitors-tools (59 files, +8285/-613) and 5305 as origin/token-monitors-babysit...origin/token-monitors-source-providers (73 files, +4763/-384). Nobody has ever suggested either is redundant; both carry live execution-verified review findings to fix, which is a request to iterate, not to close.

Consequence for review

Land the stack in order. Reviewing a later slice before its base lands means reviewing the base's code again, and the FULL-overlap signal a keyword or file-overlap sweep produces here means nothing.

Separately, #5184 vs #7634 (the zero-token-probe monitor gate) was adjudicated as independent — same monitor area, different behaviour — so neither blocks the other.


From a repository-wide duplicate/overlap audit of every pull request open against main, re-run against the current tree (origin/main 680baf9448dc). This PR is not being closed — the note is informational. If the reading is wrong, please correct the reasoning rather than just the conclusion.

@bolichen97

Copy link
Copy Markdown
Collaborator

Review — head 7ce67a772c75f5903b0f9c997b77a19121c0b3d0 (PR 7 of 8)

Re-checked against the 2026-09-01 review. Verdict: mergeable in stack order. CI 60/60 green; 1817 tests pass locally across the monitor/autonudge/babysit/provider suites at the stack top.

Prior blocking findings — verified fixed

  • src/kiro_crew/config/prompt.md:106-114 routes babysit → babysit skill → monitor_watch, with monitor_start limited to unsupported evidence and required to carry positive bounds; the skill now has triggers: so it is trigger-deliverable.
  • The pre-probe budget stop uses _sync_terminal_completion_timer (not a bare _cancel_timer), so an accepted in-flight claim can still expire.
  • Structured terminal outcomes reach the user: slack/gateway.py:6235 notifies once per (id, outcome, stopped_at).
  • monitor_stop's reason is persisted (user_stop_reason, bounded by MAX_MONITOR_STOP_REASON_CHARS); Webex is named consistently across the refusal/ack strings.

Leftovers (should-fix, not blocking)

  1. docs/system-specs/features/babysit-pr-watch.md is untouched and now false ("two current monitoring modes", pr_watch.py:watch as a live recipe); src/kiro_crew/builtin_skills/kirocrew-dev/prepare-pr/SKILL.md:393 still points at a babysit "Verify the loop armed" section that no longer exists.
  2. src/kiro_crew/mcp_tools/control.py:368monitor_start opens with "Legacy fallback for targets or objectives unsupported by monitor_watch" and still says "starting a new one replaces the old". The bundled goal-conductor and pipeline-conductor skills patrol their own session with monitor_start — the primary, intended use of the loop, not a fallback — and re-arming is now create-only (refused, not replaced). Suggest one sentence in the descriptor naming self-session patrol as a first-class use, and "re-arm via monitor_update" in both conductor skills. Pre-existing but adjacent: neither skill sets max_cycles/max_runtime_secs, so a long-horizon patrol ends at the 24-cycle / 4h defaults silently.
  3. src/kiro_crew/session_summary.py:40 — add [monitor wake] to _INJECTED_PREFIXES; on Slack/Discord the wake is role user and is counted as user intent by the summary path.

Cross-cutting session-mode and memory notes (crew-mode admission, consolidation of wake turns, memory_mode) are in the #5184 review comment.

Execution-verified against a local checkout at 7ce67a77; nothing modified.

@kyleseaman

Copy link
Copy Markdown
Collaborator Author

/ai-review override gpt 7bdedcb: The reported skills.py digest line is absent from this PR diff; at this exact head line 645 is an app-manifest try block, so the finding reviewed code outside the current change.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Human judgment recorded

@kyleseaman marked the gpt AI finding as false positive, not applicable, or explicitly accepted for 7bdedcbde1e0a3a80f70459b54157413b300c9c6.

The reported skills.py digest line is absent from this PR diff; at this exact head line 645 is an app-manifest try block, so the finding reviewed code outside the current change.

This decision applies only to this commit. A new push requires a new judgment.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ⚠️ review incomplete

GPT 5.6 did not produce a complete verdict for 33893785d22d207da6a4300eec15337aa6a66e11; inspect the workflow logs and re-run it.

This comment is updated in place on each push.

See the GPT 5.6 Review job logs; this commit has no completed GPT verdict.

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 33893785d22d207da6a4300eec15337aa6a66e11: <one-sentence reason>

@kyleseaman

Copy link
Copy Markdown
Collaborator Author

Addressed both current GPT blocking findings in 95fe341:

  • subscribe the terminal observer before AutoNudge startup, closing the startup transition gap;
  • persist delivery acknowledgement for the exact terminal generation outside the versioned monitor payload, preserving opaque future-version payloads and restart deduplication.

Both regressions failed on the prior head and now pass. Focused verification: 348 AutoNudge/persistence/Slack gateway tests passed; 410 broader Slack/babysit tests passed; Black, subprocess encoding, flake8, Linux mypy, docs lint, brand, and diff checks passed. #5305 was restacked and pushed bottom-up with explicit leases. No CI job was manually rerun.

@kyleseaman

Copy link
Copy Markdown
Collaborator Author

PR Hygiene correctly caught the temporary third commit. I folded the new fix into the existing terminal-notification commit and force-pushed with the recorded lease. Current #5186 head is 91ce5df; it remains exactly two commits above #5185 and its tree is byte-for-byte identical to the verified 95fe341 tree. #5305 is restacked at eb09468, also exactly two commits above #5186.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

readiness: passed Eligible automated validation passed for the current revision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants