Read tools lists built by an expression in diff --application (#909) - #939
Merged
Merged
Conversation
# Conflicts: # CHANGELOG.md
An agent whose tools=, handoffs= or sub_agents= is built by a spread, concatenation, conditional, `or`, filter or another module's list is read member by member through one resolver both readers share, instead of being reported as a dynamic expression. A part that cannot be read is named where it is, on its agent, which stays incomplete; a member held only under a condition carries bound_when (application comparison schema 0.3). Subsumes #584 and retires its recovery reason. Co-Authored-By: Claude Opus 5.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.
Why
On the 49-PR development corpus (#908), the most common reason
diff --applicationproduced no row was "uses a dynamic tools expression". An agent whosetools=was[*FINANCE_TOOLS, *([handoff] if wanted else [])],base + (extra or []), a filter, or another module's list had none of its bindings compared, even when every member was a plain tool the reader already understood. A reviewer gotpartialand nothing to review.What changes
One resolver both readers share (
src/agents_shipgate/inputs/list_expressions.py). The OpenAI Agents SDK reader (tools=,handoffs=) and the Google ADK reader (tools=,sub_agents=) read these expressions member by member:*spreads;a + b;a if c else banda or b;filter(...);list(...),tuple(...)andsorted(...);Nothing is imported or run.
self.tools, a name changed in place, a name bound twice. The agent stays incomplete, so the answer is nevercompared, and the members that were read are still compared. The Google ADK dynamic-tools limit now names its file, line and agent instead of covering the whole file.bound_whenon its row's side: one entry per way in, each the condition's whole source text. A change to the condition alone is achangedrow whosewhynames both conditions and says the direction is not established. It isnot_establishedwhen an unread part of the list could hold the tool another way.application_comparison_schema_version0.2 → 0.3. The text output printsbound only when ….handoffs=list no longer reads as one handoff named after the list;sdk_literal_tool_list_concatenation_unsupportedis retired because that form is now read.scanhighextraction confidence is a proven-surface claim, and a list read through a name is checked for changes only in its own module, the modules its import passes through and the packages above it. So an ADKtools=orsub_agents=list that came through a name, or from another module, keeps the module atmedium, and noscandecision moves. A literal spread into a literal ([a] + [b]) stays proven.Measured
Development corpus (49 PRs, Q2 measurement: commit the acceptance corpus, freeze a holdout, and report Q1/Q2 on every advisory release #908's runner): statuses are unchanged (1
compared, 42partial, 6not_established).added8 → 9,changed60 → 65,not_established230 → 232.Finance_Agent's rows.partial, and each changed row names where reading stopped.handoffstowant_handoff_tool = bool(handoffs) or include_handoff_tool. No reader could show that before.No new false row on the 134-PR corpus. Its pins were lost to the
/tmpcleanup, so it was re-pinned at today's PR heads (131 of 134; 3 PRs are gone). Every status is identical (91partial, 39not_established, 1compared). Two PRs gain rows and none loses any:+ skill_tools;tools=[*tool_set].I checked all 16 rows against source; each is correct.
Speed: within about 4% of
mainon 7,500-line modules, and 0.33 s against 0.21 s for 40 agent files importing one module.Review
Five reviewers, each verifying with fixtures:
diff --application;They found about 40 confirmed problems, 15 after merging overlaps; all are fixed and each has a regression test in
tests/test_list_expressions.py. The ones that mattered:mod.LIST.appendthrough the module's own spelling;__init__that re-exports the list and changes it;helper.tools.append), in its own file or another;*spread to a helper;replaceor a non-agent.clone;Agentclass.sub_agents=lists read from expressions had letscanpass;withblock was treated as conditional, which regressed a list the old reader read.Left as a follow-up: ADK's #865 reader for builder-local
tools.append(...)uses its own rules beside this resolver. Folding it in changes documented behavior, so it is proposed separately.Surface discipline
bound_when) and its schema version, both registered indocs/distribution-surfaces.md, with the parity note intests/test_distribution_surface_parity.py.Closes #909. Closes #584.
🤖 Generated with Claude Code