fix(agents): resolve remote invoke name from inline service config#9114
Conversation
Brownfield init against an existing Foundry project writes the hosted agent name inline in the azure.ai.agent service, but does not populate the deployed AGENT_<SERVICE>_NAME environment output. Remote invoke only consulted that env value, so an otherwise valid unified azure.yaml service resolved with an empty agent name and failed with the misleading "agent name is required" error. Seed AgentServiceInfo.AgentName from the inline/config-nested hosted-agent definition, then let AGENT_<SERVICE>_NAME override it after deployment. Do not fall back to the azure.yaml service key because the service and deployed agent names may intentionally differ. Adds regression coverage for pre-deploy/brownfield inline-name resolution and for deployed environment output retaining precedence. Fixes #9109 Co-authored-by: Copilot <[email protected]> Copilot-Session: ea5e8124-350f-4420-b3dc-f88011b2aa74
📋 Prioritization NoteThanks for the contribution! The linked issue isn't in the current milestone yet. |
There was a problem hiding this comment.
Pull request overview
Fixes brownfield remote agent invocation by resolving the deployed agent name from inline service configuration.
Changes:
- Uses inline hosted-agent names before deployment outputs exist.
- Preserves environment output precedence.
- Adds regression tests for both resolution paths.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
internal/cmd/helpers.go |
Adds inline agent-name fallback resolution. |
internal/cmd/helpers_test.go |
Tests fallback and deployed-name precedence. |
There was a problem hiding this comment.
Verified the fix locally. Seeding the agent name from the inline hosted-agent definition before reading AGENT_<SERVICE>_NAME correctly fixes brownfield pre-deploy remote invoke, and the environment output still wins once it exists since it's applied after. All callers already guard on a non-empty AgentName, so no downstream regression. Both regression tests pass here.
trangevi
left a comment
There was a problem hiding this comment.
This feels like the wrong fix to me. If I understand what we're doing correctly, we're resolving the agent name from the azure.yaml, because the env var that we populate at deployment time doesn't exist. This means we haven't deployed yet, which also means we shouldn't be able to invoke that agent. Adding handling which allows a user to invoke against a previously existing deployed agent just because the names match has the potential to lead to unexpected confusion due to different code, protocols, etc.
In the case where a user has an azure.yaml without the corresponding env var, we should probably fail invoke, saying that the agent doesn't appear to have been deployed yet.
If the claim in the corresponding work item is that users should be able to invoke agents in existing projects by redefining the agent in the local azure.yaml, I don't think that's correct. They can already invoke without the need for an azd project at all, we added that support, I don't think we should introduce this potential logic split.
|
I traced the remote invoke path to sanity-check @trangevi's concern about invoking before deploy. The endpoint isn't gated on deploy. In project mode, The thing is, that capability already exists without this PR. So the real question isn't "should undeployed agents be invokable", since the explicit-name and If we do want a guard, the cleaner spot is a pre-invoke "not deployed / agent not found" check that applies to all three name-resolution paths, so the behavior stays consistent regardless of how the agent name is supplied. |
Addresses review feedback on PR #9114. Using every inline azure.yaml agent name as a pre-deploy fallback could silently invoke an older live agent with matching name but different code or protocols. Only use the inline name when the agent explicitly depends on an azure.ai.project service with a non-empty endpoint, which is the brownfield adoption signal from the reported scenario. Greenfield services without AGENT_<SERVICE>_NAME remain blocked and now receive an actionable "deploy first" validation error. Explicit agent names and --agent-endpoint continue to support intentionally invoking existing agents without deployment. Deployed AGENT_<SERVICE>_NAME remains authoritative. Adds tests for brownfield fallback, greenfield rejection, env precedence, and the improved error. Co-authored-by: Copilot <[email protected]> Copilot-Session: ea5e8124-350f-4420-b3dc-f88011b2aa74
|
Addressed the review discussion in f67f8a7 after reading all comments. @trangevi — agreed that a broad inline-name fallback was too permissive: a greenfield, undeployed azure.yaml could accidentally target an older live agent with matching name but different code/protocols. The fallback is now limited to the reported brownfield adoption shape: the agent must @jongio — this retains the intentional existing-agent capability you described. Explicit name and Added focused tests for brownfield fallback, greenfield rejection, deployed-name precedence, and the improved error. Full extension validation remains green. |
There was a problem hiding this comment.
Re-reviewed the reworked approach against the new commit. Gating the inline fallback on brownfield adoption (agent depends on an azure.ai.project service with a non-empty endpoint) preserves the #9109 fix while stopping a greenfield service from silently invoking an older live agent that shares the inline name. The deployed AGENT_<SERVICE>_NAME output still overrides, and greenfield now returns an actionable deploy-first error. Brownfield, greenfield, and env-precedence tests pass locally.
Non-blocking: the deploy-state branch is a missing-output case, so exterrors.Dependency with CodeMissingAgentEnvVars lines up with evalAgentContextError and the category table in the extension AGENTS.md better than Validation/CodeInvalidParameter. Fine as a follow-up.
A final call-site audit found that the shared agent resolver is also used by destructive commands such as delete. Applying the brownfield inline-name fallback globally could let delete target an adopted pre-existing agent before azd had deployed it. Make brownfield inline-name resolution an explicit resolver option and enable it only from remote invoke. All other commands retain env-only deployed-name resolution. The regression test asserts the default shared resolver does not target the brownfield agent, while invoke's opt-in path still resolves it. Co-authored-by: Copilot <[email protected]> Copilot-Session: ea5e8124-350f-4420-b3dc-f88011b2aa74
|
Follow-up 5db0605 scopes the brownfield inline-name fallback to remote invoke only after a full call-site audit found the shared resolver is also used by destructive commands such as |
Address review feedback by reporting a missing AGENT_<SERVICE>_NAME deployment output as exterrors.Dependency with CodeMissingAgentEnvVars, matching existing agent-context error classification. Assert the stable code and dependency category in the invoke error test. Co-authored-by: Copilot <[email protected]> Copilot-Session: ea5e8124-350f-4420-b3dc-f88011b2aa74
Check adopted agent names against the selected existing Foundry project, require confirmation before reuse, and persist replacement names when declined. Co-authored-by: Copilot <[email protected]> Copilot-Session: ef953bfc-0f9a-477e-aef6-7e703f7e4bee
|
Addressed the review feedback in commit
|
jongio
left a comment
There was a problem hiding this comment.
Re-reviewed the new commit (4936d3b) that adds brownfield agent-name conflict confirmation to the unified azure.yaml adopt path.
confirmAdoptedAgentNameConflicts runs after configureFoundryProject stamps FOUNDRY_PROJECT_ENDPOINT into the environment, so the shared resolveExistingAgentNameConflict resolver reads the selected project's endpoint and actually checks each declared agent against it rather than silently no-opping. updateAdoptedAgentNames only rewrites azure.yaml when the resolved name differs, walks services in sorted order, and handles both the unified inline shape (path name) and the deprecated config-nested shape (path config.name). It's gated on result.FoundryProject != nil, so greenfield init skips the check. Build and the new TestAdoptedAgentNameConfig / TestUpdateAdoptedAgentNames tests pass locally.
Non-blocking: in no-prompt mode the shared resolver prints "re-run init with --agent-name <unique-name>", but the unified azure.yaml adopt path returns through runInitFromAzureYaml and doesn't consume flags.agentName, so that specific recovery hint doesn't apply to adopted services. Adoption-specific no-prompt guidance would read better as a follow-up.
RickWinter
left a comment
There was a problem hiding this comment.
This resolves the agent name for remote invoke from the inline service configuration when deployment output is absent. No material issues found, and nothing blocks merge.
jongio
left a comment
There was a problem hiding this comment.
Re-checked the current HEAD. The narrowed opt-in scope handles the shared/destructive-command concern, but two things are still open.
The design question trangevi raised is the key one: gating brownfield resolution on a project endpoint proves the project exists, not that the inline agent exists in it, so remote invoke can still target a name that was never deployed. That's a maintainer decision on the intended behavior, not something to settle in code review.
One concrete code gap on the auto-protocol invoke path is noted inline.
Co-authored-by: Copilot <[email protected]>
jongio
left a comment
There was a problem hiding this comment.
The protocol/name split (protocolServiceName vs flags.name) resolves my earlier point: the positional azd ai agent invoke my-agent form now keeps the direct name through auto-detected protocol instead of returning deploy-first guidance. Verifying the inline name via AgentExists before targeting the adopted project, and surfacing env-read failures instead of falling back silently, both tighten the brownfield path. Built the extension package and ran the cmd tests locally, all green.
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
jongio
left a comment
There was a problem hiding this comment.
Re-checked the new autofix commit (ae1fd5d). Returning immediately once the brownfield inline agent is confirmed to exist is correct: this branch only runs when AGENT_<SERVICE>_NAME is absent from the environment, so the sibling version and endpoint outputs aren't present to read anyway, and AgentName plus ProjectEndpoint are already set for the invoke. The not-exists path is unchanged. Re-approving against the new HEAD.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 21 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
What
Remote
azd ai agent invokefailed after initializing a unifiedazure.yamlagainst an existing (brownfield) Foundry project, even though the agent service and inline name were declared.Root cause
Project-mode invoke normally gets the deployed agent identity from
AGENT_<SERVICE>_NAME, which is created by deployment. Brownfield adoption can pointazure.yamlat an existing Foundry project/agent without producing a new deploy output, leaving automatic name resolution empty.Fix
The final implementation distinguishes intentional brownfield adoption from an undeployed greenfield service and scopes the behavior to invoke only:
uses:list references anazure.ai.projectservice with a non-emptyendpoint. This is the explicit brownfield signal.delete— remain env-only and cannot implicitly target an adopted agent.AGENT_<SERVICE>_NAMEremains authoritative whenever deployment output exists.missing_agent_env_vars) and guidance to runazd deploy.--agent-endpointinvocation remain unchanged for users intentionally targeting an existing agent without deployment.This preserves the reported brownfield workflow while preventing greenfield or shared/destructive commands from silently targeting an older live agent with matching name but different code or protocols.
Tests
uses:+ projectendpointresolves the inline name only through invoke's opt-in resolver option.azd deployguidance withCodeMissingAgentEnvVars.Validation
go build ./...go test ./... -count=1(all extension packages)go vet ./...gofmtcleangolangci-lint run ./internal/cmd/...— 0 issuescspell— 0 issuesNo
CHANGELOG.mdentry is included; extension changelog entries are added during the release process.Fixes #9109