Docs Building Skills

Skills

Skills are the agent's toolbox: web search, PDF parsing, CRM queries, image generation, and anything else you install.

Skill vs Connect. They're often confused. A Skill is the capability (the verb: "search the web", "send a Slack message"). A Connect is the OAuth credential the capability needs to act as you on an external service. A web-search Skill needs no Connect; a Slack-post Skill needs a Slack Connect.

How Skills work operationally

  1. You install a Skill in your workspace (or it ships built-in).
  2. The Skill becomes available in every Chat and to every App that declares it in resources.skills.
  3. The agent reads the Skill's instructions (its SKILL.md) and knows when to call it.
  4. When the agent calls a Skill, you see the call inline in the conversation: name, arguments, result.
  5. 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 with is_global = true): a symlink at ~/.claude/skills/{name} points at the cache. Claude Code's default configDir picks them up automatically.
  • Per-app skills (declared in resources.skills but 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.

Skills are not MCP servers. An MCP server is a runtime process that exposes tools over the Model Context Protocol; Aitroop passes those to the runner via the separate 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_skillsapp_user_skills_custom
AuthorAitroop platformThe end user (you)
VersionSemver string seeded into the row (1.0.0, 1.1.0, …)Auto-incrementing integer, bumped on each publish
VisibilityAvailable to every workspaceAvailable only to the publishing user
Install stateapp_user_skills_enabled with optional is_global flagAlways 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

  1. Understand intent: asks 1-5 clarifying questions if your description has gaps.
  2. Design the App: constructs the full AppDef.
  3. Validate: every ID unique, every {{ref}} resolves, goals are specific.
  4. 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

  1. Click Skills in the sidebar.
  2. You see installed Skills (with their categories) and available-to-install Skills.
  3. 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

> what Skills do I have available?
> which Skills can search the web?
> is there a Skill for reading PDFs?

The agent enumerates Skills that match your question.

Installing a Skill

  1. Open Skills in the sidebar.
  2. Switch to the Library tab to see available Skills.
  3. Filter by category (research, productivity, data, integrations) or search by name.
  4. Click Install on a Skill.
  5. If the Skill requires a Connect (OAuth), you're prompted to authorize it. Authorize once; available everywhere.
  6. 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:

"resources": {
  "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:

SymptomLikely causeFix
"Skill not installed"The App declared a Skill that's not in your workspaceInstall it from Skills → Library
"Required Connect missing"The Skill needs Gmail/GitHub/etc. and you haven't authorizedAuthorize in Settings → Connects
Skill returned empty resultThe agent's arguments to the Skill didn't match real dataEdit the stage goal; be more specific about inputs
Skill timed outThe underlying API was slowRe-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:

  1. 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's resources.skills for app-scoped skills, and (c) any per-stage stage.skills override. Each entry becomes a SkillRef with { name, version, s3Prefix }.
  2. 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.
  3. 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.
  4. 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 nameWhat it does
mcp__aitroop__ask_questionSurface 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_taskRead 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_taskUpdate task fields: used by the App Builder skill to set draft App state during a save flow.
mcp__aitroop__get_asset_contentRead 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_imageOptional; 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.

This is what makes "skills" feel native. A user skill that needs to ask a clarifying question or attach a follow-up artifact doesn't have to invent its own protocol; it calls the platform's built-in MCP tools. The skill content can stay declarative (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

  1. Open Skills → Add custom.
  2. Pick MCP server.
  3. Enter the endpoint URL and any auth credentials.
  4. The platform fetches the server's tool list and presents it for review.
  5. You name the Skill, set its category, and approve.
  6. 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.

my-skill/
 ├─ 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.

curl -X POST https://app.aitroop.net/api/user-skills/my-skill/publish \
  -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/list handshake; check its logs.

Debug commands:

# List configured MCP servers and their status
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.