Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2180784
feat: resolve repository-local imported tools for ADK and SDK readers…
pengfei-threemoonslab Sep 25, 2026
3f83b94
fix(#864): import resolution never produces a false or complete answe…
pengfei-threemoonslab Sep 26, 2026
341d9c5
fix(#864): read a reference where it is used (review round 2)
pengfei-threemoonslab Sep 26, 2026
21ad927
fix(#864): read a list and a flat name where they are bound (review r…
pengfei-threemoonslab Sep 26, 2026
b45d3ed
fix(#864): a guess is scoped to its tool, and a list's second handle …
pengfei-threemoonslab Sep 26, 2026
60a0a4d
fix(#864): scope a guess to its candidates; read list arguments and p…
pengfei-threemoonslab Sep 26, 2026
8ad8d0c
fix(#864): only reads keep a list literal; follow a guess's candidate…
pengfei-threemoonslab Sep 26, 2026
1790e5e
fix(#864): spreads and tests are reads; a package's routine imports n…
pengfei-threemoonslab Sep 26, 2026
db223a5
fix(#864): code that runs first is checked, and unread code keeps a b…
pengfei-threemoonslab Sep 26, 2026
6282afc
fix(#864): every spelling of unread code keeps the caveat; a lazy pac…
pengfei-threemoonslab Sep 26, 2026
a54811b
fix(#864): the repository says what is its own code; harden the lazy-…
pengfei-threemoonslab Sep 26, 2026
dc21750
fix(#864): every directory above the scope is an import root; unread …
pengfei-threemoonslab Sep 26, 2026
a017a6e
fix(#864): a package is not an import root; a sys.modules store is re…
pengfei-threemoonslab Sep 26, 2026
1bc307b
fix(#864): packages above the scope are read; sys.modules and globals…
pengfei-threemoonslab Sep 26, 2026
48e1817
Read what the packages above the scope import, and every spelling of …
pengfei-threemoonslab Sep 27, 2026
40e8463
A local named vars or globals is a variable, not the builtin namespac…
pengfei-threemoonslab Sep 27, 2026
6533f20
Only a two-part patch on another module is exempt above the scope; re…
pengfei-threemoonslab Sep 27, 2026
148ba62
Read a frame's namespace by allow-list; batch a directory only while …
pengfei-threemoonslab Sep 27, 2026
8ad2f80
Bind the located definition, prove readers by their binding, check th…
pengfei-threemoonslab Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
- **Submodules.** A gitlink under the scope is materialized as the empty directory a checkout without `--recurse-submodules` leaves; its content is never fetched. The same gitlink commit on both sides is named in `limits` as unchanged and does not make the comparison `partial`, since identical content cannot carry a change. An added, removed or moved gitlink is a coverage gap over its path on each side that has it, and over the whole scope when it is the selected scope itself, so the result is `partial`, never `compared`. A gitlink was refused (exit 2) before.
- **Google ADK.** `google.adk.Agent`, the package-root re-export of `google.adk.agents.llm_agent.Agent`, is read as an agent constructor by the ADK reader, for `from google.adk import Agent` and `import google.adk as adk` alike. CubeSandbox#1508 now establishes `cube_code_agent` and is `partial`, naming the tool it imports from a sibling module (#864), instead of `not_established`.
- **Re-measured.** At the root scope, main `a430e81a` refused CubeSandbox#1508, dlt#4417 and asterinas#3834; this change compares each, with its own limits (a Python census past `--max-python-files`, an unsupported framework, an unresolved import). No application comparison schema change; `application_comparison_schema_version` stays `"0.1"`.
- Follow a tool an agent binds from another module in the repository. (#864)
- **The problem.** jpka/attest#3 added `memory_bank.remember_firm_finding` and `memory_bank.recall_firm_memory` to an ADK agent's `tools=[...]`, with the module brought in by `from . import memory_bank`; the reader stopped at the module boundary, so `diff --application` showed no row and `scan` catalogued only the unchanged local tools. The same gap left OpenAI Agents SDK tools such as `from ..tools.shop import add_to_cart` unresolved.
- **What resolves.** For Google ADK, a name imported from a sibling module or re-exported by a package, a module-qualified `module.function`, a plain `alias = function`, and `FunctionTool(imported_function)` / `LongRunningFunctionTool(...)`, including a wrapper built in the imported module; for the OpenAI Agents SDK, a name or `module.function` that reaches a definition carrying the SDK's `@function_tool`. The tool is the definition, with its own signature, location and implementation digest, so jpka/attest#3 now shows the two memory tools as `ADDED` and leaves its four unchanged bindings, `scorer.score_answer` included, alone. A definition reached by several spellings is one tool; same-named functions in different modules stay two.
- **The boundary.** Only regular `.py` files inside the directory the read was given — the `--scope` for `diff --application`, the manifest directory for `scan` — are read, through the bounded input reader, and parsed without being imported or run. Symbolic links are not followed and a module name must match a file's exact spelling. Each application row reached through an import adds `import_path`: every module read, the line of the binding followed and that module's SHA-256; it is evidence, not compared meaning.
- **What stays unresolved, by name.** A module the scope does not contain (the scope spelled from the repository root, `svc.app.tools` with scope `svc/app`, is read inside it), a relative import above the scope, more than one matching module location, a name bound twice or only inside an `if`/`try`, a wildcard import, an import cycle, a class or other value, a parameter or other local assignment of the scope that uses the name, a name that scope binds more than once, a module attribute the same module reassigns (`tools.lookup = ...`, `setattr`), an attribute named like a step of the chain on an imported module that code running first reassigns (every module on the chain, the agent's own file included, every enclosing package's `__init__.py`, and every in-scope module those import; the defining module handing its function on does not count), and such a module that cannot be read (a link, or a missing relative module not imported under `except ImportError`), an SDK function without `@function_tool`, a symbolic link, or more than 64 modules read. The gap names the reason (`Not resolved because …`) and is scoped to the agent that lists the tool, so another agent's change in the same file is still established. One agent binding two different functions under one name is named, not resolved. The ADK unresolved-tool warning keeps its wording. No schema or contract change.
- **Identity.** One agent binding two different functions under one name binds neither, in both readers and whatever their order, and names both definitions. `import a.b` then `a.b.f` reads the submodule, as the import system does. A reference is read where it is used: a builder's own import is followed like a module-level one, `nonlocal` follows the outer function, a nested `def` that is the only one of its name is that definition, a module-level agent binds what the module binds at top level (its `def`, its import, its wrapper assignment) and never a same-named `def` or wrapper nested in a function, a module-level list's names are read at module level whatever the building function binds, and a factory's own toolset or wrapper variable is read like a module-level one. An SDK list variable is read only when the scope that binds it binds it once, to a literal list, and every use of it in the file only reads it — iterated, indexed, compared, tested, handed to a read-only builtin, standard-library reader or logger's method (each proven by its binding), to an agent's (or a copy's) own `tools=`, or to a function whose every use of that parameter is such a read. Spreading it (`[*TOOLS, x]`, `f(*TOOLS)`) or testing it (`TOOLS or []` in a condition) is a read. A method call on it, `+=`, a second name (including through `x or y`), a tuple, a return, `*args`, `globals()`, `sys.modules` or importing the module by `__name__` makes it dynamic. For `scan`, a definition that an import reaches and another configured source also reads (spelling the module's path the same way) is one catalog tool, and the binding reaches it through the exact definition the reader resolved; a `{tool: …}` selector for it is not ambiguous, and the dropped copy's guard evidence goes with it. A source an inventory completes keeps its own observation, so when that source imports a definition another source also reads, the catalog holds both and a selector for it is ambiguous. When what the module binds is not established (a name rebound, or bound only inside an `if`) and the ADK reader falls back to a same-named `def` or wrapper, that binding is named but never established: in a comparison its row is `not_established` on whichever side it is present, added, removed or changed, and so is the row of every tool the module's bindings of that name could give the agent instead, each followed to its definition (a function from a module outside the scope by its imported name); every one of the agent's rows is when one of those cannot be followed or a wildcard import could bind the name; for `scan` it stays the medium-confidence shadowed definition it was. `x = FunctionTool(func=x)` right after `def x` wraps that `def`, and is not a guess. Code that runs before the name is used but is not read — a relative import above the scope, an absolute import of a module the repository holds outside the scope (read from the compared commit's tree, or the checkout for `scan`, at the root, under `src/`, or under any directory between the root and the scope, modules the interpreter preloads, a standard-library name at a root that is a regular package, and the scope's own package aside; a linked or submodule entry counts, and a namespace directory only when it holds the named submodule, so SDK apps under `agents/` still import the SDK), a relative module no file provides (a generated `*_pb2` or `_version` aside), or a package `__getattr__` that is not the lazy-submodule idiom (or that the package could subvert through `sys.modules`, `globals()`, `__name__` or a patched `importlib`) — keeps the tool named. The `__init__.py` of every package above the scope, and what each imports (every package on the way to it and each submodule named), are read too; there a reassignment counts unless it sets one attribute of another module file outside the scope, a module of the scope they import is read like the chain's own, a change to `__path__` is a caveat (`pkgutil.extend_path` aside), their files are read in batches, and what they import in turn, or import by name at run time, is not followed. `sys.modules` and `globals()` are read by allow-list: a store whose key names a module on the chain, a package above one or the framework's own modules is a named stop, a module rebinding its own name through them or its own module object (however spelled, including `__dict__` and `vars()` stores) is a reassignment, reads (a comparison, iteration, a spread, `pkgutil.iter_modules(__path__)`, `get_type_hints(globalns=globals())`) are nothing, and any other use (`mods = sys.modules`, `|=`, a computed key or `setattr`, the module object anywhere but an attribute, a plain alias or a reader, `__dict__.update`, a frame's `f_globals`, `builtins.globals` under another name) or a change to `__path__` is a caveat: its row is `not_established` with the reason, including through a `FunctionTool` wrapper, and for `scan` the ADK module stays at medium. Attest's lazy loaders (`importlib.import_module(f".{name}", __name__)`, `if name == "x": from . import x`) stay established; `from . import other as x` is not the idiom. When a definition another source reads under its own name is the one the reader resolved, the binding reaches it through its exact locator, never a same-named local definition. The module-binding walk and the SDK list reader are linear in the tree, and a chain of thousands of attributes no longer crashes the run.

### Changes

Expand Down
Loading
Loading