NewWacht Bench is live — AI-assisted development for Wacht
GuidesAgents

Agent skill bundles

Upload, inspect, and manage per-agent skill bundles from the backend SDKs.

Agent skill bundles

An agent runs with two kinds of skills. System skills are built into the platform and available to every agent — the reusable agentic primitives Wacht maintains, read-only through this API. Agent skills are ones you upload per agent as a ZIP; they land in the agent's filesystem under /skills/agent/<slug> and are tracked in the database so you can list, fetch, and delete them independently.

The backend SDKs (@wacht/backend, wacht-rs) cover the full lifecycle. Every endpoint is on the backend API surface (/ai/agents/{id}/skills/*) — API-key authenticated, not user-session.

Bundle format

A bundle is a ZIP that contains exactly one skill: either a SKILL.md at the zip root, or a single top-level folder containing one. More than one top-level folder is rejected.

---
name: My Skill
description: One-line summary an agent uses to decide when to load this skill.
---

# Body content

The slug comes from the zip file name (when SKILL.md is at the root) or the single top-level folder name — not from the frontmatter. It's kebab-case normalized. The frontmatter name becomes the display name and description the summary; both are optional, and an upload with no frontmatter still succeeds as long as SKILL.md is present. Bundles are capped at 25 MB; symlinks are rejected.

List the summary

Returns system and agent skills in one call. Each row carries slug, name, optional description, mount_path, and source ("system" or "agent").

Node

import { ai } from "@wacht/backend";

const summary = await ai.listAgentSkillsSummary("agent_id");
console.log("system skills:", summary.system.length);
console.log("agent skills:", summary.agent.length);

Rust

let summary = client
    .ai()
    .agents()
    .list_skills_summary("agent_id")
    .send()
    .await?;

Browse the filesystem

list_skill_tree returns the directory listing for a scope (system or agent) at a path. Use it to walk the bundle structure or render a tree UI.

Node

const tree = await ai.listAgentSkillTree("agent_id", "agent", "/");
for (const entry of tree.entries) {
  console.log(entry.kind, entry.path);
}

Rust

use wacht::models::SkillScope;

let tree = client
    .ai()
    .agents()
    .list_skill_tree("agent_id", SkillScope::Agent)
    .send()
    .await?;

Read a file

Text files come back as content; binaries as content_base64. Check is_text to know which.

Node

const file = await ai.readAgentSkillFile("agent_id", "agent", "/my-skill/SKILL.md");
const body = file.is_text ? file.content : Buffer.from(file.content_base64!, "base64");

Rust

let file = client
    .ai()
    .agents()
    .read_skill_file("agent_id", SkillScope::Agent, "/my-skill/SKILL.md")
    .send()
    .await?;

Upload a bundle

Multipart POST. replace_existing decides what happens on a slug collision: true overwrites the matching skill, false errors. The call returns the updated agent skill tree.

Node

const file = new File([buffer], "my-skill.zip", { type: "application/zip" });
const tree = await ai.importAgentSkillBundle("agent_id", file, {
  replace_existing: true,
});

Rust

let bytes = std::fs::read("./my-skill.zip")?;
let tree = client
    .ai()
    .agents()
    .import_skill_bundle("agent_id", "my-skill.zip", bytes)
    .replace_existing(true)
    .send()
    .await?;

Delete an agent skill

Removes the skill row and its files. Only agent-scoped skills are deletable; system skills are read-only through this API and managed at the platform deployment level.

Node

await ai.deleteAgentSkill("agent_id", "my-skill");

Rust

client
    .ai()
    .agents()
    .delete_skill("agent_id", "my-skill")
    .send()
    .await?;

On this page