-
Notifications
You must be signed in to change notification settings - Fork 315
doc: add design spec for azd ai skill commands
#8204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
huimiu
wants to merge
7
commits into
main
Choose a base branch
from
hui/skill-design-spec
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+402
−0
Open
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5875382
doc: add design spec for `azd ai agent skill` commands
huimiu 3e6eb6b
doc: add exterrors to cspell ignore; drop speculative open question
huimiu fda201c
docs: update design spec for `azd ai agent skill` commands to clarify…
huimiu afc1e89
Merge branch 'hui/skill-design-spec' of github.com:Azure/azure-dev in…
huimiu 7042035
fix: correct typo in validation error description for front matter pa…
huimiu 2cbac99
docs: address review feedback on skill design spec
huimiu 45d72b0
docs: address PR feedback — move skill commands to `azd ai skill`
huimiu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,395 @@ | ||
| <!-- cspell:ignore exterrors foundry gzip tarball zipslip orderby foundrysdk --> | ||
|
|
||
| # 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 <name>` (three mutually exclusive input modes). | ||
| - `azd ai agent skill update <name>` (in-place metadata or body update). | ||
| - `azd ai agent skill show <name>` (metadata only). | ||
| - `azd ai agent skill list` (paginated). | ||
| - `azd ai agent skill download <name>` (default extracts the server gzip; `--raw` keeps it). | ||
| - `azd ai agent skill delete <name>` (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. | ||
|
|
||
| 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`. | ||
| - 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 <verb>`. 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 | | ||
| | 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 <name>` | ||
|
huimiu marked this conversation as resolved.
Outdated
|
||
|
|
||
| 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 <text> --instructions <markdown>` | `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 | | ||
| |------|------|----------|-------| | ||
| | `<name>` | 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 | ||
| `<name>`, 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 unparsable, 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 <name>` | ||
|
|
||
| Flags: | ||
|
|
||
| | Flag | Type | Notes | | ||
| |------|------|-------| | ||
| | `<name>` | 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` 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 | ||
| 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 <name>` | ||
|
|
||
| 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 <name>` | ||
|
|
||
| Flags: | ||
|
|
||
| | Flag | Type | Default | Notes | | ||
| |------|------|---------|-------| | ||
| | `<name>` | positional, required | | | | ||
| | `--output-dir` | path | `./.agents/skills/<name>/` (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 `<output-dir>/<name>.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/<name>/` groups downloads under one directory. | ||
| Projects can add `.agents/` to `.gitignore`. | ||
|
|
||
| ### 6.6 `azd ai agent skill delete <name>` | ||
|
|
||
| Flags: | ||
|
|
||
| | Flag | Default | Notes | | ||
| |------|---------|-------| | ||
| | `--force` | `false` | Skip the confirmation prompt. | | ||
| | Cross-cutting | | See §4. | | ||
|
|
||
| Behavior: | ||
|
|
||
| 1. Interactive mode: prompt `Delete skill "<name>"? [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 `<name>` 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. | ||
|
huimiu marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## 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": "<name>", "outputDir": "<absolute path>", | ||
| "files": ["<relative path>", ...], "raw": false }` on extraction; | ||
| `{ "skill": "<name>", "outputDir": "<absolute path>", | ||
| "archive": "<relative path>", "raw": true }` with `--raw`. | ||
| - `delete`: `{ "deleted": true, "name": "<name>" }`. Cancelled (interactive | ||
| `n`) emits `{ "deleted": false, "cancelled": true, "name": "<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`: `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). | ||
|
|
||
| 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.** `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-<date>.log`. | ||
|
|
||
| ## 12. Reference: Command Summary | ||
|
|
||
| ```bash | ||
| # Create (three mutually exclusive modes) | ||
| azd ai agent skill create <name> --description "..." --instructions "..." \ | ||
| [-p <url>] [--output table|json] [--no-prompt] [--debug] [--force] | ||
| azd ai agent skill create <name> --file ./SKILL.md \ | ||
| [-p <url>] [--output table|json] [--no-prompt] [--debug] [--force] | ||
| azd ai agent skill create <name> --file ./skill.tar.gz \ | ||
| [-p <url>] [--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 <name> [--description "..."] [--instructions "..."] \ | ||
| [--file <path>] [-p <url>] [--output table|json] [--no-prompt] [--debug] | ||
|
|
||
| # Show / list / delete | ||
| azd ai agent skill show <name> [-p <url>] [--output table|json] [--no-prompt] [--debug] | ||
| azd ai agent skill list [--top N] [--orderby <field>] \ | ||
| [-p <url>] [--output table|json] [--no-prompt] [--debug] | ||
| azd ai agent skill delete <name> [--force] [-p <url>] [--output table|json] \ | ||
| [--no-prompt] [--debug] | ||
|
|
||
| # Download (default extracts; --raw keeps the gzip archive) | ||
| azd ai agent skill download <name> [--output-dir <path>] [--raw] [--force] \ | ||
| [-p <url>] [--output table|json] [--no-prompt] [--debug] | ||
| ``` | ||
|
|
||
| Resolution cascade: see §4. | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.