Skills
Think of Skills as the agent's toolbox. Reasoning gets the agent halfway there; Skills get it the rest of the way — they're what let the agent search the web, parse a PDF, query a CRM, generate an image, scrape a site, or build another App. The agent decides when to reach for one based on the task; you decide which ones are in the toolbox at all.
How Skills work operationally
- You install a Skill in your workspace (or it ships built-in).
- The Skill becomes available in every Chat and to every App that declares it in
resources.skills. - The agent reads the Skill's instructions (its
SKILL.md) and knows when to call it. - When the agent calls a Skill, you see the call inline in the conversation: name, arguments, result.
- If the Skill needs a Connect (e.g., a CRM skill needs Salesforce OAuth), the agent prompts you to authorize the first time.
You don't invoke Skills by name. The agent decides when to call them based on your request.
Anatomy of a Skill
A Skill is a versioned bundle of files — a SKILL.md at the root, plus any scripts, prompts, or data the skill references. Bundles live in S3 (under builtin/skills/{name}/{version}/ for platform skills and users/{userId}/skills/{name}/{version}/ for user-published ones) and are mirrored into each sandbox at run time.
name: web-search
title: Web Search
category: research
author: aitroop
description: Search the public web and fetch results.
---
# Web Search
Use this skill when the user wants up-to-date information,
news, or any data that requires looking at public web pages.
## When to use
- Recent news ("what's happening with X")
- Public company info
- Documentation lookup
## How to use
<instructions for the agent>
The frontmatter is metadata; the body is instructions for the agent. Aitroop reads name, title, description, category, icon, and author from the frontmatter to populate the platform catalogue (builtin_skills and app_user_skills_custom). The body describes when to use the Skill, how to use it, what its inputs/outputs look like, and common pitfalls.
How a Skill becomes available in a sandbox
Skills are surfaced through Claude Code's filesystem-based skill loader — not through a runtime registry, and not as MCP servers. The agent runner stages skills onto a couple of well-known paths inside the sandbox:
- Cache: the skill bundle is unpacked to
~/.aitroop/skills/{name}/{version}/. Versioned, immutable, reused across runs. - Global skills (the ones marked
category: "core"or installed withis_global = true): a symlink at~/.claude/skills/{name}points at the cache — Claude Code's defaultconfigDirpicks them up automatically. - Per-app skills (declared in
resources.skillsbut not core): symlinked under~/.aitroop/app_skills/{appId}/.claude/skills/and exposed to Claude Code via--add-dir. They're visible only for runs of that App.
When an App's stage overrides stage.skills, only that subset is symlinked for the stage — Per-stage scoping shrinks the toolbox without touching the workspace's installed set.
extraMCPServers channel on an agent request. Skills, by contrast, are static content the agent reads — there's no process to spawn, no protocol to negotiate, no tools/list handshake.Builtin vs. custom skills
Two stores back the catalogue:
builtin_skills | app_user_skills_custom | |
|---|---|---|
| Author | Aitroop platform | The end user (you) |
| Version | Semver string seeded into the row (1.0.0, 1.1.0, …) | Auto-incrementing integer, bumped on each publish |
| Visibility | Available to every workspace | Available only to the publishing user |
| Install state | app_user_skills_enabled with optional is_global flag | Always available to its owner; no separate install row |
A user-published skill of the same name as a builtin shadows the builtin for that user — the symlink resolution picks the user's version. That's the supported way to override a built-in skill's behaviour without forking the platform.
The built-in Skill: App Builder
Every Aitroop workspace has aitroop-app-create (the App Builder) installed by default. It's what turns conversations into saved Apps.
What it does
Translates a natural-language description of a workflow into a full AppDef JSON, validates the design, and saves it via POST /api/apps or PUT /api/apps/{id}.
Trigger phrases
- "Create an app that…"
- "Build me a workflow for…"
- "I want an app to…"
- "Make an automation that…"
- "Update my app…"
- "Save this as an app" — works mid-conversation to crystallize whatever the chat just did.
The 4 phases
- Understand intent — asks 1–5 clarifying questions if your description has gaps.
- Design the App — constructs the full AppDef.
- Validate — every ID unique, every
{{ref}}resolves, goals are specific. - Save via API — calls the platform's REST API and reports the App ID.
See Apps for the full walkthrough of each phase.
Finding which Skills you have
Three ways:
1. The Skills page
- Click Skills in the sidebar.
- You see installed Skills (with their categories) and available-to-install Skills.
- Click any Skill to see its full description, supported inputs, and any required Connects.
2. The chat status bar
Above the message input, the status bar shows the active Skill count ("Skills: 12 active"). Click for a list.
3. Ask the agent
> which Skills can search the web?
> is there a Skill for reading PDFs?
The agent enumerates Skills that match your question.
Installing a Skill
- Open Skills in the sidebar.
- Switch to the Library tab to see available Skills.
- Filter by category (research, productivity, data, integrations…) or search by name.
- Click Install on a Skill.
- If the Skill requires a Connect (OAuth), you're prompted to authorize it. Authorize once; available everywhere.
- The Skill is now active in your workspace.
Some Skills are workspace-wide (installed once for everyone), others are per-user. The Skill's description tells you which.
Declaring Skills on an App
Each App declares the Skills it needs in resources.skills:
"skills": ["web-search", "pdf-extract"],
"connects": []
}
When you build an App conversationally, the App Builder figures out which Skills the App needs and adds them automatically. You don't write the array by hand.
Why scope Skills per App
Restricting an App's available Skills (vs. letting it use all of them) has two operational benefits:
- Predictability. The App can only use the tools you've allowed. Behavior stays stable across runs and across users. Different teammates running the same App see consistent results.
- Speed. Fewer tools means less reasoning overhead per call. Stages that need only one or two Skills run noticeably faster.
Read-only vs write-capable Skills
Skills come in two flavors:
- Read-only Skills — fetch information without changing anything. Web search, PDF extraction, file reading.
- Write-capable Skills — modify external state. Sending email, creating CRM records, creating Calendar events. These are clearly marked in the Library.
Confirmation flow for writes
In Chat, the agent pauses before any write-capable action and shows you exactly what it's about to do. You can approve, modify, or cancel.
In an App, write Skills are approved at App-creation time and again at Schedule-creation time. They don't silently widen later.
What happens when a Skill fails
Common failure modes:
| Symptom | Likely cause | Fix |
|---|---|---|
| "Skill not installed" | The App declared a Skill that's not in your workspace | Install it from Skills → Library |
| "Required Connect missing" | The Skill needs Gmail/GitHub/etc. and you haven't authorized | Authorize in Settings → Connects |
| Skill returned empty result | The agent's arguments to the Skill didn't match real data | Edit the stage goal — be more specific about inputs |
| Skill timed out | The underlying API was slow | Re-run; the agent will retry automatically once |
How a skill physically gets into the sandbox
Skills don't live "in the workspace" as a single canonical install — every run materializes the subset it needs into a fresh sandbox. The path from S3 to a working ~/.claude/skills/{name} symlink is short but worth understanding:
- Resolution. When a turn starts, the server resolves the union of (a) the user's globally-installed skills marked
is_global = true, (b) the App'sresources.skillsfor app-scoped skills, and (c) any per-stagestage.skillsoverride. Each entry becomes aSkillRefwith{ name, version, s3Prefix }. - Presigning. For every
SkillRef, the server lists the S3 prefix and signs each object key with a 1-hour expiry GET URL. The signed URLs are bundled into the run-agent config — so the agent's host can download skill files without holding S3 credentials itself. - Caching. Inside the sandbox, the runner pulls each presigned URL into
~/.aitroop/skills/{name}/{version}/. Because the path is versioned, a second run that needs the same skill at the same version skips the download entirely — the directory is already there. - Symlinking. The runner then symlinks the cache into the location Claude Code expects:
~/.claude/skills/{name}for global skills, or~/.aitroop/app_skills/{appId}/.claude/skills/{name}for app-scoped ones (loaded via--add-dir). Symlinks are cheap to make and break, so per-stage skill scoping is a re-link, not a re-download.
The net effect: the agent reads skill files from the local filesystem the same way Claude Code does in any other context. No remote calls at runtime, no skill-server-side rate limits. The bottleneck is the first download of an unseen version, which the per-sandbox cache eliminates on repeat use.
Aitroop's own MCP server
Independently of user-installed skills, the platform ships a small built-in MCP server that every run-agent attaches to. It's not optional — it's how stages talk back to the platform without going through HTTP from inside the sandbox. The runner sees it as mcp__aitroop__* tools.
| Tool name | What it does |
|---|---|
mcp__aitroop__ask_question | Surface a question to the user mid-turn. Drives the ask_user event you see in the chat UI — backs the "agent needs clarification" flow described in Core concepts. |
mcp__aitroop__get_task | Read the current platform-side task structure (active App, stage being executed, prior artifacts). The agent uses it to know which stage of which App it's running. |
mcp__aitroop__update_task | Update task fields — used by the App Builder skill to set draft App state during a save flow. |
mcp__aitroop__get_asset_content | Read an artifact by ID (e.g. a file the user attached, or an earlier stage's CSV). Returns raw bytes; the agent can then parse them however it likes. |
mcp__aitroop__generate_image | Optional — only registered when the Gemini API key is configured. Generates images from a prompt and returns an artifact handle. |
Transport: HTTP, not stdio — the URL is {mcpServerURL}?session={conversationId}, and the agent treats it like any other MCP server in its mcpServers list. You don't configure this manually; the runner config is built with it pre-populated.
SKILL.md + scripts) because the interactivity is provided by the runtime.MCP server compatibility
Aitroop's runtime accepts any MCP-compliant server as a Skill source. If your internal tool already exposes an MCP endpoint, it can be plugged in without writing any glue code.
Operational flow for MCP
- Open Skills → Add custom.
- Pick MCP server.
- Enter the endpoint URL and any auth credentials.
- The platform fetches the server's tool list and presents it for review.
- You name the Skill, set its category, and approve.
- The MCP tools become callable by the agent.
Building your own Skill
Custom Skills are stored in two places, depending on origin:
app_user_skills_custom— a Skill you authored or imported into your workspace. Versioned, with an S3 prefix where the Skill's files live.app_user_skills_enabled— which built-in or installed Skills are active for you (since a workspace may have more available than you've actually turned on).
The shape of a custom Skill
A Skill is a directory with at minimum a SKILL.md at the root. The frontmatter declares identity, the body instructs the agent. Additional files (scripts, references, prompts) live alongside and can be loaded by the agent on demand.
├─ SKILL.md
├─ prompts/
│ └─ phase1.md
├─ scripts/
│ └─ extract.py
└─ references/
└─ api-spec.json
Publishing a new version
After editing your Skill, call POST /api/user-skills/:name/publish — it bumps the version, uploads the new artifact bundle to S3, and rolls forward any Apps that don't pin a version.
-H "Authorization: Bearer $AT_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "notes": "Added error handling for empty PDFs" }'
Pin an App to a specific Skill version if you want stability across upgrades. Otherwise the App always uses the latest published version on each run.
FAQ & troubleshooting
Why didn't the agent use a Skill I have installed?
Likely cause: the agent decided a different approach fits the task — usually reasoning from context instead of calling the tool.
Fix: tell it explicitly. "Use the web-search Skill for this." Or, for App stages, list the Skill in stage.skills so it's the only choice for that stage.
Can the agent use multiple Skills in one task?
Yes, and it usually does. A typical research task calls web-search several times, then pdf-extract on a downloaded report, then writes the summary. You see each call inline in the conversation with its arguments and result.
My MCP server boot failed.
Possible causes:
- The command isn't on the sandbox's
PATH(for command-launched MCP servers). - The HTTP endpoint is unreachable from the sandbox (for HTTP-transport MCP).
- Auth headers wrong or missing.
- The server crashed during the initial
tools/listhandshake — check its logs.
Debug commands:
GET /api/mcp/servers
# Ping a specific server
POST /api/mcp/servers/:key/ping
A Skill returned an empty result.
Most often: the agent's arguments to the Skill didn't match what the data actually looks like. Open the call in the run log and inspect the arguments. Tighten the stage goal with a more specific description of what to query.
Skill timed out.
The underlying API was slow. The agent retries once automatically; if both attempts fail, the stage fails. Increase timeout_ms on the stage, or wrap the Skill call in a more tolerant prompt: "if the call times out, proceed with the data you already have."
How do I share a custom Skill with my whole workspace?
Workspace admins can install Skills at the workspace level — visible to and usable by every member. Per-user Skills install only for you. The Skill's settings page has the visibility toggle.
Can I write a Skill in something other than markdown?
The SKILL.md is required (it's how the agent learns when/how to use the Skill). But the body can reference scripts, datasets, or schemas in any format. The agent loads the referenced files on demand at runtime, into the same sandbox where it's executing.