Skip to content

fix(autonudge): treat a merged subject as terminal when a paused loop's cap is raised - #8201

Merged
iamwhatever merged 1 commit into
kirodotdev:mainfrom
LuisBrel:fix/babysit-merged-pr-report-8060
Sep 3, 2026
Merged

fix(autonudge): treat a merged subject as terminal when a paused loop's cap is raised#8201
iamwhatever merged 1 commit into
kirodotdev:mainfrom
LuisBrel:fix/babysit-merged-pr-report-8060

Conversation

@LuisBrel

@LuisBrel LuisBrel commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

Merged #8122 fixed the user-facing expiry notice: it now derives terminal from stopped_reason == monitor_terminal or an outstanding monitor.terminal_pending. It deliberately left _timer's ordering, max_cycles semantics and stopped_reason alone.

monitor_update's paused-loop branch in src/kiro_crew/dashboard/session_directive_apply.py still read stopped_reason on its own. Two consequences on a loop whose subject has already reached a terminal state:

  • the agent is told the loop "hit its cycle cap", and a settled terminal loop is reported as "paused manually" — the opposite of what the expiry notice says about the same loop;
  • if the same patch also raises max_cycles, the branch injects active=True and silently re-arms a watch on an already-merged pull request.

Why it matters

The second one costs real work: a fresh loop wakes repeatedly to watch something that is finished, and nothing in the reply says the subject already merged. It is also a disagreement between two surfaces reading the same state — the notice says merged, the directive says out of cycles.

What changed

The paused-loop branch now reads the same two fields the expiry notice reads, with the same precedence, so the agent-facing and user-facing endings cannot diverge:

  • a terminal subject outranks every bound, and an owed terminal turn counts as one
  • a settled outcome outranks a stale owed turn
  • revival is confined to the genuinely bound cases — a spent cap or budget with no terminal news

The reply now names the actual ending (merged, or closed without merging) instead of a bound that was not what stopped it.

Scope: _timer delivery, max_cycles semantics and stopped_reason itself are untouched, which is why this is Refs rather than Closes — the contested delivery half of the issue remains open.

Tests

test/test_autonudge_stop_auth.py gains five tests (it already owns the paused-loop revival and denial cases):

  • an owed terminal turn is not reported as a spent cap
  • an owed blocked turn is not reported as a merge
  • a settled terminal loop is not reported as a manual pause
  • a settled outcome outranks a stale owed turn
  • control: a spent cap with no terminal news still revives — this one passes pre-fix, and guards the cap-raise revival affordance fix(babysit): report an owed terminal turn as terminal, not as a spent cap #8122 relies on

Pre-fix, four fail: the loop is revived with active: True on a merged subject, and a settled terminal loop reports "paused manually". After: 50 passed. Neighbouring directive suites: 100 passed. black, isort, flake8 clean; mypy 1.14.1 reports no issues in 1279 source files.

Manual verification

None beyond the tests — the behaviour is a directive reply string and a patch payload, both asserted directly.

Screenshots / video

Why no screenshot: a backend directive-reply change; no UI surface is touched.

Related Issues

Refs #8060

Not Closes: this fixes the monitor_update half. The _timer delivery-ordering question the issue also raises is untouched and should keep the issue open.

Pattern harvest

Rule candidate: review-prompt

Pattern: two surfaces deriving the same user-visible verdict from the same state, where only one is updated. The fix that lands in the notice leaves the directive reading a single field, and the two then disagree about the same loop.

…ent bound

A channel-bound monitor loop does not settle on observation: the probe records
the owed final turn in `monitor.terminal_pending` and leaves the loop active
with no `outcome`. If that turn is refused because the channel is busy -- the
ordinary case for a live thread -- and the retry finds a bound spent, the loop
deactivates tagged with that bound before the settlement that would promote the
debt ever runs.

The expiry notice already consults the debt for its wording. The `monitor_update`
paused-loop branch did not: it read `stopped_reason` alone, so the same loop was
reported to the agent as "it hit its cycle cap", and a settled terminal loop as
"it was paused manually". A patch that also raised the bound then satisfied the
revival condition and injected `active=True`, re-arming a watch on a subject
that had already merged -- the wasted fresh loop this branch exists to prevent.

That branch now derives the terminal reading the same way the notice does --
`stopped_reason == monitor_terminal` or an outstanding `terminal_pending`, with
the merged-vs-closed-unmerged distinction taken from the settled `outcome`
falling back to the debt, which speaks the same `success`/`blocked` vocabulary
-- and a terminal subject is refused with that news instead of revived. The
precedence is expressed once, as a term in the revival decision, rather than as
a guard per branch: the notice next door lost this same precedence three times
because each newly added bound was evaluated ahead of it.

Deliberately unchanged: no stop check moves, `max_cycles` semantics and
`stopped_reason` stay untouched so the spent bound remains observable, the
revival affordance for a genuine cap or budget is preserved (covered by a
control test), and no field or schema is added. Whether an owed terminal turn
should instead outrank the cap and be DELIVERED is the separate contested half
of the issue and remains an open maintainer decision.

Refs kirodotdev#8060
Refs kirodotdev#8122
@LuisBrel
LuisBrel requested a review from a team as a code owner September 3, 2026 16:36
@LuisBrel
LuisBrel requested a review from dwu96 September 3, 2026 16:36
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — 🟡 CONCERNS

Design-level review of 859bb937a0254d14125a30124387dc4fc4de682d via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: CONCERNS

Sound fix, but it unifies two surfaces by hand-copying the derivation — re-seeding the exact "only one copy gets updated" failure it fixes.

Watch

The terminal-precedence derivation (terminal_pending debt → stopped_reason → settled-outcome-outranks-debt) now lives inline twice: src/kiro_crew/slack/gateway.py:6001 and this hunk ("Expressed ONCE, as a term in the revival decision itself" — once per file, twice in the codebase). The code's own comment says this precedence was already lost three times; the PR's pattern-harvest names "two surfaces deriving the same verdict from the same state, where only one is updated" as the bug class. A third consumer, or a change to the success/blocked settlement vocabulary, updates one copy and the surfaces diverge again — the same bug, one layer up. Convergence today is enforced only by per-surface wording tests.

Suggestions

  • Hoist the derivation into one shared helper next to the fields it reads (e.g. terminal_ending(loop) -> "" | "success" | "blocked" in autonudge.py or monitoring/models.py), and have both the expiry notice and this branch call it — that is the mechanism that actually makes the two endings "cannot diverge", which the PR currently claims by parallel construction.

[DESIGN-REVIEWED] 859bb93

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5, fork) — 🟡 CONCERNS

Premise-level review of 859bb937a0254d14125a30124387dc4fc4de682d via the fork AI-review pipeline — 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 the evidence is in. The fix is real and its zero option costs a re-armed watch on a merged PR — but the new code is a line-for-line second spelling of the derivation slack/gateway.py:6001-6059 already carries, which is exactly the divergence pattern the PR's own "Pattern harvest" section names.

First-Principles-Verdict: CONCERNS

The fix earns its place, but it ships as a second inline copy of the exact terminal/decided derivation the expiry notice already carries — the divergence the PR itself warns about.

What this change ships

Intent: stop a cap/budget raise from re-arming a monitor loop whose subject already finished, and word the refusal with the real ending. A FIX.

  1. A cap or budget raise no longer revives a loop with terminal news — justified (named waste: a fresh loop watching a merged PR).
  2. The refusal now says "merged" or "closed without merging" instead of "cycle cap" / "paused manually" — justified (agrees with the expiry notice about the same loop).
  3. A second inline spelling of the owed/terminal/decided derivation — duplicate of slack/gateway.py:6001-6059, symptom-level.
  4. Five tests pinning the precedence, including a pre-fix control for cap revival — justified.

Watch

Grepped terminal_pending|MONITOR_TERMINAL_REASON under src/: the five-line precedence (owedterminalsettleddecided) now exists at exactly 2 sites, slack/gateway.py:6001-6059 and this hunk, each guarded only by a comment. The description's own harvest names the root cause — "two surfaces deriving the same user-visible verdict from the same state, where only one is updated" — and this fix patches the second reader instead of removing the second reading; the next reader loses it again.

Subtractions

  • Delete one of the two spellings: both surfaces should read a single shared derivation next to MONITOR_TERMINAL_REASON in autonudge.py, shrinking two copies plus two long justifying comment blocks to one.

[FIRST-PRINCIPLES-REVIEWED] 859bb93

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed 859bb937a0254d14125a30124387dc4fc4de682d via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 859bb93

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

Reviewed 859bb937a0254d14125a30124387dc4fc4de682d via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 859bb93

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 3, 2026
@iamwhatever
iamwhatever enabled auto-merge (squash) September 3, 2026 20:07

@iamwhatever iamwhatever 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.

Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: monitor_update no longer revives a paused loop whose subject is already terminal -- terminality (a settled monitor.outcome, or the owed terminal_pending turn a channel loop records before settlement) is now a term in the revival decision itself, so raising a cap or budget cannot re-arm a watch on a subject that already merged; the second file is its test. CodeQL is not applicable on this fork PR (default-setup emits no check-run); SAST coverage is Semgrep only, latest run success with 0 annotations.

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

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants