fix(engine): resolve @-links per scope, and settle the remaining ADK gaps - #138
Merged
Conversation
…gaps Closes the last items from the ADK audit. Two were real bugs, two were dead code that should not be wired, and one is deferred with a reason. @-links now resolve against the scope that owns the document, not against the project root. An @-link in ~/.claude/CLAUDE.md — the real case is "@RTK.md", which sits next to it — was looked up as <repo>/RTK.md and silently found nothing. It became reachable when PR #133 started loading the user-global scope. AgentContextScopes pairs each search directory with its link root, LoadedContextDoc.Root and Rule.Root carry it, and BuildSystemPrompt groups links by root before resolving. The JIT rule path uses r.Root for the same reason. functioncallmodifier is removed rather than enabled. It injects synthetic arguments into tool declarations at request time; ask needed that for the required `description` phrase, which is now a static field on every native tool's params struct, so there is nothing left to inject. It had been registered with a predicate that always returned false since PR #132. Bridge tools still get `description` added to their input schema in pkg/tools/bridge.go, because their input types come from the MCP handler cores — a one-time build at construction, not per-request surgery. agenttool is rejected, and its four dead builders are deleted. agent_tool.go builds its own runner with a hardcoded config: no PluginConfig, so a subagent loses retryandreflect, and MemoryService: memory.InMemoryService(), so it loses ask's memory. It also produces one tool per agent, replacing task(agent: "foo") with a tool named foo. The task tool's nested engine.Run goes through RunnerBuilder — ask's plugins, memory, and file session service — and keeps the background-job path and the subagent UI events. toolconfirmation is deferred, not forgotten. It emits an adk_request_confirmation call, pauses the run, and resumes on a function response; ask has no suspend/resume path for a chat turn, so adopting it means building one. Approval stays ToolEnv.ApprovalDenied, blocking on the modal. IsConfirmationCall / UnwrapConfirmationCall stay wired because an MCP server can declare confirmation on its own tools; FormatConfirmationResponse had no callers at all and is deleted. One item from the audit was my error and needed no change: the deferred registry is not empty for subagents. The task tool passes the parent session's deferred tools as opts.Tools, so engine.Run never reaches the nil registryFunc. It is nil only on a pure headless engine.Run with no tools supplied, where there genuinely is no registry — MCP is not connected and the linear tools live in cmd/ask. docs/adk-20-upgrade.md gains a "Deliberate departures" section so none of these gets wired later by someone reading an import as an intention. Tests: global-scope @-links resolve under ~/.claude and do not pick up a same-named file in the project; project-scope links still resolve under the project root; every coding tool declares `description` statically, which is what makes functioncallmodifier unnecessary. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cidan
force-pushed
the
fix/remaining-adk-gaps
branch
from
August 21, 2026 07:31
af6db48 to
d22469d
Compare
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.
Closes the last items from the ADK audit. One real bug, two pieces of dead code that should not be wired, one deferred with a reason, and one item that was my error.
@-links resolve per scope (real bug)An
@-link in~/.claude/CLAUDE.mdwas resolved against the project root. Your actual case: it contains@RTK.md, which sits right next to it, and ask looked for<repo>/RTK.mdand silently found nothing. It became reachable when #133 started loading the user-global scope.LoadedContextDoc.RootandRule.Rootcarry it,BuildSystemPromptgroups links by root before resolving, and the JIT rule path usesr.Root. Verified against your real~/.claude:functioncallmodifierremoved, not enabledIt injects synthetic arguments into tool declarations at request time. ask needed that for the required
descriptionphrase — which is now a static field on every native tool's params struct, so there is nothing left to inject. It had been registered withPredicate: func(string) bool { return false }since #132.Bridge tools still get
descriptionadded to their input schema inpkg/tools/bridge.go, because their input types come from the MCP handler cores. That's a one-time build at construction, not per-request surgery.agenttoolrejected, four dead builders deletedagent_tool.go:142builds its own runner with a hardcoded config:No
PluginConfig, so a subagent would loseretryandreflect.MemoryServiceis in-memory, so it would lose ask's memory. And it produces one tool per agent, replacingtask(agent: "foo")with a tool namedfoo.The task tool's nested
engine.Rungoes throughRunnerBuilder— ask's plugins, memory service, and file session service — and keeps the background-job path and the subagent UI events.BuildResearchSubagent/BuildNamedSubagent/BuildResearchAgentTool/BuildNamedAgentToolhad no production callers and are deleted.toolconfirmationdeferredIt emits
adk_request_confirmation, pauses the run, and resumes on a function response. ask has no suspend/resume path for a chat turn, so adopting it means building one — a design change, not a bug fix. Approval staysToolEnv.ApprovalDenied, which blocks on the modal and returns the denial inline.IsConfirmationCall/UnwrapConfirmationCallstay wired in the event loops, because an MCP server can declare confirmation on its own tools and those branches render the inner intent correctly if it ever fires.FormatConfirmationResponsehad no callers at all and is deleted.Correction: the deferred registry was not a bug
I listed "empty deferred registry on the engine path" as a gap. That was wrong. The task tool already threads the parent session's registry:
engine.Runusesopts.Toolswhen non-empty, soGetDefaultToolFactory()and itsnilregistry are never reached. Subagents do see MCP andlinear_*. Thenilapplies only to a pure headlessengine.Runwith no tools passed, where there genuinely is no registry source — MCP isn't connected and the linear tools live incmd/ask.Docs
docs/adk-20-upgrade.mdgains a Deliberate departures section, so none of these gets wired later by someone reading an import as an intention.Tests
@-links resolve under~/.claude, and do not pick up a same-named file in the projectdescriptionstatically — the reasonfunctioncallmodifieris unnecessaryDefaultPluginsstays free of the modifiermake testgreen across all 9 packages.🤖 Generated with Claude Code