Skip to content

feat(appkit): Agent Skills (v1) — SKILL.md progressive disclosure for agents - #532

Closed
MarioCadenas wants to merge 4 commits into
mainfrom
feat/agent-skills
Closed

MarioCadenas wants to merge 4 commits into
mainfrom
feat/agent-skills

Conversation

@MarioCadenas

Copy link
Copy Markdown
Collaborator

Agent Skills (v1)

Runtime Agent Skills for the agents plugin — the SKILL.md format Claude Code / Cursor use, brought to AppKit agents. Only each skill's name + description sit in the system prompt (always-on, cheap); the full body loads on demand. Works on any Databricks-served model — AppKit implements the progressive disclosure itself, so it doesn't depend on a provider-native skills feature. Fills the seam the loader already reserved (RESERVED_DIRS = new Set(["skills"])).

Not to be confused with the dev-time "Databricks Agent Skills" product (Claude Code skills for building apps) — this is a runtime capability of deployed agents.

What a skill is

A directory with a SKILL.md (frontmatter name + description, Markdown body) plus optional bundled reference files. Frontmatter is an Anthropic-format superset (also tolerates license, allowed-tools, metadata); unknown keys warn, not error — so skills authored elsewhere drop in.

How it works

  • Every visible skill's name + description is injected into the agent's system prompt.
  • Two read-only built-in tools are added to any agent with a catalog: load_skill(skill) returns the body + a manifest of bundled files; read_skill_file(skill, path) reads one of those files (through a directory-containment guard).
  • The model auto-loads a skill when a task matches; a user can force one for a turn with /skill-name in chat (or useAgentChat's send(msg, { skill })).

Sources & visibility

  • Global bundle config/agents/skills/, per-agent config/agents/<id>/skills/, and a catalog UC Volume (skillsVolume / DATABRICKS_VOLUME_AGENT_SKILLS), read as the service principal.
  • Per-agent skills are always visible; global skills are opt-in via skills: [...] frontmatter (or autoInheritSkills).
  • Name collisions resolve to qualified <scope>:name; the bare name errors as ambiguous.

Commits (phased, independently reviewable)

  1. feat(appkit): load and resolve agent skills from bundle sources — model, parser, loader, per-agent catalog resolution
  2. feat(appkit): expose skills to agents via prompt catalog and load_skill — disclosure + built-in tools (bundle e2e)
  3. feat(appkit): source agent skills from a Unity Catalog volume — catalog source, SP-read
  4. feat(appkit): let users load skills from chat (/skill-name + picker) — client UX + forced load
  5. feat(appkit): document agent skills, wire sub-agents, ship example skill
  • plus a plugin-catalog sync and two dev-playground commits (a haiku demo skill + a /-triggered skill menu) for hands-on testing.

Try it in dev-playground

pnpm --filter=dev-playground dev, open /agent (Helper agent):

  • Type / → a menu of the agent's skills (/haiku); ↑/↓ + Enter/Tab to insert.
  • /haiku what's the weather in Paris? forces the skill; "give me a haiku about NYC taxi trips" triggers auto-load (watch the load_skill tool call).

Tests & verification

New coverage across skills.test.ts, dispatch-tool-call, skill-volume, skill-client, and the use-agent-chat hook. Full appkit (3075) and appkit-ui (366) suites pass; all packages typecheck; docs build succeeds.

Deferred to v2 (non-goals here)

Script/code execution from skills; allowed-tools enforcement (advisory only in v1); per-user (OBO) skill volumes; TTL refresh of volume listings; marketplace / end-user-uploaded skills; standalone runAgent skill parity.

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 909 KB (+36 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 929 KB (+37 KB) 326 KB (+14 KB)
Type declarations 351 KB (+11 KB) 122 KB (+4.3 KB)
Source maps 1.8 MB (+68 KB) 611 KB (+24 KB)
Other 11 KB 3.7 KB
Total 3.1 MB (+116 KB) 1.0 MB (+43 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 88 KB 2.5 KB 91 KB external 288 KB
./beta 56 KB (+7.1 KB) 457 B 56 KB (+7.1 KB) external 164 KB (+21 KB)
./testing 17 KB 0 B 17 KB external 50 KB
./tsdown 520 B 0 B 520 B external 813 B
./type-generator 21 KB 0 B 21 KB external 61 KB

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 84 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 40 KB
./beta stream-manager.js initial 5.8 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta databricks.js initial 3.0 KB
./beta configuration.js initial 2.1 KB
./beta service-context.js initial 1.3 KB
./beta client.js initial 434 B
./beta client-options.js initial 220 B
./beta supervisor-api.js lazy 192 B
./beta databricks.js lazy 142 B
./beta index.js lazy 123 B
./testing index.js initial 17 KB
./tsdown index.js initial 520 B
./type-generator index.js initial 21 KB

@databricks/appkit-ui

npm tarball (packed): 349 KB (+751 B) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 394 KB (+316 B) 132 KB (+158 B)
Type declarations 229 KB (+300 B) 84 KB (+130 B)
Source maps 765 KB (+1.2 KB) 253 KB (+492 B)
CSS 16 KB 3.2 KB
Total 1.4 MB (+1.8 KB) 471 KB (+780 B)
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 5.3 KB 49 KB 55 KB 208 KB 14 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 432 KB (+120 B) 49 KB 481 KB (+120 B) 1.3 MB 177 KB (+168 B)
./react/beta 1.0 KB 0 B 1.0 KB 0 B 1.9 KB

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 5.2 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 430 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 1.0 KB

@github-actions

github-actions Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 32470045125 -R databricks/appkit -n appkit-template-0.63.0-pr.2ca1159-feat-agent-skills-532 -D appkit-pr-532 \
  && unzip -o "appkit-pr-532/appkit-template-0.63.0-pr.2ca1159-feat-agent-skills-532.zip" -d "appkit-pr-532" \
  && databricks apps init --template "appkit-pr-532"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

@MarioCadenas
MarioCadenas changed the base branch from main to agents-discovery-dx August 19, 2026 13:57
@MarioCadenas
MarioCadenas force-pushed the agents-discovery-dx branch 2 times, most recently from 4b886ca to c3f6872 Compare August 20, 2026 16:24
Base automatically changed from agents-discovery-dx to main August 21, 2026 09:40
…ents)

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…xtures

Exercise the previously-untested skill sources/paths in the reference app:

- global `bullet-brief` skill; helper opts into it (multi-entry menu on a code agent)
- per-agent `query/skills/routing-brief` (bundle-agent source + bundled reference.md)
- per-agent `query/skills/haiku` collides with global `haiku` (query opts in),
  forcing qualified agent:haiku / bundle:haiku addressing
- add `query` to the /agent page picker so its skills surface in the input

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Reconciles with the rebased base: agents({ dir }) was removed (server/agents
is now a fixed convention), so the skill-client tests construct the plugin
with {} instead of { dir: false }.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Ponytail review cuts: one-line query picker entry (shorter label), and a
single worked example in routing-brief/reference.md. No behavior change.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
@MarioCadenas

Copy link
Copy Markdown
Collaborator Author

Split into two stacked PRs for easier review — this PR is superseded:

Both are rebased onto current main (includes #485 defineManifest), so they merge cleanly — unlike this branch, which is CONFLICTING. Suggest closing this in favour of the stack once #543/#544 land.

@MarioCadenas

Copy link
Copy Markdown
Collaborator Author

Superseded by the split stack: #543 (SDK, base main) → merge first, then #544 (playground + template fixtures, stacked). Both rebased onto current main and mergeable. Closing this one.

MarioCadenas added a commit that referenced this pull request Aug 21, 2026
Playground/template half of #532 (split 2/2), stacked on the SDK PR (1/2).
Demo skills exercising every source/case: global bullet-brief (+ helper opt-in),
per-agent query/skills/routing-brief, the agent:haiku/bundle:haiku collision, and
the template tracer-bullets skill. Auto-retargets to main once 1/2 merges.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
MarioCadenas added a commit that referenced this pull request Aug 21, 2026
Playground/template half of #532 (split 2/2), stacked on the SDK PR (1/2).
Demo skills exercising every source/case: global bullet-brief (+ helper opt-in),
per-agent query/skills/routing-brief, the agent:haiku/bundle:haiku collision, and
the template tracer-bullets skill. Auto-retargets to main once 1/2 merges.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
MarioCadenas added a commit that referenced this pull request Aug 31, 2026
#532) (#544)

* feat(appkit): agent skills v1 — SKILL.md progressive disclosure (1/2)

SDK half of #532 (split 1/2). Skills engine (parse/load/resolve/render/read),
agent-definition skills: wiring, agents-plugin integration (load_skill +
read_skill_file tools, catalog resolution, clientConfig), the appkit-ui
useAgentChat /skill surface, and docs. Playground/template fixtures follow in 2/2.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* chore(appkit): sync template manifest with agents skills volume resource

template/appkit.plugins.json is generated from the plugin manifests; it must
travel with the agents manifest.json change (skills volume resource) or CI's
sync:template check fails. Was mis-bucketed into the 2/2 fixtures PR.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* refactor(appkit): tidy skill loading + drop dead biome-ignore comments

Review follow-ups on #543:
- parallelize UC-volume skill reads (network-bound) via Promise.all; per-skill
  failures still skip individually and sorted order is preserved
- extract the useAgentChat slash-command parse into a named resolveSkill helper (appkit-ui)
- delete 18 stale biome-ignore comments — post-oxlint migration, and no-explicit-any
  is off in .oxlintrc.json, so they suppressed nothing

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* test(playground): agent skills fixtures + template demo (2/2)

Playground/template half of #532 (split 2/2), stacked on the SDK PR (1/2).
Demo skills exercising every source/case: global bullet-brief (+ helper opt-in),
per-agent query/skills/routing-brief, the agent:haiku/bundle:haiku collision, and
the template tracer-bullets skill. Auto-retargets to main once 1/2 merges.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* refactor(playground): drop dead biome-ignore comments in agent.route.tsx

react/exhaustive-deps is off in .oxlintrc.json and oxlint doesn't parse
biome directives, so these two suppressions were inert post-migration.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* fix(appkit-ui): gate /skill-prefix parsing on the skill catalog

The leading-/token sugar in useAgentChat stripped any /word off the
message and sent it as a skill, so ordinary messages like '/tmp is full'
or '/usr/bin/python needs upgrading' were mangled into a bogus skill with
the first word cut out.

Parse the leading token as a skill only when it matches a name in the
agent's skill catalog; otherwise send the message verbatim. useAgentChat
gains an optional 'skills' option (the known names); the template passes
its catalog through, and the playground route applies the same guard to
its inline parser.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

---------

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Co-authored-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
MarioCadenas added a commit that referenced this pull request Aug 31, 2026
* refactor(appkit): extract pure agents-plugin helpers into sibling modules

Step 1 of splitting the ~2.5k-line agents plugin. Moves module-scope pure
functions/constants out of agents.ts verbatim (behavior-preserving):
- approval.ts            requiresApproval
- prompt.ts              composePromptForAgent
- builtin-tools.ts       LOAD_SKILL_TOOL_DEF, READ_SKILL_FILE_TOOL_DEF
- adapter-extensions.ts  buildAdapterExtensions, supervisorToolDescription, warnOnCapabilityMismatch

agents.ts: 2512 -> 2331 lines. typecheck + 394 agent tests green.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* refactor(appkit): extract agents skill loading/dispatch into skill-loader

Step 2 of splitting the agents plugin. Moves skill discovery, per-agent
catalog resolution, and the load_skill/read_skill_file dispatch into
skill-loader.ts as free functions; the class keeps thin delegators (call sites
unchanged) and skillWorkspaceClient() as the OBO credential seam.

agents.ts: 2331 -> 2152 lines. typecheck + 394 agent tests green.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* refactor(appkit): extract agents registry assembly into registry.ts

Step 3 of splitting the agents plugin. Moves the decoupled boot-time
assembly helpers into registry.ts: loadCodeAgents, hasCodeAgentSources (now
internal), resolveDefaultAgent, and the AgentSource type. buildAgentRegistry
stays as the orchestrator that wires them. Also merges a duplicate import in
skill-loader.ts.

agents.ts: 2152 -> 2088 lines. typecheck + 394 agent tests green.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* refactor(appkit): extract agents tool-dispatch engine into tool-dispatch.ts

Step 4 (final) of splitting the agents plugin. Moves dispatchToolCall +
runSubAgent — the tool-call budget, approval gate, and sub-agent recursion —
into tool-dispatch.ts as free functions over RunState + a ToolDispatchDeps
object. The plugin builds deps via toolDispatchDeps(); the two executeTool
closures call the free function. RunState moves with them. Tests updated to
invoke the free functions (deps built from the plugin's own builder).

agents.ts: 2088 -> 1839 lines (2512 -> 1839 across all four steps). typecheck +
394 agent tests green.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* refactor(appkit): extract agents config resolution into resolve-config.ts

Moves resolvedApprovalPolicy / resolvedLimits defaulting into pure functions
over AgentsPluginConfig. The getters keep the approval-policy memo cache and
delegate. Config-only interface; both now unit-testable in isolation.

agents.ts: 1839 -> 1812 lines. typecheck + agent tests green.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* refactor(appkit): extract stream tracking into StreamRegistry

Moves the active-stream map + per-user counter (the O(1) concurrency-limit
check) into a StreamRegistry class. The plugin holds one instance and keeps
trackStream/untrackStream/countUserStreams as delegators; cancel/approve read
via streams.get(). Tests inject via trackStream and assert via the registry.

agents.ts: 1812 -> 1784 lines. typecheck + 394 agent tests green.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* refactor(appkit): rename agents stream tracker to ActiveStreamTracker

The previous commit named it StreamRegistry, colliding with the existing
SSE-layer StreamRegistry in src/stream/ (connection/event-buffer tracking used
by StreamManager). They're different concepts; renamed the agents-plugin one to
ActiveStreamTracker (tracks active streams + per-user counts for the O(1)
concurrency limit) to avoid the name clash.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* fix(appkit): finish ActiveStreamTracker rename (add file + agents wiring)

The prior commit's git add hit the already-deleted stream-registry.ts path,
aborted, and recorded only the deletion — leaving the pushed tip non-compiling
(agents.ts imported the removed file; active-stream-tracker.ts was uncommitted).
This adds the new module and the agents.ts import/usage so the tree builds.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

* docs(appkit): note volume skills list resource files one level only

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

---------

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Co-authored-by: MarioCadenas <MarioCadenas@users.noreply.github.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.

1 participant