fix(babysit): name the PR by URL in the guidance, since that is what gates the loop - #8326
Conversation
…ates it The gating example that every agent reads showed the subject as a bare `PR #123`, and inference deliberately refuses that form. A loop armed from the example therefore stayed on the plain timer and spent a turn every interval, so the saving read as zero while the mechanism worked exactly as designed. Measured on a live gateway before this change: of six loops that asked to be gated, five had written a bare number and only the one that wrote a URL was gated, at ten quiet ticks with zero gate fallbacks. Fixes the two places that draft the message -- the monitor_start guidance in config/prompt.md and the babysit skill's rule plus worked example -- and adds a ratchet that asserts the shipped text against the real inference function, so a reword back into a non-gating form fails instead of silently costing turns. The placeholder style matters too: https://github.com/<owner>/<repo>/pull/N does not infer, so the samples use bracket-free placeholders.
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS A measured adoption failure fixed at its true cause — the copied example — with a ratchet asserting the shipped text against the real predicate, not a spelling. The alternative — loosening [DESIGN-REVIEWED] a7dc0a1 |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of All evidence is gathered; here is the review. First-Principles-Verdict: CONCERNS The fix is aimed at the true cause — examples outrank prose — but a third copyable example still teaches the bare form, uncovered by the new ratchet. What this change shipsIntent: make babysit loops actually observation-gated by fixing the examples agents copy when arming
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] a7dc0a1 |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
|
Everything on this PR that is mine is green: all five review lanes (GPT 5.6, Opus The one remaining red,
I am deliberately NOT adding a Nothing else is outstanding. Not merging. For the record on PR Hygiene, since it took me two tries: the gate wants a section |
iamwhatever
left a comment
There was a problem hiding this comment.
Tier 1 auto-approve: fix (3 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: babysit guidance named the watched PR by number, but the gated monitor keys on the full PR URL, so the guidance is corrected to name the URL -- SKILL.md + prompt.md wording plus a test pinning the gate.
Problem / Motivation
A monitor loop is observation-gated only when its instruction names a pull request
by explicit public URL. Inference deliberately refuses a bare
PR #123and theowner/name#123shorthand, because a loop's own subject and a blocker it merelymentions cannot be told apart, and gating on the wrong one retires a loop whose
real work is unfinished.
Both places that teach an agent to draft that instruction showed the subject as a
bare number:
src/kiro_crew/config/prompt.md-- themonitor_startguidance injected intoevery agent's prompt:
Check PR #123 for new CI results ...src/kiro_crew/builtin_skills/kirocrew-dev/babysit/SKILL.md-- the decisiontable said only "name the pull request in the instruction", and the worked
example armed
message="Check PR #247. ..."The tool description already said to use a full URL, but the tool description is
read when calling the tool; the examples are what get copied when composing the
message. So agents wrote the form that refuses gating.
Why it matters
Measured on a live gateway with the feature merged: of six loops that had asked
to be gated, five had written a bare number and were running on the plain timer,
spending a turn every interval. Only the one loop whose instruction carried a URL
was actually gated -- ten quiet ticks, zero wakes charged to the fallback path,
zero gate fallbacks.
The mechanism worked exactly as designed while the saving read as one sixth of
what was available. That is the same failure mode this line of work exists to
prevent: a correct feature whose adoption is quietly zero. Here the counters
surfaced it, which is the only reason it was found.
What changed
Guidance only -- no behaviour change, no production code touched.
config/prompt.md: the example names the subject by URL, and one clause sayswhy -- the URL is what makes the loop gated, a bare
PR #123leaves it on theplain timer, and the user will usually say "babysit PR revert: remove project agents feature (backend + frontend) #123" so you write the
URL.
babysit/SKILL.md: the decision-table rule now names the required forminstead of saying "name the pull request", and the worked example arms a URL
while keeping the user's realistic bare-number request above it -- the point
being the translation from what the user says to what you arm.
test/test_babysit_guidance_gates.py: a ratchet that extracts the URLs fromthe shipped text and asserts
probes.targets.inferaccepts them, so a rewordback into a non-gating form fails instead of silently costing turns. A third
test guards the guard: it pins that a bare reference is still refused, so the
other two cannot start passing vacuously if inference ever loosened.
One detail worth recording because it nearly shipped: the placeholder style
matters.
https://github.com/<owner>/<repo>/pull/123does NOT infer -- the anglebrackets are not valid in an owner or repo segment -- so an example written that
way is as ungated as the bare number it replaced. The samples use bracket-free
placeholders (
owner/repo), which infer, and the ratchet pins that too.Tests
test/test_babysit_guidance_gates.py-- 3 new assertions, eachmutation-verified: reverting
prompt.md's example toPR #123reddens thefirst, reverting the skill example to
PR #247reddens the second, and bothreturn green when restored.
test_babysit_guidance_gates.py,test_acp_prompt_blocks.py,test_builtin_skill_packaging.py,test_builtin_skill_scope.py,test_builtin_skill_sync_safety.py,test_babysit_pr_watch.pyandtest_probe_targets.py.flake8,black --checkandisort --check-onlyclean on the new file.Manual verification
Ran
probes.targets.inferagainst the real strings rather than reasoning aboutthe regex: the old prompt example is refused, the new one selects
github.com, the babysit example still gates with a bare247appearing laterin the same message (same number, so no ambiguity refusal), a URL alongside a
DIFFERENT bare
PR #99is refused as designed, and one of the five live loopinstructions is refused verbatim.
Related Issues
Refs #7634, which shipped the gate this guidance failed to demonstrate.
Pattern harvest
Rule candidate: when a feature's behaviour depends on how an instruction is
WORDED, the guidance that teaches the wording must be asserted against the real
predicate, not reviewed by eye -- and the EXAMPLE is what gets copied, so fixing
the prose while leaving a wrong example fixes nothing.
An example is an interface, and it outranks the prose beside it. The
monitor_starttool description already required a full URL. It lost anyway,because the agent reads the tool description when CALLING the tool and copies the
EXAMPLE when composing the argument. Five of six live loops copied the example.
When a correct rule and a wrong example disagree, assume the example wins and fix
the example.
When behaviour depends on how a string is worded, test the shipped words
against the real predicate. Reviewing guidance by eye is what let this survive:
Check PR #123looks like it names a pull request, and a human reader cannot seethat inference refuses it. The new test does not assert a spelling; it runs
probes.targets.inferover the text that ships, so the guard tracks the predicateeven if the predicate changes. The third test guards the guard by pinning that a
bare reference is still refused -- without it, loosening inference would make the
other two pass vacuously.
A placeholder can fail the predicate it is illustrating.
https://github.com/<owner>/<repo>/pull/123reads as a perfectly good genericURL and does not infer, because angle brackets are not valid in an owner or repo
segment. A placeholder that cannot survive being copied is the same defect as the
bare number, one level more subtle. Bracket-free placeholders (
owner/repo)both read as generic and satisfy the predicate.
A feature can be correct, adopted, and still saving nothing. The gate worked
exactly as designed from the day it merged; what was missing was that anything
reached it. The counters shipped in #7634 are the only reason the one-in-six ratio
was visible at all -- without a number that distinguishes "gated and quiet" from
"never gated", this would have read as a feature that simply did not help.