Skip to Content
Polyant is open source under AGPL-3.0 — star us on GitHub.
ConceptsSkills

Skills

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 global catalogue in PostgreSQL and can be attached to any number of instances. 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. Three tables back the catalogue:

  • skills — One row per skill (slug, name, description, category, current_version_id, default flag, status).
  • skill_versions — Immutable rows. Every save creates a new version with its own version string, content (Markdown body), metadata (parsed frontmatter), scripts (jsonb array of helper script blobs), and an optional changelog.
  • skill_tools — Join table linking a skill to the framework tools it declares as required.

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. The version string auto-increments (1, 2, 3, …), so every change leaves a complete trail.

Each instance can either:

  • Track the skill’s current version (the moving pointer) — the default.
  • Pin to a specific historical version.

This matters when the same skill is shared across a fleet: a fix that makes sense for the dental-office instance can be rolled out only when each operator promotes the new version. There is no implicit “everyone gets the latest” surprise.

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 deliberately rejects everything else to keep the contract tight.

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 assumes to be present. The framework warns when these are not enabled on the host instance.
  • requiredEnv — Array of either strings (legacy shorthand) or objects of the form { name, description?, sensitive }. The sensitive flag drives whether the admin UI masks the value (default true).
  • autoLoaded — Boolean. When true, the skill’s content is inlined into the system prompt’s available-skills block as <skill autoLoaded="true">…<content>…</content></skill> (rendered in packages/engine/src/agents/supervisor/prompt.ts:190) and the LLM is instructed to follow its content directly without going through readSkill. 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 inbound HubSpot contacts and book a discovery call requiredTools: - hubspotContact - scheduleTask requiredEnv: - name: HUBSPOT_PORTAL_ID description: Numeric portal ID, found in HubSpot settings sensitive: false - name: HUBSPOT_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-instance and per-skill, AES-256-GCM encrypted using ENCRYPTION_KEY from the engine’s environment. Values are decrypted at request time when the supervisor loads the skill (via the readSkill tool) and injected into the skill’s prompt scope only — they never leak into the user-visible system prompt, never appear in conversation history, and never show up in audit logs.

This is why skills must declare their env requirements upfront: the framework needs the contract to know which keys to surface to operators in the admin panel and which to inject at runtime. A skill that uses an env var without declaring it will fail closed (key not found) rather than silently expose the placeholder.

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. The default 05-skills prompt instructs the LLM to:

  1. Read the <description> of each available skill.
  2. If exactly one clearly matches the user’s intent, call readSkill with that skill’s name.
  3. If multiple could match, pick the most specific one.
  4. If none clearly match, load nothing.

readSkill is a built-in tool that returns the skill’s full body (current version, or the pinned version if the instance pins one). 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.

Skills marked autoLoaded in frontmatter 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

+-----------------------------+ +-------------------------------+ | Global 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 + env | | from instance_skills join | | decrypts at use | +-----------------------------------+ +----------------------+ | | +--------------+-------------------+ | v LLM picks one (or none), calls readSkill, follows it.

Code reference

  • packages/engine/src/skills/schema.tsskills, skill_versions, skill_tools tables.
  • packages/engine/src/skills/skills.service.ts — Global skill CRUD and version auto-increment.
  • packages/engine/src/skills/skills.controller.ts/api/skills REST 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 and requiredEnv normaliser.
  • packages/engine/src/agents/supervisor/prompt.tsdiscoverSkills() renders the {{skillsList}} block.
  • packages/engine/src/agents/tools/read-skill.tool.ts — The readSkill tool that loads a skill on demand.
  • packages/engine/src/crypto/index.ts — AES-256-GCM encrypt/decrypt used for env values.

See also

Last updated on