Skills
On this page
A skill is a reusable capability bundle - a focused chunk of prompt, an optional list of helper scripts, an optional list of required environment variables, and an optional list of tools the skill needs to function. Skills live in a per-organization catalogue in PostgreSQL and can be enabled on any number of agents, subject to the workspace allow-list described below. They are how Polyant keeps an instance’s identity short while letting operators bolt on situational expertise.
This page covers the skill data model, the immutable versioning scheme, the frontmatter contract, per-instance environment variables, and the runtime discovery loop that surfaces skills to the supervisor.
Database-first, never on disk
Skills are stored in PostgreSQL - there is no skills/ folder, no Markdown files in a workspace, no filesystem source of truth. Five tables back the catalogue:
skills- One row per skill (slug, name, description, category,organization_id,current_version_id, default flag, status). The slug is unique per organization, not globally.skill_versions- Immutable rows. Every save creates a new version with its ownversionstring,content(Markdown body),metadata,scripts(jsonb array of helper script blobs), and an optionalchangelog.skill_tools- Join table linking a skill to the framework tools it declares as required.organization_skills- Which platform-owned skills an organization has been granted.workspace_skills- Which of those a given workspace may actually draw on. Both gates must pass; see Two boundaries.
Per-instance enablement lives in instance_skills (which instance has which skill turned on, and pinned to which version). Per-instance, per-skill environment variables live in instance_skill_env - never in the global instance_secrets table.
Immutable versions, mutable pointer
Skills are append-only. Editing a skill’s body in the admin panel does not overwrite the previous content - it inserts a new skill_versions row and updates skills.current_version_id to point at it. Versions are semantic and assigned server-side: the first is 0.1.0, and every save bumps the minor (0.1.0 → 0.2.0 → 0.3.0). You do not pick the number, and there is no draft mode.
An agent is always pinned. The instance_skills row carries a non-nullable skill_version_id, set to whatever was current when the skill was enabled. There is no “follow the latest” mode: a new catalogue version reaches an agent only when someone upgrades that agent explicitly.
That is the point when the same skill is shared across a fleet: a fix that makes sense for one agent is rolled out per agent, and there is no implicit “everyone gets the latest” surprise.
Two boundaries: organization and workspace
Being in the catalogue is not enough for an agent to see a skill. Visibility is the intersection of two grants, evaluated on every use - when the prompt is built, when a skill is read, and when its script is executed:
- the organization must own the skill, or have been granted it from the platform catalogue, and
- the agent’s workspace must have it assigned.
Either missing and the skill stays out of the prompt and cannot be read or run. The check fails closed. A workspace created after this shipped starts with nothing assigned, and granting a skill to an organization does not assign it to any workspace - see What a workspace may draw on.
Frontmatter contract
A skill’s content is plain Markdown. If the body starts with ---\n…\n---, the leading block is parsed as YAML frontmatter. Only a small, stable set of fields is honoured. The parser itself collects every key it finds; it is the consumers that ignore what is not in this list, so an unrecognised field is silently inert rather than an error.
Honoured fields:
name- Short, human-friendly title. Surfaced in the admin UI.description- One-sentence summary. This is what the LLM sees when deciding whether to load the skill (see the discovery flow below). Keep it short, action-oriented, and specific.requiredTools- String array. Tools the skill needs. They are auto-enabled on any agent that turns the skill on - the agent’s tool set is recomputed from its pinned skill versions plus its manual additions. Declaring a tool the organization cannot use is rejected at save time, not flagged: once a plugin is revoked, editing a skill that still names one of its tools fails until the dependency is dropped.requiredEnv- Array of either strings (legacy shorthand) or objects of the form{ name, description?, sensitive }. Thesensitiveflag drives whether the admin UI masks the value (defaulttrue).autoLoad- Not a frontmatter field. It is a per-agent column oninstance_skills, set from the agent’s Skills section, so the same skill can be auto-loaded on one agent and not another. When it is on, the skill’s content is inlined into the system prompt’s available-skills block as<skill autoLoaded="true">…<content>…</content></skill>(rendered inpackages/engine/src/agents/supervisor/prompt.ts:190) and the LLM is instructed to follow its content directly without going throughreadSkill. Useful for mandatory greetings, regulatory disclaimers, or always-on behaviours.
Frontmatter fields that are not part of the contract - language, tags, version, anything else - are ignored. Don’t rely on them.
Example:
---
name: Lead Qualification
description: Triage an inbound lead against the knowledge base and book a discovery call
requiredTools:
- searchKnowledge
- scheduleTask
requiredEnv:
- name: CRM_PORTAL_ID
description: Numeric portal ID of the CRM this skill writes to
sensitive: false
- name: CRM_API_TOKEN
sensitive: true
---
# Lead Qualification
When a new contact arrives...Encrypted environment variables
Skill env vars declared in requiredEnv are stored in instance_skill_env, per agent and per skill. Sensitive values are AES-256-GCM encrypted with the engine’s ENCRYPTION_KEY; non-sensitive settings remain visible where the UI and skill output document them.
readSkill never returns a sensitive plaintext value directly. It exposes the opaque placeholder {{skill_env.<skill>.<KEY>}} instead. The model can copy that token into an eligible tool argument, and the runtime resolves it immediately after validation and immediately before the in-process tool executes. The engine records the placeholder in tool arguments, conversation history and audit arguments. Unknown or unavailable keys, non-sensitive keys written as placeholders, disabled skills and tools running outside the engine process stay unresolved.
Non-sensitive entries may be returned inline because the operator explicitly marked them as settings rather than secrets. runSkillScript follows a separate process boundary: it loads the skill environment only when executing and passes it directly to the child process environment. This controls engine injection, not what a tool can disclose: an in-process tool can return a resolved value, and a script that prints one exposes it in stdout or stderr as a tool result.
Skills declare their env requirements upfront so the framework can show the right fields to operators and filter skills with missing requirements. The declaration is an operator contract, not the runtime allow-list: placeholder resolution uses the sensitive rows stored for the served skill. A placeholder with no corresponding sensitive row stays unresolved.
Discovery and selection
Skills are not loaded into the system prompt en masse. Instead the supervisor sees a list of available skill descriptions rendered through the {{skillsList}} placeholder in the 05-skills section. That placeholder is the only thing the default 05-skills section contains - the surrounding instructions are yours to write. A useful shape is to tell the LLM to:
- Read the
<description>of each available skill. - If exactly one clearly matches the user’s intent, call
readSkillwith that skill’s name. - If multiple could match, pick the most specific one.
- If none clearly match, load nothing.
readSkill is a built-in tool that returns the skill’s full body at the version the agent is pinned to. The LLM then follows the returned instructions. The discovery flow keeps token usage low and lets the engine host hundreds of skills without ballooning the system prompt.
The list is also filtered: a skill whose requiredTools or requiredEnv are not satisfied on this agent is left out of it entirely, rather than offered and then failing when read.
Skills with autoLoad on for this agent bypass the readSkill round-trip and are inlined directly into the available-skills block - useful for hard-required behaviours like a mandatory greeting or a regulatory disclaimer.
How it works
+-----------------------------+ +-------------------------------+
| Org catalogue (Postgres) | | Per-instance binding |
| | | |
| skills | | instance_skills |
| |-- id, slug, name |<--------| |-- skill_id |
| |-- current_version_id | | |-- pinned_version_id? |
| | | |-- enabled |
| skill_versions | | |
| |-- skill_id | | instance_skill_env |
| |-- version (auto inc) | | |-- skill_id, key |
| |-- content (md+frontm.) | | '-- value (AES-256-GCM) |
| |-- scripts (jsonb) | +-------------------------------+
| '-- metadata (jsonb) | |
+-----------------------------+ |
| |
v v
+-----------------------------------+ +----------------------+
| supervisor prompt build | | readSkill tool |
| renders {{skillsList}} | | loads body |
| from instance_skills join | | emits placeholders |
+-----------------------------------+ +----------------------+
| |
+--------------+-------------------+
|
v
LLM picks one (or none),
copies placeholders to tools;
runtime resolves at execution.Code reference
packages/engine/src/skills/schema.ts-skills,skill_versions,skill_toolstables.packages/engine/src/skills/skills.service.ts- Global skill CRUD and version auto-increment.packages/engine/src/skills/skills.controller.ts- the/api/organizations/:orgSlug/skillsREST endpoints.packages/engine/src/skills/skills.store.ts- DB access layer.packages/engine/src/instances/skill-env.schema.ts/skill-env.store.ts- Per-instance encrypted env values.packages/engine/src/utils/frontmatter.ts- Frontmatter parser andrequiredEnvnormaliser.packages/engine/src/agents/supervisor/prompt.ts-loadSkillsList()renders the{{skillsList}}block;discoverSkills()is the step behind it that drops skills whose requirements are unmet.packages/engine/src/agents/tools/read-skill.tool.ts- ThereadSkilltool that loads a skill on demand.packages/engine/src/crypto/index.ts- AES-256-GCM encrypt/decrypt used for env values.
See also
- Tools
- Agents
- Architecture
- Write a skill
- Skill frontmatter - how a skill declares its required env
- Skills (admin panel) - assigning a skill and filling in its env