Managed and Prompt Agents - #9687
Closed
Kshitij Chawla (kshitij-microsoft) wants to merge 27 commits into
Closed
Conversation
…a/azd-managed-harness
…a/azd-managed-harness
…ates Rename agent manifest ref to agent definition ref, extend prompt harness gating, update agent YAML mapping and API models, and refresh init/show command behavior with tests.
…a/azd-managed-harness
Kshitij Chawla (kshitij-microsoft)
force-pushed
the
kchawla/azd-managed-harness
branch
from
August 26, 2026 09:55
849025b to
ff410f9
Compare
… fixes
Adds RAI policy support end to end: init discovers the policies on the
Foundry account and records one in the environment, and the deploy graph
gains a policy node that lists the account's policies to verify the
declared name. A missing policy is now a warning that falls back to the
built-in default rather than a hard failure -- the account applies its own
default content filters to an agent that names no policy at all, so the
fallback leaves the agent no less filtered than publishing it without
guardrails would.
Also fixes three defects found while exercising the $ref authoring path:
- Agent definitions were decoded into the inline hosted-agent shape before
the kind was checked, so a prompt agent's string "model" failed to
unmarshal into the hosted Model struct. The kind gate now runs first.
- rai_policy_name was validated before environment expansion, which
rejected ${RAI_POLICY_ID} for not being a full ARM resource ID.
Unexpanded values are now deferred to the deploy path, where the
expanded value is validated.
- Connection authType "Entra" -- the spelling azd's own documentation and
scaffolding used -- has never been accepted by the service, whose
discriminator for that mode is AAD. It is now normalized rather than
forwarded verbatim into a bad request that lists twenty-one auth types
without explaining that the two name the same thing.
Replaces the standalone Foundry skills client with the shared skills path,
and updates the agent schema accordingly.
Bring CHANGELOG.md and README.md in line with what the branch actually ships. The entries were written against the behavior in the code, not against the design notes, so each one names the failure it replaces rather than only the feature it adds. CHANGELOG: - --kind managed is rejected in favor of --kind prompt with a harness, the preview notice now fires on every init path, and the harnessed flavor gets its own default agent name so two inits in one folder no longer collide on the Foundry agent identity. - RAI policy selection during init, the RAI_POLICY_ID indirection that keeps a scaffold portable across subscriptions, and the best-effort pre-publish verification that names the policy and the account. - skills/ folders now become azure.ai.skill services owned by the skills extension; this extension only attaches the published version. - connections and toolbox references become sibling Foundry services so provision and deploy order live in azure.yaml. - azure.ai.routine is recognized in uses: with the dependency running routine to agent. - The harness type spelling is github_copilot_preview throughout. README: point at the init-time policy picker and --rai-policy from the RAI section, which previously implied hand-editing was the only path.
A branch-wide sweep with deadcode, staticcheck, golangci-lint and cspell turned up two findings introduced by this branch. One was a latent bug. service_target_prompt.go: resolvePromptWorkspaceFromAzure assigned p.credential, a concrete *azidentity.AzureDeveloperCLICredential, into an azcore.TokenCredential and then compared that interface to nil. Prompt agents skip the hosted credential-init path, so p.credential is always nil there -- but a nil concrete pointer boxed into an interface yields an interface that is non-nil while carrying a nil pointer. The comparison never succeeded, the promptCredential() fallback never ran, and a typed-nil credential was handed to armresources.NewClient. The nil check now happens on the concrete pointer before the assignment, so workspace discovery actually falls back instead of relying on the deferred recover above it to swallow the result. No other assignment into azcore.TokenCredential has the same shape. deploy.go: the deprecated deploy command reassigned extCtx from ensureExtensionContext and never read it. ensureExtensionContext is pure, so the call was dead. The parameter is kept for symmetry with the other constructors in root.go and the doc comment now says why it is unused. resource_services_test.go: gosec G301 -- the test bundle directory was created 0o755 and is now 0o750. cspell.yaml: allow the fifteen terms the branch introduces (prompt-agent graph, skills, connection and policy vocabulary) so the extension spell check passes. samples_test.go is removed. It walked a samples/ tree at the extension root that was never committed, so it would have failed in CI on every run while passing locally off untracked files. Verified clean on all three touched modules: gofmt -s, go fix -diff, go build, go test, golangci-lint v2.11.4, staticcheck 2026.1, cspell 8.13.1 and the copyright header check.
Three related parse gaps in the prompt agent manifest, all with the same
shape: an author writes something wrong and azd deploys anyway.
`harness:` changed from a bare string to a block during this work, but
nothing handled the old form. An author carrying a manifest forward got
go-yaml's "cannot unmarshal !!str into agent_yaml.PromptHarness", which
names a Go type and no fix. It now reports the block to write, and
upgrades `ghcp` to `github_copilot_preview` in that suggestion so the
two changes are fixed in one pass rather than one after the other.
`harness.type: ghcp` in the block form was forwarded verbatim, so the
rename was only enforced on the `--harness` flag -- the fourth instance
on this branch of the interactive path validating and the declarative
path not. Only the name azd itself renamed is rejected; an unrecognized
harness still passes through, which is the documented reason there is no
allowlist.
The `harness:` and `memory:` blocks were decoded non-strictly, so
`builtin_tool:` for `builtin_tools:` bound nothing and left every
built-in capability enabled. Both now reject unknown keys. This is
scoped to the blocks azd interprets, not applied at the top level:
validateAgentServiceDefinition passes the whole azure.yaml service entry
-- host, project, uses, config -- through the same validator, so a
top-level strict decode would reject every prompt agent. Tools stay
`[]any` and are unaffected, so a tool type newer than this build still
deploys.
Also drops the panic recovery in resolvePromptWorkspaceFromAzure. It
converted a crash into ("", false), which the caller cannot tell apart
from "no workspace exists", so a panic silently became a request to
provision a new workspace -- that is what hid the typed-nil credential
bug fixed in b59d590. deployPromptAgent already recovers at the RPC
boundary and reports it as a deploy error.
Guards two prompt responses that were dereferenced unconditionally,
matching the form already used in delete.go and eval_helpers.go.
The two words added to cli/azd/.vscode/cspell.yaml were not needed. subagents appears in no Go file, and builtin already occurs in core files that pass CI today, so cspell resolves it from its own dictionaries. Reverting keeps this branch out of the shared core config; the extension config covers the terms this work actually introduces.
`azd ai agent init` no longer writes a separate agent.yaml for prompt and
managed agents. The definition -- kind, model, instructions, harness, tools,
memory, connections, policies -- is written inline on the azure.yaml service
entry, which is the shape hosted and voice agents already used, so every agent
kind is authored in one file and `kind: prompt` is what identifies it.
The config.promptAgent block is no longer written either. Every value it
carried (subscription, resource group, workspace, project endpoint) is recorded
in the azd environment by provision and read from there at deploy time, so the
block could only ever hold a copy of the environment or a set of ${VAR}
references pointing back at it. Hosted agents never had an equivalent.
Deploy still accepts a definition in its own file through $ref:, the
agent.yaml/agent.yml convention, or AGENT_DEFINITION_PATH, and still prefers a
promptAgent block when one is present, so existing projects keep working.
Also in this change:
- Resolve prompt agents by `kind: prompt` rather than by the removed config
block. list, show, invoke, delete and the down hooks all used the block as
their discriminator and stopped recognizing an inline definition.
- Surface the whole harness block in `azd ai agent show`, not just its type:
pinned skills, sandbox CPU/memory/idle timeout, and allowed/excluded built-in
tools. An explicit empty allow list renders as "(none)" so "nothing allowed"
is distinguishable from "not configured".
- Spell a RAI policy `rai_policy_name` in azure.yaml, matching the service and
agent.yaml. Inline entries went through the JSON tag, which was camelCase, so
the same field had two spellings depending on where it was written. The
legacy key is still read.
- Validate an inline harness/memory block as strictly as an authored one.
UnmarshalYAML never runs on the inline path, so the typo checks added for
agent.yaml would have been lost on what is now the default route.
- Set servicePath for prompt agents, which an early return had skipped, so the
skills/ and vector-assets/ convention folders resolve relative to the service
directory when the definition is inline.
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.
No description provided.