An agent that hits a wall of denied tool calls is often probing a guardrail. A single denial is noise; a burst is a signal.
Task
In apps/orchestrator/core/audit/detection/rules.py, add a pure function rule_repeated_denials(events) and append it to REGISTRY. Fire when a session has >= 4 tool_called events with action.outcome == "denied". Aggregate into one Detection (severity high) listing all offending event_ids. Reuse the existing accessors (_action_type, _outcome, _event_id, ...).
Acceptance criteria
Hint: copy the shape of rule_autonomous_unapproved_write — it already aggregates offending events into one detection.
An agent that hits a wall of denied tool calls is often probing a guardrail. A single denial is noise; a burst is a signal.
Task
In
apps/orchestrator/core/audit/detection/rules.py, add a pure functionrule_repeated_denials(events)and append it toREGISTRY. Fire when a session has >= 4tool_calledevents withaction.outcome == "denied". Aggregate into oneDetection(severityhigh) listing all offendingevent_ids. Reuse the existing accessors (_action_type,_outcome,_event_id, ...).Acceptance criteria
apps/orchestrator/tests/test_detection_engine.py(one fires, one near-miss), using the_event(...)helper.python -m ruff check core api testsandpython -m pytest -qpass.Hint: copy the shape of
rule_autonomous_unapproved_write— it already aggregates offending events into one detection.