pub fn is_observation_batch(calls: &[ToolCall], patterns: &[String]) -> boolExpand description
Return true if every call in calls is an observation-only tool.
A tool call is classified as observation-only when its lowercased name
contains at least one pattern in patterns as a substring. Matching is
case-insensitive (both sides are lowercased before comparison).
A substring captures more than it names, which matters for a hand-written
list, since a user’s observation_tools replaces the shipped one whole: a
pattern read also matches thread_create and spreadsheet_update, and
tools that change state get the read-only allowance. The shipped
coding-agent patterns are long enough that everything they capture is
read-only (see AgentConfig::observation_tools); the browser ones are
not, and snapshot also matches delete_snapshot. Whole-segment
matching, the rule is_costly_batch uses, would stop list_dir covering
list_directory and click covering get_clickable_elements, and
over-capturing here is the cheaper error.
An empty patterns list means no tools are ever classified as
observation-only, so the function always returns false.
An empty calls slice returns true (vacuous truth), but the caller
(crate::domain::agent::LoopDetector::check) is never invoked with an empty batch — both the
agent loop and the proxy’s history scan skip loop detection when there are
no tool calls. That is now load bearing rather than merely tidy: an empty
batch would hash to a signature of its own and break the consecutive run.