Skip to main content
A skill is a folder of files with a SKILL.md manifest at its root, attached to an agent, and loaded into the model’s context only when a task appears to need it. The point is that an agent can have twenty skills without paying for twenty skills on every request: what the model sees up front is a one-line description of each, and the full text arrives only after the model asks for it. That staging is called progressive disclosure, and in AgentArea it has three tiers rather than the usual two — because the third tier puts files on a disk, not text in a prompt.

The problem

Instructions do not compose. Everything an agent might need to know has to fit in one system prompt, so a prompt that covers ten procedures is ten times too long for the nine requests that need one of them. The cost is not only tokens: a model given ten procedures at once follows the wrong one more often than a model given the right one on its own. The naive fix — retrieve relevant instructions per request — trades a length problem for a retrieval problem, and adds an embedding index, a chunking policy and a similarity threshold to a system that had none of those. It also puts a ranker between the author and the model, so an author who writes a procedure cannot predict when it will be used. There is a second, less obvious problem. Procedures are rarely only prose. A procedure that says “run the migration checker” needs the migration checker to exist somewhere the agent can run it, and injecting a Python script into a prompt does not put it on a disk.

How AgentArea approaches it

The model decides, from a description it can read, whether to load a skill. No ranker, no embeddings. The decision is a tool call, so it is visible in the event stream and subject to the same policy gate as any other tool call.

Tier 1 — the catalog

When the agent’s config carries any skills, the workflow builds a SkillEntry per skill and appends a catalog block to the agent instruction. The block lists name and description only, under an ## Available Skills heading that tells the model to call activate_skill when a task matches one. At the same time an activate_skill tool is injected into the tool list. Its schema is not a free-form string: get_schema overrides the generated schema to constrain skill_name to an enum of exactly the available names, so the model cannot invent one. The catalog is appended to the instruction, not stored on the agent, so it is rebuilt on every run and reflects the skills attached at that moment. Nothing is disclosed unconditionally. The assembled tool list, activate_skill included, passes through filter_disclosed_tools against the task’s effective policy before the model sees it, and the workflow logs how many tools were withheld. See tool authorization.

Tier 2 — activation

The run’s configuration carries each skill’s name, description and package path, never its SKILL.md. The body is read when the model activates the skill, by activate_skill_activity, from the package recorded in the configuration: a catalog update between the start of the run and the activation does not change what the run reads. It is still gated: _gate_tool_call runs first, and a denied call never reaches the skill. The activity asks the install policy, reads SKILL.md from the skill’s package (a catalog package from the worker’s cache, keyed by its digest), copies the package into the sandbox, and writes the tool result straight into the task log at the position the workflow reserved for it. It returns only the entry’s size, so the body never enters Temporal history, whatever it weighs. The entry is the SKILL.md text wrapped in a <skill_content name="..."> element, followed by where the skill’s files are in the sandbox. Activation is idempotent per run: calling it twice returns “already active in this session” instead of a second copy of the text. An entry takes at most a tenth of the model’s context window (SKILL_CONTEXT_SHARE), the listing of the skill’s files included, and all of a run’s activated skills together take at most AGENTAREA_TASK_SKILLS_WINDOW_SHARE of it (30%). Sizes are counted with the tokenizer model calls are metered with. A larger SKILL.md is summarized: its size, its opening, and the path of its copy in the sandbox (or a read_tool_output id when there is no sandbox); a skill activated once the skills’ share is used up is not loaded, and the model is told why. Activated entries are never compacted, so the caps hold for the rest of the run. A refusal by the install policy, a skill that no longer exists, and a GitHub source that is gone come back to the model as a tool error naming the skill. Unreachable storage is waited out (see tasks). Activated skill content is then protected from context compaction. SkillContextGuard identifies these messages two ways — by tool name activate_skill, and by the <skill_content tag as a fallback — and the compaction boundary search skips any boundary that would drop one. Compaction otherwise triggers at 75% of the effective context window, which is itself the model’s window less a 15% reserve for output.

Tier 3 — materialization

Activation also copies the skill’s whole folder into the task’s sandbox workspace, under skills/<slug>-<hash8>/. From that point the agent reaches the skill’s scripts with an ordinary shell command. There is no separate “run skill script” tool, and that absence is the design: one execution path, not two. Three details of the layout are deliberate. The directory is keyed on a hash of the skill id, with the slug kept only for readability. Slugging the display name collapses distinct skills — “deploy_api” and “Deploy API” both reduce to deploy-api — and two skills sharing a directory means the agent reads one skill’s manifest and runs the other’s scripts. A skill that is only prose is still a folder: its package holds one file, SKILL.md. There is one kind of skill rather than a “content-only” second kind needing its own handling, and assemble_skill_bundle adds an empty SKILL.md to a package that has none. An unsafe path rejects the entire bundle. A path that is absolute, or contains .., raises rather than being skipped, because silently dropping one file can turn a valid skill into a subtly broken one and it violates the workspace’s atomic commit contract. Materialization failure is not fatal. The skill’s instructions still stand on their own, so the agent continues with a degraded skill rather than a dead task.

Where a skill lives at rest

A skill row is workspace-scoped with a workspace-unique slug, and holds metadata only: name, description, source_type (content, zip, github or path), source_url, and s3_path, the location of its package in object storage. Nothing writes the body to the database. A skill created from inline content is stored as a one-file package holding SKILL.md; activation reads SKILL.md from the package, and materialization copies the whole package. A skill installed from the catalog points at the catalog item’s package, which is immutable and stored under the digest of its files, and is pinned to the item’s version until a workspace admin updates it. Editing it copies the package into the workspace’s own prefix first. See registry and catalog. Skills are managed through the agentarea/skills toolset as well as REST, and the two are kept in parity by a contract test. That toolset lives in the agents library rather than in the API app specifically so that agents running in the worker can register it — see Limits.

Why not retrieval-augmented instructions

The obvious alternative is to embed every skill, retrieve the top-k for each request, and inject them. AgentArea does not, and the reasons are worth being concrete about. A retriever adds infrastructure that has to be right: an embedding model, a chunk size, a similarity threshold, and a re-index step every time a skill is edited. Each is a tuning surface that fails quietly — a threshold slightly too high silently stops loading a skill, and nothing in the transcript says so. The model already has the judgement being outsourced. It has read the task and the descriptions; asking it to pick is one tool call against a closed enum, which is a decision it makes well and, more importantly, a decision that appears in the event stream. A retrieval hit does not appear anywhere, so “why did it not use the deployment skill” has no answer. And retrieval only solves the prose half. Files still have to reach a disk, and once you have built the copy-into-the-workspace path for that, the model asking for a folder by name is a smaller mechanism than the model asking for chunks by similarity. The cost is real and should be stated. Selection quality depends entirely on how well descriptions are written, since the description is the only signal the model has. A skill with a vague description will not be picked, and there is no relevance score to inspect afterwards — only the presence or absence of an activate_skill call.

Limits

  • The catalog does not name a skill’s files. The model learns their names only after activation puts the folder in the sandbox.
  • The install policy is asked again at run time. A skill installed from a registry, or imported from a GitHub URL (the github registry, trust community), is not offered to a run once a workspace rule denies its registry or trust level; the run’s feed shows a skill.withheld event naming the skill and the rule, and activating it fails with the rule. This holds for skills installed before the rule was written. A run is held to the workspace’s rules only: a member’s own rules apply when they install, not to the runs of an agent, whoever starts them. Skills a workspace wrote itself have no registry and are not asked about, and an install whose registry or catalog item was deleted keeps working from its own package.
  • An early activation can block compaction entirely. The boundary search rejects any split that would drop protected skill content, and returns 0 — no compaction — when no safe boundary exists. A skill activated in the first iterations pins everything after it.
  • The activation registry is keyed by skill name. build_registry maps name to entry, so two attached skills with the same name collapse to one. The workspace uniqueness constraint is on slug, not on name.
  • Skills stored before packages existed are moved on first read. A row that still holds its body in the content column gets a package the first time anything reads it. Until then the toolset’s edit_content accepts only a single SKILL.md for it.
  • Authoring limits are enforced at the toolset, not the model. Inline creation caps at 200 files, 5 MB total, and path depth 10. GitHub import caps at a 25 MB repository archive, 200 files and 10 MB per package, 50 SKILL.md candidates, and 10 packages per import.
  • Skills are not advertised on the agent card. The card lists three generic capabilities — text-processing, and conditionally tool-execution and task-planning — and never enumerates attached skills. A remote A2A caller cannot discover them.
  • A failed toolset import is silent. The code tools loader imports every platform toolset module inside a try/except that logs at debug level, so a toolset that fails to import is absent from the registry with no error surfaced to the operator or the agent. This is why agentarea/skills was moved out of the API app into the agents library. The related claim that worker-run agents cannot reach agentarea_api.tools.* at all does not hold as stated: the worker declares agentarea-api as a dependency and those modules import cleanly in the workspace install. The residual risk is the silent skip, not a guaranteed absence.

What is an agent

How skills attach, and the configuration/policy line

Context strategies

The other half of what the model sees, and what gets offloaded

Sandbox sessions

The workspace a skill’s files are copied into

Tool authorization

The gate activate_skill clears like any other tool
Last modified on October 20, 2026