Skip to content

feat(agent): implement Skill, SkillSet, and SkillGenerator - #3397

Merged
ditman merged 9 commits into
mainfrom
feat/ts-agent-skill-generator
Oct 24, 2026
Merged

ditman merged 9 commits into
mainfrom
feat/ts-agent-skill-generator

Conversation

@ditman

@ditman ditman commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Implements the skill_generator feature blueprint in @a2ui/agent, adding Skill, SkillSet, and SkillGenerator so A2UI inference formats and component catalogs can be compiled into monolithic or modular SKILL.md packages.

Changes

  1. typescript/agents/a2ui_agent/src/skill/skill.ts and typescript/agents/a2ui_agent/src/skill/generator.ts implement Skill, SkillSet, and SkillGenerator:
    1. SkillGenerator.generateSkill(...) builds a single monolithic Skill from an initialized InferenceFormat (generateBaseRules() + generateCatalogInstructions() + generateExamples()).
    2. SkillGenerator.generateCoreSkill(...) builds a syntax-only Skill (a2ui-core) containing the format's base rules without catalog schemas.
    3. SkillGenerator.generateCatalogSkill(...) builds a catalog-scoped Skill (a2ui-<catalog>) containing a single catalog's schema instructions and optional examples.
    4. SkillGenerator.generateSkillset(...) returns a SkillSet pairing a2ui-core with one Skill per catalog.
    5. Skill.toMarkdown() renders the YAML frontmatter (name, description, and optional metadata) followed by the Markdown body, and SkillSet.exportToDirectory(outputDir) writes <outputDir>/<skill.filename> to disk after cleaning existing subdirectories for skills in the set.
  2. typescript/agents/a2ui_agent/src/inference-format.ts, ExpressFormat, and DirectJsonFormat expose readonly catalogs?: readonly CatalogApi[] on InferenceFormat so SkillGenerator can default to the format's registered catalogs.
  3. typescript/agents/a2ui_agent/tests/conformance/loader.ts and typescript/agents/a2ui_agent/tests/conformance/skill-conformance.test.ts enable all four conformance actions in conformance/agent/skill.yaml (from_format, core_syntax, from_catalog, and skill_set), alongside unit tests in typescript/agents/a2ui_agent/tests/unit/skill/skill-generator.test.ts.
  4. blueprints/codebases/typescript/agents/a2ui_agent/codebase.blueprint.md, KNOWN_GAPS.md, and CHANGELOG.md record skill_generator under implemented_features and document the new exports and conformance coverage.

Verification

  1. yarn --cwd typescript/agents/a2ui_agent build
  2. yarn --cwd typescript/agents/a2ui_agent test
  3. yarn --cwd typescript/agents/a2ui_agent lint
  4. yarn --cwd typescript/agents/a2ui_agent format:check
  5. python3 blueprints/validate_blueprints.py

One time:

For this PR:

  • I have updated the relevant CHANGELOG.md file.
  • I updated/added relevant documentation.
  • My code changes (if any) have tests.
  • If my branch is on a fork, I have verified that scripts/e2e_test.sh passes.

If you need help, consider asking for advice on the discussion board.

@ditman
ditman added this pull request to stack #3398 October 22, 2026 00:45
@github-project-automation github-project-automation Bot moved this to Todo in A2UI Oct 22, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces 'Skill', 'SkillSet', and 'SkillGenerator' to compile A2UI inference formats and component catalogs into monolithic or modular 'SKILL.md' packages, alongside comprehensive unit and conformance tests. The review feedback highlights a potential path traversal vulnerability in 'SkillSet.exportToDirectory' when deleting directories, recommends using 'Object.hasOwn' instead of the 'in' operator to prevent prototype lookup issues, points out a redundant check on 'rawId' in 'cleanCatalogName', and suggests removing unnecessary casting of 'fmt' in 'resolveCatalogsList'.

@ditman

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

@ditman
ditman force-pushed the feat/ts-agent-skill-generator branch from 2f2fbee to afe60be Compare October 23, 2026 01:55
@ditman

ditman commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the skill_generator feature for the TypeScript A2UI Agent SDK, introducing the Skill, SkillSet, and SkillGenerator classes to compile inference formats and component catalogs into monolithic or modular SKILL.md markdown documents with YAML frontmatter. It also integrates the skill.yaml conformance suite and adds comprehensive unit tests. The review feedback suggests improving the robustness of the SkillGenerator API by using nullish coalescing to fall back to positional arguments when options are partially provided, and refining the directory cleanup logic in SkillSet.exportToDirectory by removing the isDirectory() check to prevent potential EEXIST errors if a file exists where a directory is expected.

@nan-yu nan-yu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for porting this! A few things before merge, some tied to the plan in #3416:

  1. Assembly drift: generateSkill reimplements the body instead of sharing it with PromptGenerator.generate(), and the two already differ in TS (Express: ### Examples: only in the skill; Direct JSON: ## Workflow Description: and the schema block). Fine to land, but please don't claim parity in the description, and link #3416 so the shared assembler follows.
  2. #3526 in TS: DirectJsonPromptGenerator.generateCatalogInstructions(catalog) includes the protocol schemas, so a two-catalog Direct JSON monolithic skill repeats them. Either move them into generateBaseRules() here, or I'll cover TS in #3526. A Direct JSON two-catalog test would catch it either way.
  3. InferenceFormat.catalogs: not needed, since promptGenerator.catalogs is already public on the base class. Please revert the interface change and keep the format catalogs private.
  4. API surface: can we go options-object only, with one name per method (generateSkillSet), and drop length, items(), the positional Skill constructor and fuzzy get()? #3416 will reshape this API, and every alias becomes a deprecation.
  5. exportToDirectory: removal is guarded, but writing isn't. filename: '../x/SKILL.md' writes outside outputDir. Validate filename once and use it for both.
  6. CHANGELOG: the diff condenses existing entries and drops several BREAKING notes (Express positional args, Express validation, A2uiRequestProcessor state). Could this PR only add the skill line?

@nan-yu

nan-yu commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

I also left my thoughts in #3416 and some action items along with the proposal. Please take a look and let me know if they all make sense to you.

nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 23, 2026
… snippet

Add from_format_matches_snippet cases to conformance/agent/skill.yaml for
Express and Direct JSON, with one and two catalogs. Each builds a format,
generates the monolithic skill and the prompt snippet, and checks that the
skill's body equals the snippet.

The Python runner reads a catalogs list and the direct_json format, and
fails on an unhandled action. The TS harness skips the new action until
skill generation lands there (a2ui-project#3397).

Refs a2ui-project#3416, a2ui-project#3464

Signed-off-by: Nan Yu <nanyu@google.com>

@nan-yu nan-yu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround, everything from the last round looks good. Two small things, then I'm happy to approve:

  1. Skill.filename can be changed after construction, so SkillSet.add() can take a path that skipped validateSkillFilename. Since the rmSync guard was removed, that now deletes outside outputDir (details inline).
  2. A few TypeScript members are second names for the same thing (inline).

@ditman
ditman force-pushed the feat/ts-agent-skill-generator branch from ca466b0 to 4d3ea55 Compare October 23, 2026 22:55
@ditman
ditman requested a review from nan-yu October 23, 2026 22:58
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 23, 2026
… snippet

Add from_format_matches_snippet cases to conformance/agent/skill.yaml for
Express and Direct JSON, with one and two catalogs. Each builds a format,
generates the monolithic skill and the prompt snippet, and checks that the
skill's body equals the snippet.

The Python runner reads a catalogs list and the direct_json format, and
fails on an unhandled action. The TS harness skips the new action until
skill generation lands there (a2ui-project#3397).

Refs a2ui-project#3416, a2ui-project#3464

Signed-off-by: Nan Yu <nanyu@google.com>
- SkillGenerator methods take an options object only; the CatalogApi/string overloads and the structural isCatalogApi check are gone, and a missing catalog throws A2uiCatalogError.
- Skill.filename and SkillSet keys are validated on construction (relative, no empty, '.' or '..' segments), so exportToDirectory relies on that instead of guarding each path.
- InferenceFormat no longer declares catalogs; resolveCatalogsList reads promptGenerator.catalogs, and the formats keep their catalogs private.
- Express base rules match express_base_rules.txt byte for byte; rule 15's root example is filled from the active catalogs as Python does, so the golden substitutions in the tests are gone.
- The skill_set conformance case compares each skill's content against a golden via expected_skill_files.
…d, and keep one spelling per API member

- Skill takes an options object only and its filename is read-only; SkillSet.add validates the filename again so a JavaScript caller who reassigns it cannot make exportToDirectory remove or write outside the output directory.
- Removed the second spellings carried over from Python: generateSkillset (keep generateSkillSet), SkillSet.length (keep size) and SkillSet.items() (keep entries()).
A skill directory whose parent is a symlink out of the output directory used to be followed by rmSync, so exporting src/skills/nested/SKILL.md with out/src -> victim removed victim/skills/nested. exportToDirectory now resolves each skill directory's nearest existing ancestor and aborts, before touching anything, when it leaves the output directory. A symlink at the skill directory path itself is still removed as a link, as rmSync does, and replaced by a real directory.
@ditman
ditman force-pushed the feat/ts-agent-skill-generator branch from c116242 to 41c0ec8 Compare October 23, 2026 23:59
@ditman
ditman merged commit 5f94564 into main Oct 24, 2026
46 checks passed
@ditman
ditman deleted the feat/ts-agent-skill-generator branch October 24, 2026 00:08
@github-project-automation github-project-automation Bot moved this from Todo to Done in A2UI Oct 24, 2026
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 26, 2026
… snippet

Add from_format_matches_snippet cases to conformance/agent/skill.yaml for
Express and Direct JSON, with one and two catalogs. Each builds a format,
generates the monolithic skill and the prompt snippet, and checks that the
skill's body equals the snippet.

The Python runner reads a catalogs list and the direct_json format, and
fails on an unhandled action. The TS harness skips the new action until
skill generation lands there (a2ui-project#3397).

Refs a2ui-project#3416, a2ui-project#3464

Signed-off-by: Nan Yu <nanyu@google.com>
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 26, 2026
… snippet

Add from_format_matches_snippet cases to conformance/agent/skill.yaml for
Express and Direct JSON, with one and two catalogs. Each builds a format,
generates the monolithic skill and the prompt snippet, and checks that the
skill's body equals the snippet.

The Python runner reads a catalogs list and the direct_json format, and
fails on an unhandled action. The TS harness skips the new action until
skill generation lands there (a2ui-project#3397).

Refs a2ui-project#3416, a2ui-project#3464

Signed-off-by: Nan Yu <nanyu@google.com>
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 26, 2026
…TypeScript harness

SkillGenerator landed in TypeScript (a2ui-project#3397), so the TS skill harness now
runs the cases instead of skipping them: it reads a catalogs list and
compares generateSkill() with promptGenerator.generate(). Also drop a
duplicate unhandled-action branch left in the Python runner by the rebase.

Signed-off-by: Nan Yu <nanyu@google.com>
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 27, 2026
… snippet

Add from_format_matches_snippet cases to conformance/agent/skill.yaml for
Express and Direct JSON, with one and two catalogs. Each builds a format,
generates the monolithic skill and the prompt snippet, and checks that the
skill's body equals the snippet.

The Python runner reads a catalogs list and the direct_json format, and
fails on an unhandled action. The TS harness skips the new action until
skill generation lands there (a2ui-project#3397).

Refs a2ui-project#3416, a2ui-project#3464

Signed-off-by: Nan Yu <nanyu@google.com>
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 27, 2026
…TypeScript harness

SkillGenerator landed in TypeScript (a2ui-project#3397), so the TS skill harness now
runs the cases instead of skipping them: it reads a catalogs list and
compares generateSkill() with promptGenerator.generate(). Also drop a
duplicate unhandled-action branch left in the Python runner by the rebase.

Signed-off-by: Nan Yu <nanyu@google.com>
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 27, 2026
… snippet

Add from_format_matches_snippet cases to conformance/agent/skill.yaml for
Express and Direct JSON, with one and two catalogs. Each builds a format,
generates the monolithic skill and the prompt snippet, and checks that the
skill's body equals the snippet.

The Python runner reads a catalogs list and the direct_json format, and
fails on an unhandled action. The TS harness skips the new action until
skill generation lands there (a2ui-project#3397).

Refs a2ui-project#3416, a2ui-project#3464

Signed-off-by: Nan Yu <nanyu@google.com>
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 27, 2026
…TypeScript harness

SkillGenerator landed in TypeScript (a2ui-project#3397), so the TS skill harness now
runs the cases instead of skipping them: it reads a catalogs list and
compares generateSkill() with promptGenerator.generate(). Also drop a
duplicate unhandled-action branch left in the Python runner by the rebase.

Signed-off-by: Nan Yu <nanyu@google.com>
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 27, 2026
… snippet

Add from_format_matches_snippet cases to conformance/agent/skill.yaml for
Express and Direct JSON, with one and two catalogs. Each builds a format,
generates the monolithic skill and the prompt snippet, and checks that the
skill's body equals the snippet.

The Python runner reads a catalogs list and the direct_json format, and
fails on an unhandled action. The TS harness skips the new action until
skill generation lands there (a2ui-project#3397).

Refs a2ui-project#3416, a2ui-project#3464

Signed-off-by: Nan Yu <nanyu@google.com>
nan-yu added a commit to nan-yu/A2UI that referenced this pull request Oct 27, 2026
…TypeScript harness

SkillGenerator landed in TypeScript (a2ui-project#3397), so the TS skill harness now
runs the cases instead of skipping them: it reads a catalogs list and
compares generateSkill() with promptGenerator.generate(). Also drop a
duplicate unhandled-action branch left in the Python runner by the rebase.

Signed-off-by: Nan Yu <nanyu@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants