From 5875382ff79af86cb575aa2f1d7a4c8316800a42 Mon Sep 17 00:00:00 2001 From: huimiu Date: Fri, 15 May 2026 17:30:34 +0800 Subject: [PATCH 1/6] doc: add design spec for `azd ai agent skill` commands --- cli/azd/docs/design/ai-skill-design-spec.md | 405 ++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 cli/azd/docs/design/ai-skill-design-spec.md diff --git a/cli/azd/docs/design/ai-skill-design-spec.md b/cli/azd/docs/design/ai-skill-design-spec.md new file mode 100644 index 00000000000..5986c634459 --- /dev/null +++ b/cli/azd/docs/design/ai-skill-design-spec.md @@ -0,0 +1,405 @@ + + +# Design Spec: `azd ai agent skill` Commands + +## 1. Summary + +This spec covers the Foundry Skill commands that ship in the `azure.ai.agents` +extension: + +- `azd ai agent skill create ` (three mutually exclusive input modes). +- `azd ai agent skill update ` (in-place metadata or body update). +- `azd ai agent skill show ` (metadata only). +- `azd ai agent skill list` (paginated). +- `azd ai agent skill download ` (default extracts the server gzip; `--raw` keeps it). +- `azd ai agent skill delete ` (confirmation by default; `--force` to skip). + +These commands are pure CLI integration on top of the existing Foundry Skills +data plane. No new server work is required; packaged update is conditional on +Open Question 2 (§12). + +A Skill on the Foundry platform is a reusable behavioral guideline that an +agent can attach at runtime. The Skill payload is either inline JSON +(description + Markdown instructions) or a packaged gzip tarball that bundles +`SKILL.md` plus any sibling assets the skill ships. + +## 2. Scope and Non-Goals + +In scope: + +- The six subcommands above and their flag surface. +- File-input handling for `--file`: `SKILL.md` and packaged archives on + `create`, `SKILL.md` on `update`, and packaged update only if OQ2 (§12) + resolves to a supported service endpoint. +- Gzip extraction behavior for `download`, including safe extraction guarantees. +- Reuse of the existing endpoint-resolution cascade and cross-cutting flags. + +Out of scope: + +- Any change to the Foundry Skill REST surface. +- Wiring a skill into an agent build. `download` writes files to disk; what + the developer does with them is their responsibility. +- Tracking `--file` as a manifest. The file is read at invocation time only. + +## 3. Extension Placement and Surface + +The skill subtree lives inside the existing `azure.ai.agents` extension (no +new module, no change to `registry.json`). Commands surface as +`azd ai agent skill `. The layout is designed so that a future move to a +standalone `azd ai skill` extension is registration-only with no behavior diff. + +Internally, files land under +`cli/azd/extensions/azure.ai.agents/internal/cmd/skill_*.go`, mirroring the +`project_*.go` layout. A new typed client lives at +`internal/pkg/agents/skill_api/` so the agent client is not overloaded with +skill operations. + +## 4. Endpoint Resolution and Cross-Cutting Flags + +All skill commands resolve the Foundry project endpoint via the standard +5-level cascade already implemented for the extension: + +1. `-p` / `--project-endpoint` flag on the invoked command. +2. Active azd env value (`AZURE_AI_PROJECT_ENDPOINT`). +3. Global config under `extensions.ai-agents.project.context.endpoint` + in `~/.azd/config.json`. +4. Host environment variable `FOUNDRY_PROJECT_ENDPOINT`. +5. Structured error with an actionable suggestion. + +Each skill command accepts the cross-cutting flag set: `-p`, `--output`, +`--no-prompt`, and `--debug`. Resource-returning commands default to `json` +and allow `table` as an opt-in view. Verb-specific flags layer on top. + +## 5. Data-Plane Surface + +| Verb | HTTP method | Path | Notes | +|------|-------------|------|-------| +| Create (inline / parsed) | POST | `/skills` | JSON body | +| Create (package) | POST | `/skills:import` | `application/gzip` body | +| Show | GET | `/skills/{name}` | Metadata only | +| Update (inline / parsed) | POST | `/skills/{name}` | JSON body | +| Update (package) | POST | `/skills/{name}:import` (TBC) | Conditional on OQ2 (§12). | +| List | GET | `/skills` | Paginated; supports `top`, `orderby`, `skip`, etc. | +| Delete | DELETE | `/skills/{name}` | | +| Download | GET | `/skills/{name}:download` | Returns `application/gzip` | + +Auth uses the same bearer-token policy the existing agent client uses +(scope `https://ai.azure.com/.default`). The User-Agent header carries the +extension version, consistent with `agent_api`. + +## 6. Command Behavior + +### 6.1 `azd ai agent skill create ` + +Three mutually exclusive input modes. The CLI rejects (does not silently +merge) any combination that supplies more than one mode. + +| Mode | Flags | Wire format | +|------|-------|-------------| +| Inline | `--description --instructions ` | `POST /skills` (JSON) | +| File: `SKILL.md` | `--file ./path/to/SKILL.md` | `POST /skills` (JSON, CLI parses YAML front matter + body) | +| File: gzip package | `--file ./path/to/skill.tar.gz` | `POST /skills:import` (`application/gzip`) | + +Flag and argument shape: + +| Flag | Type | Required | Notes | +|------|------|----------|-------| +| `` | positional | yes | Validated per §6.7. | +| `--description` | string | inline mode only | Plain text; max length matches server limit. | +| `--instructions` | string | inline mode only | Markdown body. | +| `--file` | path | file modes only | Mode picked from extension (`.md`, `.tar.gz`, or `.tgz`). Must exist and be readable; validated before any network call. | +| `--force` | bool | no | When the name already exists, replace it (delete-then-create). | +| `-p`, `--output`, `--no-prompt`, `--debug` | | no | Cross-cutting (§4). | + +Mode selection logic: + +1. If both `--file` and (`--description` or `--instructions`) are supplied, + exit non-zero with a validation error pointing at the conflicting flags. +2. If only `--file` is supplied, branch on extension: + - `.md`: parse as `SKILL.md`, build the JSON body locally, send to + `POST /skills`. + - `.tar.gz` / `.tgz`: stream the file bytes to `POST /skills:import` with + `Content-Type: application/gzip`. The CLI does not inspect the contents of + the archive before upload, so server-side validation owns the package contents. + - Any other extension is rejected with a validation error before any + network call. +3. If only inline flags are supplied, both `--description` and `--instructions` + are required. Missing either is a validation error. +4. If neither mode is supplied: + - With prompting enabled (TTY, `--no-prompt` not set): prompt for description + and instructions interactively. + - With `--no-prompt` set: exit non-zero with a structured error listing the + missing inputs. + +`SKILL.md` parsing: + +- The file must start with a YAML front matter block delimited by `---`. +- The CLI extracts `name`, `description`, and any other metadata fields the + Skills service recognizes. The remaining Markdown body becomes + `instructions`. +- If the front matter `name` is present and differs from the positional + ``, the positional wins and a one-line warning is printed to stderr. + Suppressed under `--no-prompt` or `--output json`. +- If the front matter is missing or unparseable, the command fails with a + validation error that points to the problematic line. + +`--force` semantics on create: + +- Without `--force`: a name collision returns a `409`-shaped structured error + with the suggestion to use `--force` or `update`. +- With `--force`: the CLI issues a `DELETE` followed by the appropriate + create call. The two requests are not transactional; if the delete succeeds + but the create fails, the original skill is gone and the error message says so. + +### 6.2 `azd ai agent skill update ` + +Flags: + +| Flag | Type | Notes | +|------|------|-------| +| `` | positional, required | The skill on the server. | +| `--description` | string | Optional. | +| `--instructions` | string | Optional. | +| `--file` | path | Mutually exclusive with `--description` / `--instructions`. | + +Behavior: + +1. The CLI GETs the current skill, merges omitted fields locally, then POSTs + the merged payload to `/skills/{name}`. +2. `--file` follows the same `.md` versus `.tar.gz` rule as `create`. For `.md`, the CLI + parses front matter and body into the JSON update payload. + Gzip `--file` is conditional on OQ2 (§12); until confirmed, `.tar.gz` / + `.tgz` on `update` fails with a validation error suggesting `create --force`. +3. If no field flags and no `--file` are supplied, the command exits non-zero + with a validation error. +4. If the skill does not exist, the initial GET returns 404 and the command + fails with a "not found" error. + +`--force` is not available on `update`; the target skill must already exist. + +### 6.3 `azd ai agent skill show ` + +Returns metadata only. The Skill body lives behind `download`; `show` keeps +its output focused so that humans and coding agents can scan it without +streaming kilobytes of Markdown to the terminal. + +JSON output is the verbatim metadata returned by the service. Table output +prints a fixed key/value layout (`Name`, `Description`, `Created`, `Updated`, +plus any additional first-class fields the service exposes). + +### 6.4 `azd ai agent skill list` + +Flags: + +| Flag | Type | Notes | +|------|------|-------| +| `--top` | int | Maximum number of results to return. | +| `--orderby` | string | Forwarded to the service. | +| Cross-cutting | | See §4. | + +Behavior: + +1. Without `--top`, the CLI iterates all pages transparently into one flat list. + With `--top`, it fetches up to that many items and stops. +2. JSON output emits a top-level array (pagination hidden). Table output + prints columns `NAME`, `DESCRIPTION`, `UPDATED` (long descriptions truncated). +3. Pagination errors mid-iteration surface as a normal error; partial results + are not printed. + +### 6.5 `azd ai agent skill download ` + +Flags: + +| Flag | Type | Default | Notes | +|------|------|---------|-------| +| `` | positional, required | | | +| `--output-dir` | path | `./.agents/skills//` (resolved against the current working directory) | Where to write the extracted files (or the raw archive). | +| `--raw` | bool | `false` | Skip extraction; write the gzip archive as-is. | +| `--force` | bool | `false` | Overwrite existing files in the output directory. | + +Default behavior (no `--raw`): + +1. Stream the gzip response body to a temp file inside the OS temp directory. +2. Verify the response Content-Type starts with `application/gzip` (or the + equivalent server-confirmed content type). Otherwise, fail with a clear + error. +3. Walk the tar entries once to validate the complete archive and build an + extraction plan before writing to `--output-dir`. For each entry: + - Reject absolute paths and any path containing `..` components (zip-slip guard). + - Reject symlinks, hard links, and entries other than regular files and directories. + - Without `--force`, refuse to overwrite existing files (exit non-zero, + listing the colliding path). +4. Extract into a staging directory first; copy to `--output-dir` only after + full validation. A failed extraction must not write any files to `--output-dir`. +5. Files use the user's umask. Executable bits from the tar are dropped. +6. Decompression is bounded at 10,000 entries / 512 MB uncompressed. + Exceeding either limit aborts with `CodeSkillArchiveUnsafe`. + +`--raw` behavior: + +1. The CLI ensures `--output-dir` exists. +2. The gzip archive is written to `/.tar.gz`. Without + `--force`, an existing file of the same name causes a clean error. + +`--output-dir` resolution: + +- Relative paths resolve against cwd, not the azd project root. +- Default `./.agents/skills//` groups downloads under one directory. + Projects can add `.agents/` to `.gitignore`. + +### 6.6 `azd ai agent skill delete ` + +Flags: + +| Flag | Default | Notes | +|------|---------|-------| +| `--force` | `false` | Skip the confirmation prompt. | +| Cross-cutting | | See §4. | + +Behavior: + +1. Interactive mode: prompt `Delete skill ""? [y/N]:`. Declining returns + exit 0 (not `exterrors.Cancelled`). JSON mode emits the cancelled shape from §7. +2. `--no-prompt` without `--force`: refuse to delete (exit non-zero). +3. `--force`: skip the prompt regardless of TTY state. +4. Non-existent skill: 404 surfaced as a "not found" error. + +### 6.7 Skill Name Validation + +The CLI validates `` on every command that takes it: + +- Non-empty after trim. +- Matches the service-documented regex, or the conservative fallback + `^[a-zA-Z][a-zA-Z0-9-_]{0,62}$`. The service makes the final decision. + +## 7. Output Contracts + +JSON shapes for all commands with `--output json` are part of the public +contract and must not change without a deprecation step. The shapes: + +- `create`: passthrough of the created `Skill` resource returned by the service. +- `update`: passthrough of the updated `Skill` resource returned by the service. +- `show`: passthrough of the service `Skill` resource. +- `list`: a top-level array of `Skill` resources. Pagination is hidden. +- `download`: `{ "skill": "", "outputDir": "", + "files": ["", ...], "raw": false }` on extraction; + `{ "skill": "", "outputDir": "", + "archive": "", "raw": true }` with `--raw`. +- `delete`: `{ "deleted": true, "name": "" }`. Cancelled (interactive + `n`) emits `{ "deleted": false, "cancelled": true, "name": "" }` + with exit code `0`. + +Errors use `exterrors` so the azd host renders them consistently with the +rest of the extension. Reuse existing codes such as `CodeConflictingArguments` +and `CodeInvalidParameter` for generic flag problems. New skill-specific error +codes added by this work: + +- `CodeInvalidSkillName` +- `CodeInvalidSkillFile` (front matter parse failure, unsupported extension) +- `CodeSkillArchiveUnsafe` (rejected tar entry on extraction) +- `CodeSkillOutputCollision` (file exists, `--force` not set) + +These plug into the existing `exterrors.Validation` factory. + +## 8. Test Plan + +Unit tests (no network): + +- Mode-selection matrix for `create`: every combination of inline flags and + `--file` produces the expected outcome (success, conflict error, missing + field error). +- `SKILL.md` parsing: valid front matter, missing front matter, invalid + YAML, name-mismatch warning suppression under `--no-prompt` and + `--output json`. +- Pagination flattening for `list`: multi-page server response collapses into + one array in JSON output and one continuous table in table output. +- `download` extraction safety: tar entries containing `..`, absolute paths, + symlinks, and oversized entries are all rejected and leave the output + directory untouched. +- `download --raw` writes exactly the bytes returned by the service. +- `delete` cancellation paths: interactive `n`, `--no-prompt` without + `--force`, and `--force` all produce the documented exit codes and output; + interactive `n` is a successful no-op, not an `exterrors.Cancelled` error. +- Endpoint resolver: skill commands inherit the resolver and produce the + documented suggestion when nothing resolves. + +End-to-end (against a recorded fixture): + +- `create --file ./SKILL.md` then `show` then `download` round-trip; assert + the downloaded `SKILL.md` content equals the uploaded content. +- `create --file ./skill.tar.gz` then `download` (extract) then `download + --raw`; assert both forms are consistent. +- `list` with more than one page of results. + +## 9. Impact on Existing Commands + +No existing command's behavior, flags, or resolver logic changes. The new +`skill_api` package is a sibling of `agent_api`; the existing `AgentCardSkill` +type (agent-card capability) is unrelated and has no symbol conflict. + +## 10. Telemetry + +One event per command, reusing the extension's existing telemetry surface: + +- `azd.ai.agent.skill.create` (`mode`: `inline` / `file-md` / `file-gzip`, + `forced`: bool). +- `azd.ai.agent.skill.update` (`mode`: as above, `fieldsTouched`: count). +- `azd.ai.agent.skill.show` / `azd.ai.agent.skill.list` (`resolvedSource`). +- `azd.ai.agent.skill.download` (`raw`: bool, `forced`: bool, `extractedFileCount`). +- `azd.ai.agent.skill.delete` (`forced`: bool, `cancelled`: bool). + +No skill names, no descriptions, no instructions, no file paths, no project +endpoint values are emitted. Project-endpoint hostnames, if needed for +debugging, are hashed. + +## 11. Security Considerations + +- **Tar extraction.** Full rejection rules and decompression limits in §6.5. +- **File write permissions.** User's umask; executable bits dropped. +- **Auth.** Reuses the existing bearer-token pipeline; no new secret writes. +- **Argument echoing.** Debug logger sanitizes request bodies (same as `agent_api`). + +## 12. Open Questions + +1. **`create --force` semantics: delete-then-recreate versus a future server upsert.** + The Skills surface today has no explicit upsert verb, so the CLI implements `--force` + as delete-then-create. If the service later gains a true upsert (PUT-style) endpoint, + `--force` should flip to it transparently. Confirm this is acceptable before lock. +2. **Gzip update wire path.** Section 6.2 allows a gzip `--file` on `update` + only if the service exposes a named package update route. The documented + Skills data-plane surface currently lists `POST /skills:import` for create + and `POST /skills/{name}` (JSON body) for update. Confirm whether the + service exposes `POST /skills/{name}:import` for named gzip updates, or + whether gzip update should stay unsupported for this release. + Delete-and-import semantics should not be used for `update` unless the + design is revised to add an explicit destructive flag. + +## 13. Reference: Command Summary + +```bash +# Create (three mutually exclusive modes) +azd ai agent skill create --description "..." --instructions "..." \ + [-p ] [--output table|json] [--no-prompt] [--debug] [--force] +azd ai agent skill create --file ./SKILL.md \ + [-p ] [--output table|json] [--no-prompt] [--debug] [--force] +azd ai agent skill create --file ./skill.tar.gz \ + [-p ] [--output table|json] [--no-prompt] [--debug] [--force] + +# Update (any subset of fields; --file mutually exclusive with inline flags; +# packaged .tar.gz update is pending Open Question 2) +azd ai agent skill update [--description "..."] [--instructions "..."] \ + [--file ] [-p ] [--output table|json] [--no-prompt] [--debug] + +# Show / list / delete +azd ai agent skill show [-p ] [--output table|json] [--no-prompt] [--debug] +azd ai agent skill list [--top N] [--orderby ] \ + [-p ] [--output table|json] [--no-prompt] [--debug] +azd ai agent skill delete [--force] [-p ] [--output table|json] \ + [--no-prompt] [--debug] + +# Download (default extracts; --raw keeps the gzip archive) +azd ai agent skill download [--output-dir ] [--raw] [--force] \ + [-p ] [--output table|json] [--no-prompt] [--debug] +``` + +Resolution cascade: see §4. From 3e6eb6b4ad12330f837b9905b01e5793051deb95 Mon Sep 17 00:00:00 2001 From: huimiu Date: Fri, 15 May 2026 17:41:47 +0800 Subject: [PATCH 2/6] doc: add exterrors to cspell ignore; drop speculative open question Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/docs/design/ai-skill-design-spec.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/cli/azd/docs/design/ai-skill-design-spec.md b/cli/azd/docs/design/ai-skill-design-spec.md index 5986c634459..7b77b110af5 100644 --- a/cli/azd/docs/design/ai-skill-design-spec.md +++ b/cli/azd/docs/design/ai-skill-design-spec.md @@ -1,4 +1,4 @@ - + # Design Spec: `azd ai agent skill` Commands @@ -16,7 +16,7 @@ extension: These commands are pure CLI integration on top of the existing Foundry Skills data plane. No new server work is required; packaged update is conditional on -Open Question 2 (§12). +Open Question 1 (§12). A Skill on the Foundry platform is a reusable behavioral guideline that an agent can attach at runtime. The Skill payload is either inline JSON @@ -29,7 +29,7 @@ In scope: - The six subcommands above and their flag surface. - File-input handling for `--file`: `SKILL.md` and packaged archives on - `create`, `SKILL.md` on `update`, and packaged update only if OQ2 (§12) + `create`, `SKILL.md` on `update`, and packaged update only if OQ1 (§12) resolves to a supported service endpoint. - Gzip extraction behavior for `download`, including safe extraction guarantees. - Reuse of the existing endpoint-resolution cascade and cross-cutting flags. @@ -78,7 +78,7 @@ and allow `table` as an opt-in view. Verb-specific flags layer on top. | Create (package) | POST | `/skills:import` | `application/gzip` body | | Show | GET | `/skills/{name}` | Metadata only | | Update (inline / parsed) | POST | `/skills/{name}` | JSON body | -| Update (package) | POST | `/skills/{name}:import` (TBC) | Conditional on OQ2 (§12). | +| Update (package) | POST | `/skills/{name}:import` (TBC) | Conditional on OQ1 (§12). | | List | GET | `/skills` | Paginated; supports `top`, `orderby`, `skip`, etc. | | Delete | DELETE | `/skills/{name}` | | | Download | GET | `/skills/{name}:download` | Returns `application/gzip` | @@ -168,7 +168,7 @@ Behavior: the merged payload to `/skills/{name}`. 2. `--file` follows the same `.md` versus `.tar.gz` rule as `create`. For `.md`, the CLI parses front matter and body into the JSON update payload. - Gzip `--file` is conditional on OQ2 (§12); until confirmed, `.tar.gz` / + Gzip `--file` is conditional on OQ1 (§12); until confirmed, `.tar.gz` / `.tgz` on `update` fails with a validation error suggesting `create --force`. 3. If no field flags and no `--file` are supplied, the command exits non-zero with a validation error. @@ -361,11 +361,7 @@ debugging, are hashed. ## 12. Open Questions -1. **`create --force` semantics: delete-then-recreate versus a future server upsert.** - The Skills surface today has no explicit upsert verb, so the CLI implements `--force` - as delete-then-create. If the service later gains a true upsert (PUT-style) endpoint, - `--force` should flip to it transparently. Confirm this is acceptable before lock. -2. **Gzip update wire path.** Section 6.2 allows a gzip `--file` on `update` +1. **Gzip update wire path.** Section 6.2 allows a gzip `--file` on `update` only if the service exposes a named package update route. The documented Skills data-plane surface currently lists `POST /skills:import` for create and `POST /skills/{name}` (JSON body) for update. Confirm whether the @@ -386,7 +382,7 @@ azd ai agent skill create --file ./skill.tar.gz \ [-p ] [--output table|json] [--no-prompt] [--debug] [--force] # Update (any subset of fields; --file mutually exclusive with inline flags; -# packaged .tar.gz update is pending Open Question 2) +# packaged .tar.gz update is pending Open Question 1) azd ai agent skill update [--description "..."] [--instructions "..."] \ [--file ] [-p ] [--output table|json] [--no-prompt] [--debug] From fda201c8f8cda3d655373973cbcebecb9ac435b0 Mon Sep 17 00:00:00 2001 From: huimiu Date: Fri, 15 May 2026 17:49:30 +0800 Subject: [PATCH 3/6] docs: update design spec for `azd ai agent skill` commands to clarify file handling --- cli/azd/docs/design/ai-skill-design-spec.md | 34 +++++---------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/cli/azd/docs/design/ai-skill-design-spec.md b/cli/azd/docs/design/ai-skill-design-spec.md index 5986c634459..7072d0bd375 100644 --- a/cli/azd/docs/design/ai-skill-design-spec.md +++ b/cli/azd/docs/design/ai-skill-design-spec.md @@ -15,8 +15,7 @@ extension: - `azd ai agent skill delete ` (confirmation by default; `--force` to skip). These commands are pure CLI integration on top of the existing Foundry Skills -data plane. No new server work is required; packaged update is conditional on -Open Question 2 (§12). +data plane. No new server work is required. A Skill on the Foundry platform is a reusable behavioral guideline that an agent can attach at runtime. The Skill payload is either inline JSON @@ -29,8 +28,7 @@ In scope: - The six subcommands above and their flag surface. - File-input handling for `--file`: `SKILL.md` and packaged archives on - `create`, `SKILL.md` on `update`, and packaged update only if OQ2 (§12) - resolves to a supported service endpoint. + `create`, `SKILL.md` on `update`. - Gzip extraction behavior for `download`, including safe extraction guarantees. - Reuse of the existing endpoint-resolution cascade and cross-cutting flags. @@ -78,7 +76,6 @@ and allow `table` as an opt-in view. Verb-specific flags layer on top. | Create (package) | POST | `/skills:import` | `application/gzip` body | | Show | GET | `/skills/{name}` | Metadata only | | Update (inline / parsed) | POST | `/skills/{name}` | JSON body | -| Update (package) | POST | `/skills/{name}:import` (TBC) | Conditional on OQ2 (§12). | | List | GET | `/skills` | Paginated; supports `top`, `orderby`, `skip`, etc. | | Delete | DELETE | `/skills/{name}` | | | Download | GET | `/skills/{name}:download` | Returns `application/gzip` | @@ -166,10 +163,9 @@ Behavior: 1. The CLI GETs the current skill, merges omitted fields locally, then POSTs the merged payload to `/skills/{name}`. -2. `--file` follows the same `.md` versus `.tar.gz` rule as `create`. For `.md`, the CLI - parses front matter and body into the JSON update payload. - Gzip `--file` is conditional on OQ2 (§12); until confirmed, `.tar.gz` / - `.tgz` on `update` fails with a validation error suggesting `create --force`. +2. `--file` accepts `.md` only. The CLI parses front matter and body into the + JSON update payload. `.tar.gz` / `.tgz` on `update` is rejected with a + validation error suggesting `create --force`. 3. If no field flags and no `--file` are supplied, the command exits non-zero with a validation error. 4. If the skill does not exist, the initial GET returns 404 and the command @@ -359,22 +355,7 @@ debugging, are hashed. - **Auth.** Reuses the existing bearer-token pipeline; no new secret writes. - **Argument echoing.** Debug logger sanitizes request bodies (same as `agent_api`). -## 12. Open Questions - -1. **`create --force` semantics: delete-then-recreate versus a future server upsert.** - The Skills surface today has no explicit upsert verb, so the CLI implements `--force` - as delete-then-create. If the service later gains a true upsert (PUT-style) endpoint, - `--force` should flip to it transparently. Confirm this is acceptable before lock. -2. **Gzip update wire path.** Section 6.2 allows a gzip `--file` on `update` - only if the service exposes a named package update route. The documented - Skills data-plane surface currently lists `POST /skills:import` for create - and `POST /skills/{name}` (JSON body) for update. Confirm whether the - service exposes `POST /skills/{name}:import` for named gzip updates, or - whether gzip update should stay unsupported for this release. - Delete-and-import semantics should not be used for `update` unless the - design is revised to add an explicit destructive flag. - -## 13. Reference: Command Summary +## 12. Reference: Command Summary ```bash # Create (three mutually exclusive modes) @@ -385,8 +366,7 @@ azd ai agent skill create --file ./SKILL.md \ azd ai agent skill create --file ./skill.tar.gz \ [-p ] [--output table|json] [--no-prompt] [--debug] [--force] -# Update (any subset of fields; --file mutually exclusive with inline flags; -# packaged .tar.gz update is pending Open Question 2) +# Update (any subset of fields; --file accepts .md only, mutually exclusive with inline flags) azd ai agent skill update [--description "..."] [--instructions "..."] \ [--file ] [-p ] [--output table|json] [--no-prompt] [--debug] From 70420356b7240de5d3e3e4b187859c95d40fd409 Mon Sep 17 00:00:00 2001 From: huimiu Date: Fri, 15 May 2026 17:54:27 +0800 Subject: [PATCH 4/6] fix: correct typo in validation error description for front matter parsing --- cli/azd/docs/design/ai-skill-design-spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/azd/docs/design/ai-skill-design-spec.md b/cli/azd/docs/design/ai-skill-design-spec.md index fad9322aa04..3c6531c65fe 100644 --- a/cli/azd/docs/design/ai-skill-design-spec.md +++ b/cli/azd/docs/design/ai-skill-design-spec.md @@ -137,7 +137,7 @@ Mode selection logic: - If the front matter `name` is present and differs from the positional ``, the positional wins and a one-line warning is printed to stderr. Suppressed under `--no-prompt` or `--output json`. -- If the front matter is missing or unparseable, the command fails with a +- If the front matter is missing or unparsable, the command fails with a validation error that points to the problematic line. `--force` semantics on create: From 2cbac99ffc649a77f0fa4b5ed79ed5e01e9a3a34 Mon Sep 17 00:00:00 2001 From: huimiu Date: Fri, 15 May 2026 20:32:26 +0800 Subject: [PATCH 5/6] docs: address review feedback on skill design spec - Restrict the documented telemetry modes on `azd ai agent skill update` to `inline` / `file-md`. The previous "as above" wording implicitly included `file-gzip`, but section 6.2 rejects gzip on update, so the command can never emit that value. - Replace the inaccurate "debug logger sanitizes request bodies (same as agent_api)" line under Security Considerations. The Azure SDK pipeline used by agent_api sets `IncludeBody: true` under `--debug`, and the current `setupDebugLogging` sanitizer only redacts JSON connection-string fields. Document that this work extends the sanitizer to cover skill `description` / `instructions` before the skill client participates in body logging, and opts out of `IncludeBody` until that lands. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/docs/design/ai-skill-design-spec.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cli/azd/docs/design/ai-skill-design-spec.md b/cli/azd/docs/design/ai-skill-design-spec.md index 3c6531c65fe..15e9c0ffe0d 100644 --- a/cli/azd/docs/design/ai-skill-design-spec.md +++ b/cli/azd/docs/design/ai-skill-design-spec.md @@ -339,7 +339,9 @@ One event per command, reusing the extension's existing telemetry surface: - `azd.ai.agent.skill.create` (`mode`: `inline` / `file-md` / `file-gzip`, `forced`: bool). -- `azd.ai.agent.skill.update` (`mode`: as above, `fieldsTouched`: count). +- `azd.ai.agent.skill.update` (`mode`: `inline` / `file-md`, + `fieldsTouched`: count). Gzip updates are rejected at the flag layer + (§6.2), so `file-gzip` is not a valid emitted value here. - `azd.ai.agent.skill.show` / `azd.ai.agent.skill.list` (`resolvedSource`). - `azd.ai.agent.skill.download` (`raw`: bool, `forced`: bool, `extractedFileCount`). - `azd.ai.agent.skill.delete` (`forced`: bool, `cancelled`: bool). @@ -353,7 +355,15 @@ debugging, are hashed. - **Tar extraction.** Full rejection rules and decompression limits in §6.5. - **File write permissions.** User's umask; executable bits dropped. - **Auth.** Reuses the existing bearer-token pipeline; no new secret writes. -- **Argument echoing.** Debug logger sanitizes request bodies (same as `agent_api`). +- **Argument echoing.** `agent_api` sets `IncludeBody: true` on the Azure SDK + logging policy, and the current `setupDebugLogging` sanitizer only redacts + JSON connection-string fields. Skill request bodies carry user-authored + `description` and `instructions`, so this work extends the sanitizer with + JSON-field redaction rules for those fields (and any new free-text fields + the skill service exposes) before the skill client participates in + `IncludeBody` logging under `--debug`. Until the extended sanitizer is in + place, the skill client opts out of body logging so descriptions and + instructions are never written to `azd-ai-agents-.log`. ## 12. Reference: Command Summary From 45d72b0b374105fcf0bd34c3b525b51c2ff91087 Mon Sep 17 00:00:00 2001 From: huimiu Date: Mon, 18 May 2026 12:45:58 +0800 Subject: [PATCH 6/6] =?UTF-8?q?docs:=20address=20PR=20feedback=20=E2=80=94?= =?UTF-8?q?=20move=20skill=20commands=20to=20`azd=20ai=20skill`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer feedback on Azure/azure-dev#8204: - therealjohn: skill commands will ship in a new, standalone `azure.ai.skills` extension (namespace `ai.skill`) rather than the existing `azure.ai.agents` extension. Rename every command from `azd ai agent skill ` to `azd ai skill ` across §1, §3, §6.1–§6.6, §10, and §12. Update file paths to `cli/azd/extensions/azure.ai.skills/...` and move the typed client to the new extension's `internal/pkg/skill_api/`. Telemetry event names change to `azd.ai.skill.*`; debug log file becomes `azd-ai-skills-.log`. - jongio: align the fallback skill-name regex with the existing agent name pattern in `azure.ai.agents/internal/pkg/agents/agent_yaml`: `^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\$` (1–63 chars, alphanumeric with internal hyphens only). The previous fallback was labeled "conservative" but actually allowed underscores, required a leading letter, and permitted trailing special chars; aligning gives users one rule across resource kinds. Connected updates: - §4 endpoint cascade: prefer `extensions.ai-skills.project.context.endpoint` with fallback to `extensions.ai-agents.project.context.endpoint` so users who configured the endpoint via the agents extension are not forced to re-run `set`. - §5 / §9: drop the "sibling of agent_api" framing; the new client lives inside `azure.ai.skills`. Reaffirm that `AgentCardSkill` in `azure.ai.agents` is in a different module with no symbol conflict. - §11 Argument echoing: rewrite to describe `skill_api`'s own logging posture (opt out of `IncludeBody` until the sanitizer covers skill `description` / `instructions`) without depending on `agent_api`'s behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/docs/design/ai-skill-design-spec.md | 119 +++++++++++--------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/cli/azd/docs/design/ai-skill-design-spec.md b/cli/azd/docs/design/ai-skill-design-spec.md index 15e9c0ffe0d..d3eeff4317e 100644 --- a/cli/azd/docs/design/ai-skill-design-spec.md +++ b/cli/azd/docs/design/ai-skill-design-spec.md @@ -1,18 +1,18 @@ -# Design Spec: `azd ai agent skill` Commands +# Design Spec: `azd ai skill` Commands ## 1. Summary -This spec covers the Foundry Skill commands that ship in the `azure.ai.agents` -extension: +This spec covers the Foundry Skill commands that ship in the new +`azure.ai.skills` extension: -- `azd ai agent skill create ` (three mutually exclusive input modes). -- `azd ai agent skill update ` (in-place metadata or body update). -- `azd ai agent skill show ` (metadata only). -- `azd ai agent skill list` (paginated). -- `azd ai agent skill download ` (default extracts the server gzip; `--raw` keeps it). -- `azd ai agent skill delete ` (confirmation by default; `--force` to skip). +- `azd ai skill create ` (three mutually exclusive input modes). +- `azd ai skill update ` (in-place metadata or body update). +- `azd ai skill show ` (metadata only). +- `azd ai skill list` (paginated). +- `azd ai skill download ` (default extracts the server gzip; `--raw` keeps it). +- `azd ai skill delete ` (confirmation by default; `--force` to skip). These commands are pure CLI integration on top of the existing Foundry Skills data plane. No new server work is required. @@ -41,16 +41,16 @@ Out of scope: ## 3. Extension Placement and Surface -The skill subtree lives inside the existing `azure.ai.agents` extension (no -new module, no change to `registry.json`). Commands surface as -`azd ai agent skill `. The layout is designed so that a future move to a -standalone `azd ai skill` extension is registration-only with no behavior diff. +Skill commands ship in a new, standalone `azure.ai.skills` extension +(namespace `ai.skill`), separate from `azure.ai.agents`. Commands surface as +`azd ai skill `. Publishing the extension adds one new entry to +`registry.json`; no changes to `azure.ai.agents` are required. Internally, files land under -`cli/azd/extensions/azure.ai.agents/internal/cmd/skill_*.go`, mirroring the -`project_*.go` layout. A new typed client lives at -`internal/pkg/agents/skill_api/` so the agent client is not overloaded with -skill operations. +`cli/azd/extensions/azure.ai.skills/internal/cmd/skill_*.go`. A typed client +lives at `cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/`. The +extension owns no persistent state of its own; project context resolution is +covered in §4. ## 4. Endpoint Resolution and Cross-Cutting Flags @@ -59,8 +59,11 @@ All skill commands resolve the Foundry project endpoint via the standard 1. `-p` / `--project-endpoint` flag on the invoked command. 2. Active azd env value (`AZURE_AI_PROJECT_ENDPOINT`). -3. Global config under `extensions.ai-agents.project.context.endpoint` - in `~/.azd/config.json`. +3. Global config under `extensions.ai-skills.project.context.endpoint` + in `~/.azd/config.json`, falling back to + `extensions.ai-agents.project.context.endpoint` when the skills-owned key + is unset (so users who configured the endpoint via `azure.ai.agents` are + not forced to re-run `set`). 4. Host environment variable `FOUNDRY_PROJECT_ENDPOINT`. 5. Structured error with an actionable suggestion. @@ -80,13 +83,13 @@ and allow `table` as an opt-in view. Verb-specific flags layer on top. | Delete | DELETE | `/skills/{name}` | | | Download | GET | `/skills/{name}:download` | Returns `application/gzip` | -Auth uses the same bearer-token policy the existing agent client uses +Auth uses the same bearer-token policy the agent client uses (scope `https://ai.azure.com/.default`). The User-Agent header carries the -extension version, consistent with `agent_api`. +extension version, consistent with the rest of the AI extension surface. ## 6. Command Behavior -### 6.1 `azd ai agent skill create ` +### 6.1 `azd ai skill create ` Three mutually exclusive input modes. The CLI rejects (does not silently merge) any combination that supplies more than one mode. @@ -148,7 +151,7 @@ Mode selection logic: create call. The two requests are not transactional; if the delete succeeds but the create fails, the original skill is gone and the error message says so. -### 6.2 `azd ai agent skill update ` +### 6.2 `azd ai skill update ` Flags: @@ -173,7 +176,7 @@ Behavior: `--force` is not available on `update`; the target skill must already exist. -### 6.3 `azd ai agent skill show ` +### 6.3 `azd ai skill show ` Returns metadata only. The Skill body lives behind `download`; `show` keeps its output focused so that humans and coding agents can scan it without @@ -183,7 +186,7 @@ JSON output is the verbatim metadata returned by the service. Table output prints a fixed key/value layout (`Name`, `Description`, `Created`, `Updated`, plus any additional first-class fields the service exposes). -### 6.4 `azd ai agent skill list` +### 6.4 `azd ai skill list` Flags: @@ -202,7 +205,7 @@ Behavior: 3. Pagination errors mid-iteration surface as a normal error; partial results are not printed. -### 6.5 `azd ai agent skill download ` +### 6.5 `azd ai skill download ` Flags: @@ -243,7 +246,7 @@ Default behavior (no `--raw`): - Default `./.agents/skills//` groups downloads under one directory. Projects can add `.agents/` to `.gitignore`. -### 6.6 `azd ai agent skill delete ` +### 6.6 `azd ai skill delete ` Flags: @@ -265,8 +268,11 @@ Behavior: The CLI validates `` on every command that takes it: - Non-empty after trim. -- Matches the service-documented regex, or the conservative fallback - `^[a-zA-Z][a-zA-Z0-9-_]{0,62}$`. The service makes the final decision. +- Matches the service-documented regex, or the fallback + `^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$` (1–63 characters, + alphanumeric with internal hyphens only). This aligns with the agent name + pattern in `azure.ai.agents` (`agent_yaml/parse.go`) so users see one rule + across resource kinds. The service makes the final decision. ## 7. Output Contracts @@ -330,21 +336,23 @@ End-to-end (against a recorded fixture): ## 9. Impact on Existing Commands No existing command's behavior, flags, or resolver logic changes. The new -`skill_api` package is a sibling of `agent_api`; the existing `AgentCardSkill` -type (agent-card capability) is unrelated and has no symbol conflict. +`azure.ai.skills` extension introduces its own `skill_api` package and does +not modify `azure.ai.agents`. The `AgentCardSkill` type in `azure.ai.agents` +(agent-card capability) is unrelated, lives in a different module, and has no +symbol conflict with the new client. ## 10. Telemetry One event per command, reusing the extension's existing telemetry surface: -- `azd.ai.agent.skill.create` (`mode`: `inline` / `file-md` / `file-gzip`, +- `azd.ai.skill.create` (`mode`: `inline` / `file-md` / `file-gzip`, `forced`: bool). -- `azd.ai.agent.skill.update` (`mode`: `inline` / `file-md`, +- `azd.ai.skill.update` (`mode`: `inline` / `file-md`, `fieldsTouched`: count). Gzip updates are rejected at the flag layer (§6.2), so `file-gzip` is not a valid emitted value here. -- `azd.ai.agent.skill.show` / `azd.ai.agent.skill.list` (`resolvedSource`). -- `azd.ai.agent.skill.download` (`raw`: bool, `forced`: bool, `extractedFileCount`). -- `azd.ai.agent.skill.delete` (`forced`: bool, `cancelled`: bool). +- `azd.ai.skill.show` / `azd.ai.skill.list` (`resolvedSource`). +- `azd.ai.skill.download` (`raw`: bool, `forced`: bool, `extractedFileCount`). +- `azd.ai.skill.delete` (`forced`: bool, `cancelled`: bool). No skill names, no descriptions, no instructions, no file paths, no project endpoint values are emitted. Project-endpoint hostnames, if needed for @@ -355,41 +363,40 @@ debugging, are hashed. - **Tar extraction.** Full rejection rules and decompression limits in §6.5. - **File write permissions.** User's umask; executable bits dropped. - **Auth.** Reuses the existing bearer-token pipeline; no new secret writes. -- **Argument echoing.** `agent_api` sets `IncludeBody: true` on the Azure SDK - logging policy, and the current `setupDebugLogging` sanitizer only redacts - JSON connection-string fields. Skill request bodies carry user-authored - `description` and `instructions`, so this work extends the sanitizer with - JSON-field redaction rules for those fields (and any new free-text fields - the skill service exposes) before the skill client participates in - `IncludeBody` logging under `--debug`. Until the extended sanitizer is in - place, the skill client opts out of body logging so descriptions and - instructions are never written to `azd-ai-agents-.log`. +- **Argument echoing.** The Azure SDK Go logging policy supports + `IncludeBody: true` under `--debug`, which writes request and response + bodies to the extension's debug log. The new `skill_api` client opts out + of body logging until the extension ships a sanitizer that redacts + user-authored `description` and `instructions` fields (and any other + free-text fields the skill service exposes). Until that sanitizer is in + place, skill request bodies are never written to + `azd-ai-skills-.log`. ## 12. Reference: Command Summary ```bash # Create (three mutually exclusive modes) -azd ai agent skill create --description "..." --instructions "..." \ +azd ai skill create --description "..." --instructions "..." \ [-p ] [--output table|json] [--no-prompt] [--debug] [--force] -azd ai agent skill create --file ./SKILL.md \ +azd ai skill create --file ./SKILL.md \ [-p ] [--output table|json] [--no-prompt] [--debug] [--force] -azd ai agent skill create --file ./skill.tar.gz \ +azd ai skill create --file ./skill.tar.gz \ [-p ] [--output table|json] [--no-prompt] [--debug] [--force] # Update (any subset of fields; --file accepts .md only, mutually exclusive with inline flags) -azd ai agent skill update [--description "..."] [--instructions "..."] \ +azd ai skill update [--description "..."] [--instructions "..."] \ [--file ] [-p ] [--output table|json] [--no-prompt] [--debug] # Show / list / delete -azd ai agent skill show [-p ] [--output table|json] [--no-prompt] [--debug] -azd ai agent skill list [--top N] [--orderby ] \ - [-p ] [--output table|json] [--no-prompt] [--debug] -azd ai agent skill delete [--force] [-p ] [--output table|json] \ - [--no-prompt] [--debug] +azd ai skill show [-p ] [--output table|json] [--no-prompt] [--debug] +azd ai skill list [--top N] [--orderby ] \ + [-p ] [--output table|json] [--no-prompt] [--debug] +azd ai skill delete [--force] [-p ] [--output table|json] \ + [--no-prompt] [--debug] # Download (default extracts; --raw keeps the gzip archive) -azd ai agent skill download [--output-dir ] [--raw] [--force] \ - [-p ] [--output table|json] [--no-prompt] [--debug] +azd ai skill download [--output-dir ] [--raw] [--force] \ + [-p ] [--output table|json] [--no-prompt] [--debug] ``` Resolution cascade: see §4.