fix(detectors): key repeats on the call, not the tool name; declare act-only surfaces - #8
Merged
Merged
Conversation
…ilent-work surfaces REPEATED_TOOL counted calls per tool NAME, so one tool called over N distinct ids read as thrashing when it was fan-out. It now groups on arguments, degrading to name-only where an adapter maps none, plus a second group for N calls that got the same nothing back — a thrash varies an id per attempt, so arguments alone would miss the case the detector exists for. Identical success bodies stay clear: those are a bulk write. EMPTY_REPLY had no way to say a surface acts without answering. quiet_kinds cannot express it, since it describes silence BEFORE work and asserts silence after work is suspicious. silent_work_kinds declares the surface where acting and answering are separate decisions; work-then-silence reports ACTED_SILENTLY at INFO there and stays EMPTY_REPLY everywhere else. Silence without work keeps reporting GATE_FILTERED. coverage() gains an ACTED_SILENTLY row and reports REPEATED_TOOL as MISLEADING when no tool call carries arguments, which is the wiring where fan-out reads as a thrash. Closes #7 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Running the tests through uv writes one, and a lockfile for a package with no dependencies pins nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…onversational too silent_work_kinds sat next to quiet_kinds using a synonym for a different rule, which is unreadable. reply_optional_kinds says what the field asserts and pairs with conversational_kinds. Rejecting a kind declared both was wrong. conversational_kinds means a human is on the surface, not that every silence there is a fault, and the two are routinely true at once: a shared channel holds people who sometimes get an answer AND lets the agent act without broadcasting. The rejection also forced a consumer to strip the kind from conversational_kinds, and where it was the only entry that silently demoted every EMPTY_REPLY in the report from FAULT to INFO. The declarations now govern different turns on one surface: reply_optional_kinds the ones that did work, conversational_kinds the rest. ACTED_SILENTLY is checked before the reply-expectation gate, so declaring the kind is the whole statement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…inds reply_optional_kinds promised more than it delivered. It reads as "no reply is ever owed here", but the declaration only covers turns that DID work: a turn on the same kind that did nothing and said nothing is still an EMPTY_REPLY, deliberately, since an agent that no-ops in front of people is the bug the detector exists for. act_only_kinds names the same scope ACTED_SILENTLY observes, and carries no synonym collision with quiet_kinds. Comments say which turns the field covers rather than implying a blanket permission. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes both detectors in #7. Neither was tunable from
_CONFIG: both were the detector reasoning from a signal too coarse to tell flailing from correct fan-out.1.
REPEATED_TOOLgroups on the call, not the tool nameTwo keys, because a name-only
Countercannot separate a thrash from fan-out over N ids the input supplied:arguments=Nonecollapses to one key per name, so an adapter that maps no input keeps exactly the old behaviour rather than silently losing the detector.{},[],"",None) shares one key.The results arm is restricted to calls that errored, declined, or came back empty. Keying on results generally would have re-flagged a bulk write returning identical
{"ok": true}per row, which is the fan-out this change exists to stop flagging; there is a test pinning that. The cost of the arm, stated in the docstring, is N searches in one turn that legitimately found nothing.detailnow carriesbasis({"tool": ["arguments"]}/["results"]) so a reader knows which key fired.2.
act_only_kindsandACTED_SILENTLYA third declaration for surfaces where a turn that acts and says nothing is a designed outcome, because "is there work here" and "does anyone need an answer" are separate decisions there. Those turns report
ACTED_SILENTLYat INFO — countable rather than merely un-flagged — and work-then-silence staysEMPTY_REPLYeverywhere else, so the real bug survives.The field covers acting quietly, not being idle, which is what keeps it orthogonal to
conversational_kinds. A kind is often both: a shared channel holds people who sometimes get an answer and also lets the agent file work without broadcasting. The two govern different turns on the same surface:EMPTY_REPLY(fault)EMPTY_REPLY(fault)ACTED_SILENTLY(info)ACTED_SILENTLY(info)EMPTY_REPLY(fault)ACTED_SILENTLY(info)GATE_FILTERED(info)Row three is signal the old shape threw away: on a channel with people in it, a turn that did nothing and said nothing is still worth flagging. Unset, every existing path is byte-identical, and making the declaration never adds a fault.
Coverage
New
ACTED_SILENTLYrow, andREPEATED_TOOLnow reports MISLEADING when no tool call carries arguments — the wiring where name-only keying makes fan-out read as thrashing.Tests
All 15 pinning cases from the issue, plus guards for: identical-success fan-out, key-order-insensitivity, adapter degradation, both declarations on one kind, and declaring an act-only kind never producing a fault.
113 passed, ruff clean.🤖 Generated with Claude Code