Documentation source
Skills
Reusable instruction modules that agents load on demand, with CRUD management, markdown import, agent assignment, and prompt injection.
# Skills
Skills are reusable instruction sets that agents can load dynamically during a conversation. Rather than embedding every possible instruction in an agent's system prompt, skills allow instructions to be modular, maintainable, and shared across agents. An agent sees a skill index in its system prompt and uses the `loadSkill` tool to fetch detailed instructions when a task requires them.
## Overview
The module lives in `features/skills/` and provides database-backed skill management, a `loadSkill` AI SDK tool for runtime loading, prompt injection utilities, markdown import/export, and admin UI for CRUD operations. Skills can be global (available to all tenants) or tenant-scoped.
Amble uses two related skill concepts:
- **Runtime skills** are rows in the `skills` table. They are tenant/global runtime configuration loaded by in-app agents through `loadSkill`, assigned to agents through `agent_skills`, and eventually bound to actions, integrations, workstreams, SOPs, goals, and task entities.
- **Developer skills** are repository or workstation instruction packs such as `SKILL.md` files used by coding agents. They guide development work, tests, reviews, and docs updates, but they are not the canonical runtime source for tenant automation.
The boundary is intentional: developer skills improve how Amble is built; runtime skills improve how Amble executes customer workflows.
## Key Concepts
**SkillRecord** -- The database record for a skill:
- `slug` -- unique identifier (lowercase, alphanumeric + hyphens, 2-64 chars)
- `name` -- human-readable display name
- `description` -- optional summary shown in the skill index
- `instructions` -- the full instruction text loaded into agent context
- `category` -- organizational grouping (e.g., "analysis", "writing", "general")
- `source` -- how the skill was created ("manual", "imported", etc.)
- `metadata` -- arbitrary JSON for additional configuration
- `user_invocable` -- whether users can request this skill directly in chat
- `enabled` -- toggle to disable without deleting
- `tenant_id` -- null for global skills, set for tenant-specific ones
**SkillManifestV1** -- A typed execution contract stored at `skills.metadata.manifest`, parsed by `features/skills/manifest.ts`. The manifest does not execute anything by itself. It declares the capabilities and relationships a skill needs so readiness, UI, and future runners can make permission-checked decisions.
Manifest fields:
- `version` -- literal `1`; unsupported versions are ignored by safe metadata helpers.
- `requiredTools` -- tool slugs the skill expects to use.
- `requiredToolGroups` -- tool groups the skill expects to be available.
- `forbiddenTools` -- tools the skill should not be given even if generally available.
- `requiredIntegrations` -- integration ids, optional connection presets, and required resources.
- `actions` -- action slugs and the intended mode: `manual`, `trigger`, `queue`, or `goal_loop`.
- `deterministicSteps` -- governed tool/script/integration-sync steps that run before/after agent work, on approval, or on schedule.
- `delegation` -- agent slugs, roles, and required outputs for multi-agent fanout.
- `humanGates` -- approval or elicitation checkpoints such as before-run, before-write, after-draft, and low-confidence review.
- `goalLoop` -- optional goal-loop defaults including cadence, success criteria, stop conditions, and supervised/autonomous review mode.
- `evals` -- trigger cases, functional scenarios, success criteria, and tracked metrics for skill optimization.
- `bindings` -- explicit typed relationships to agents, tools, actions, integrations, workstreams, SOPs, task entities, goals, entity types, views, and governed scripts.
- `interop` -- external exposure and export hints for MCP/API/skill-pack compatibility.
A concrete manifest stored at `skills.metadata.manifest`:
```jsonc
{
"version": 1,
"requiredToolGroups": ["entities", "research"],
"requiredTools": ["webSearch"],
"forbiddenTools": ["deleteEntity"],
"requiredIntegrations": [
{ "integrationId": "slack", "requiredResources": ["channel:deals"] }
],
"actions": [
{ "actionSlug": "draft-deal-memo", "mode": "manual", "required": true }
],
"humanGates": [
{ "key": "review-before-send", "label": "Review before send", "when": "before_write", "required": true }
],
"goalLoop": { "defaultReviewMode": "supervised", "checkInCadence": "daily" },
"evals": {
"triggerCases": ["draft a deal memo for a portfolio company"],
"trackedMetrics": ["human_acceptance_rate", "task_success_rate"]
},
"interop": {
"exposeViaMcp": true,
"exportFormats": ["amble", "claude-skill", "openai-gpt"],
"externalSafeSummary": "Drafts a structured deal memo from a company record."
}
}
```
**SkillManifestSummary** -- A flattened, render-safe projection of a manifest produced by `summarizeSkillManifest()` in `features/skills/lib/manifest-summary.ts`. It carries the trigger cues, capability requirements, execution/governance counts, and interop flags without dragging the full manifest (or skill instructions) onto the prompt hot path or into list payloads. `serializeSkill()` / `serializeSkills()` (`features/skills/lib/skill-serialization.ts`) attach `manifest` + `manifestSummary` to a `SkillRecord` so the admin UI and API consumers reason about a skill without re-parsing the raw jsonb.
**SkillFileRecord** -- Skills can have associated files stored via `skill_files` for reference material.
**Skill Resolution** -- When looking up a skill by slug, tenant-specific skills take priority over global ones. This allows tenants to override global skills with customized versions.
**Agent-Skill Assignment** -- Skills are linked to agents via the `agent_skills` join table with a `priority` field. Higher-priority skills appear first in the skill index.
**Skill Bindings** -- Skills can reference existing Amble primitives without becoming a new executor:
- `actions` -- skills declare which action slugs they execute or recommend; action rows still own triggers, queue behavior, output contracts, and session creation.
- `integrations` -- skills declare required integration ids/resources; readiness checks verify connections and sync freshness.
- `workstream` entities -- skills support a workstream and show automation coverage for that operating area.
- `sop` entities -- skills implement or automate a human procedure while preserving gates.
- `goal` entities -- skills run inside goal loops as reusable methods for repeated progress.
- `task` entities -- skills create, update, or support work-model task entities through existing action/session flows.
These bindings may live in `skills.metadata.manifest` first and can later graduate to a normalized `skill_bindings` table when relation UI, reverse lookup, or analytics need indexed rows.
## How It Works
### Skill Index Injection
When an agent's system prompt is built, the `buildSkillIndex()` function generates a formatted list of available skills:
```
## Available Skills
You have access to the following skills. Use the `loadSkill` tool with the skill's slug to load its detailed instructions when needed.
- **Data Analysis** (`data-analysis`) -- Structured approach to analyzing datasets
- **Report Writing** (`report-writing`) -- Professional report formatting guidelines
```
This index tells the agent what skills exist without loading the full instructions. The agent decides when to load a skill based on the current task.
**Progressive disclosure.** When a skill carries an operating-layer manifest, `buildSkillIndex()` appends a compact, indented block (via `buildSkillManifestPromptLine()`) so the agent can decide whether to `loadSkill` without the instructions entering context:
```
- **Deal Memo** (`deal-memo`) -- Drafts a structured deal memo
- when to use: draft a deal memo for a portfolio company
- needs: tools[webSearch] · groups[entities, research] · integrations[slack]
- review: 1 human gate · supervised
```
Legacy skills with no manifest keep their original one-line entry — the block is omitted when the summary has nothing to surface.
### Runtime Loading
The `createLoadSkillTool(tenantId)` function creates an AI SDK tool that agents call to load skill instructions:
1. Agent calls `loadSkill({ slug: "data-analysis" })`
2. The tool queries the `skills` table for the slug (checking both global and tenant-scoped)
3. Returns the skill's name and full instructions
4. Records the invocation in `skill_invocations` (fire-and-forget)
The agent then incorporates the returned instructions into its reasoning for the current task.
### Manifest Parsing
`features/skills/manifest.ts` defines `SkillManifestV1Schema`, `parseSkillManifest()`, `safeParseSkillManifest()`, `getSkillManifestFromMetadata()`, and `getSkillManifestFromSkill()`.
Safe metadata helpers return an empty V1 manifest when `skills.metadata.manifest` is absent, malformed, or uses an unsupported version. That keeps older skills loadable while letting new code rely on a typed contract when a manifest is present.
### Runtime Contract
A skill may request tools, actions, agents, and integrations, but it never bypasses platform permissions. Tool resolution, RBAC, API-key scopes, action readiness, approval policy, and human gates still decide what can actually run.
Skills also do not create a parallel workflow engine. Executable work continues to flow through `actions`, `sessions`, `session_events`, and `entity_responses`; skills package the operating knowledge and capability contract around those primitives.
### Readiness
`resolveSkillReadiness({ manifest, tenantId, tenantSlug })` in `features/skills/server/skill-readiness.ts` answers a single question: _can this skill's manifest actually run in this tenant right now?_ It resolves, read-only:
- **Required tools** -- present in the tenant-scoped tool registry (`getTool`).
- **Required tool groups** -- non-empty in the resolved group map (`toToolGroupMap`).
- **Required integrations** -- a known descriptor that reports `connection.configured` via `getIntegrationOverview`.
- **Required actions** -- only those marked `required: true` are checked; an `actions` row exists for the tenant (or a global row).
- **Human gates** -- surfaced as informational `ok` ("manual gate") so the operator sees what will pause execution.
Each check is `ok | missing | unknown` (`unknown` when verification itself failed, never a silent pass). The report aggregates `ready` (true only when zero `missing`), per-status `counts`, and a `blockers` list. It **never executes** anything — it reads the registries and the `actions` table. Exposed admin-only at `GET /api/skills/[id]/readiness`; the admin UI renders it through `SkillReadinessPanel` + the `useSkillReadiness` hook.
### Export & Interop
`exportSkillPack(skill, format)` in `features/skills/lib/skill-export.ts` produces a portable pack in one of the `SkillExportFormat` values:
- `amble` -- full JSON (slug, name, description, category, instructions, manifest) for round-tripping between Amble tenants.
- `markdown` / `claude-skill` -- a `SKILL.md` with YAML frontmatter (`claude-skill` uses the `<slug>/SKILL.md` path convention).
- `openai-gpt` / `cowork` -- external-safe markdown that prefers `interop.externalSafeSummary` over raw instructions.
When the manifest declares `interop.exportFormats`, that list is an allowlist — requesting a non-enabled format throws. `toExternalSafeSkill(skill)` produces the redacted projection (capabilities, triggers, external summary, `exposeViaMcp`) used by external/MCP callers so internal instructions never leak. Served admin-only at `GET /api/skills/[id]/export?format=markdown`.
### Goal-Skill Bridge
A goal entity's spec may carry a `skills: string[]` field (see `goalSystemSpecSchema` in `features/entities/lib/goal-system-loop.ts`). The goal stays the **stateful source of progress**; the referenced skills are the reusable **operating method**. `resolveGoalSkillReadiness({ spec, tenantId, tenantSlug })` in `features/skills/server/goal-skill-bridge.ts` maps each slug to its manifest summary + readiness report so a goal surface can show whether its method can actually run. Missing slugs resolve to `found: false` rather than throwing (a stale reference still renders). The bridge **never executes** a skill — execution stays with actions/sessions per the core-loop rules.
### Markdown Import
Skills can be imported from markdown files with YAML frontmatter:
```markdown
---
slug: data-analysis
name: Data Analysis
description: Structured approach to analyzing datasets
---
Follow these steps when analyzing data:
1. Understand the data structure
2. Identify key metrics
3. Look for patterns and anomalies
...
```
The `importFromMarkdown()` function parses the frontmatter (using a built-in YAML parser that handles block scalars, inline lists, and nested mappings), validates the slug format, and returns a `SkillImportResult` ready for database insertion.
### Eval Optimization Loop
A skill's instructions are a prompt-backed operating method — they deserve the
same eval-driven iteration software gets from tests, CI, and code review. Skills
stay **standalone runtime primitives** (the canonical artifact is
`skills.instructions`; ADR-0045): optimization is a typed lens over the existing
Action / Session / Eval primitives, **not** a new executor, scorer, or artifact
store. Entity projection/catalog of skills, where present, is visual/governance
only — it never owns a skill's source of truth.
#### The declared contract (`manifest.evals`)
A skill declares what to measure and how to improve in its manifest:
- `triggerCases`, `functionalScenarios`, `successCriteria` — the cases a candidate is graded against.
- `trackedMetrics` — runtime optimization targets (precision, acceptance, cost, …) observed from real sessions.
- `criteriaSetRefs` — references to existing `criteria_sets` whose rating dimensions become the rubric (reuse over re-declaration).
- `optimization` — the improvement + promotion policy: `reviewMode` (supervised | autonomous), `candidateStrategy` (manual | llm_rewrite | dspy_bootstrap), baseline/current candidate slugs, `maxIterations`, and a `promotion` gate (`human_gate` default, or `autonomous_threshold` which **requires** an explicit `minOverallScore`).
All fields are additive and optional — legacy skills with no `optimization`
block parse unchanged and default to supervised, human-gated improvement.
#### The eval bridge (no parallel result family)
`features/skills/lib/skill-optimization.ts` converts the contract into the
**existing** eval inputs, mirroring the knowledge-artifact harness
(`features/evals/knowledge/`):
- `deriveSkillCriteriaDimensions(contract)` → `CriteriaSetDimension[]` (snake_case keys, rating scale `[1,5]`) — the same dimensions feed both scoring paths.
- `scoreSkillCandidate(instructions, contract)` → `RubricScoreResult` — a deterministic, CI-safe structural scorer (coverage of declared cases + specificity). Same shape the LLM judge emits, so it flows through every surface that already reads `RubricScoreResult`.
- `buildSkillJudgeOptions(...)` → `JudgeOptions` for the existing `runRubricJudge` (`features/evals/lib/llm-judge.ts`) — the live qualitative path over the same dimensions. No second judge.
- `evaluateSkillCandidates(...)` scores the baseline + every candidate, diffs each against the baseline, ranks them, picks the best **changed** improvement, and surfaces the remaining failed dimensions as the next gaps.
#### The runtime seam
`runSkillOptimization(params)` (`features/skills/server/skill-optimizer.ts`)
starts a run on the existing session runtime: it mints (or reuses a parent)
session, writes `skill.optimization.*` events to `session_events`, and returns
candidate diffs + scores + the promotion gate. **It never mutates canonical
instructions** — a run only proposes. It is dispatchable as the `optimize-skill`
ActionDefinition, so it rides the same `action-tick` / goal-loop spine every
other action uses (autonomous goal loops add `optimize-skill` to a goal's
`skills[]`).
Promotion is the only path that writes `skills.instructions`, and it is always
gated: `promoteSkillCandidate(...)` fails closed unless **either** a human
approval id is supplied **or** the contract's autonomous-threshold policy is
explicitly declared and the candidate's score clears it
(`isPromotionAutonomouslyAllowed`). System skills (`tenant_id = null`) cannot be
promoted through a tenant optimization run.
This mirrors Amble's broader loop: skill usage creates sessions and feedback;
the eval bridge scores candidates against the contract; the gated promotion path
updates instructions; the next run starts with better operating knowledge.
#### Seeing the loop — `OptimizationLoopSummary`
The optimization loop is made visible by a small, reusable card —
`OptimizationLoopSummary` (`features/loops/components/optimization-loop-summary.tsx`) —
that renders the loop as **six plain-language setup stages plus a derived "Next
gaps" to-do**, the same way a CI pipeline shows its steps:
| Stage | For a skill, read from… |
| -------------------- | -------------------------------------------------- |
| **Target** | the skill itself |
| **Criteria** | `evals.successCriteria` |
| **Cases & evidence** | `evals.triggerCases` + `evals.functionalScenarios` |
| **Candidates** | `evals.baselineSkillSlug` (the comparison anchor) |
| **Scores** | `evals.trackedMetrics` |
| **Promotion** | `goalLoop.defaultReviewMode` + `humanGates` |
| _Next gaps_ (output) | derived — the next action for each stage not yet ready |
The plain-language mental model is **improve with evals → review the candidate →
promote the approved version**. Each setup stage shows an honest status: `ready`,
`partial` (declared but not yet complete — e.g. metrics tracked but not all
measured, or a baseline named but unscored), or `empty`. Coverage (the `N/6`
badge) counts only the six setup stages; **Next gaps** is the loop's derived
output, not a counted stage. **Nothing is fabricated** — a skill with no eval
setup shows empty stages and a "Next gaps" checklist of what to add, not a fake
score. The card surfaces in two places, both zero-fetch (the serialized skill
already carries its manifest):
- **Skill detail → Overview tab** — the full card
(`features/skills/components/skill-detail-client.tsx`).
- **Command Center → Skills Library** — a compact `Evals N/6` coverage badge on
each skill card, shown only when the skill declares a real eval contract
(`skillHasEvalContract` — criteria, cases, metrics, or a baseline; never
promotion/goal-loop alone) (`features/custom/workspaces/command-center/pages/skills-catalog.tsx`).
The card is **source-agnostic**: it consumes an `OptimizationLoopView`, not a
skill. The skill projector is `deriveSkillOptimizationLoop()`
(`features/skills/lib/skill-optimization-view.ts`); the generic view model and
`buildOptimizationLoopView()` live in the platform loop engine — see the
[Loops](/docs/features/loops#optimization-loop-view-the-universal-lens) doc for
projecting any entity, proposal, SOP, or knowledge record through the same model.
### Admin Management
Skills are managed via the Admin > Skills tab:
- **SkillAdmin** -- top-level admin component
- **SkillList** -- lists all skills with enable/disable toggles and edit/delete actions
- **SkillEditor** -- form for creating and editing skills (slug, name, description, instructions, category, user-invocable toggle)
- **SkillImport** -- file upload for importing markdown skill files
### User-Facing Skill Library
The Skill Library at `/skills` is a first-class, permission-gated surface that makes the skill catalog visible and usable by non-admin team members.
#### Browse
The library renders a searchable card grid. Each card shows the skill name, description, category, and capability badges derived from its manifest (required tools, integration count, human gates, goal-loop flag). A scope tab bar filters by **All**, **Platform** (curated/global skills), or **Team** (tenant-authored skills), derived from `skill.source` via `isCuratedSkillSource()`. A category dropdown narrows further.
#### Run
The "Run" button on any skill card or detail sheet seeds the chat composer via `useChatDock().openCompose` — prefilled with a "use the {name} skill" prompt. There is no separate run endpoint; execution flows through the existing chat → session → agent path.
#### Detail and Document Mode
Clicking a card opens a detail sheet rendered in document mode with structured sections:
- **Capabilities** -- checklist of required tools, tool groups, integrations, and human gates drawn from the skill manifest
- **Instructions** -- the full `instructions` field rendered as markdown
- **Governance** -- `enabled`, `user_invocable`, `source`, category, and creation metadata
- **References** -- linked entity records and documents attached to this skill (see below)
#### Inline Edit
Users with `skills.team.update` (or `skills.own.update` for skills they authored) see an inline editor in the detail sheet:
- **Frontmatter form** -- editable name, category, description, and toggles (`user_invocable`, `enabled`) with autosave
- **Body editor** -- full markdown edit of the `instructions` field
Skills with a curated or platform `source` (`"curated"`, `"imported"`, `"system"`) are locked in the library UI — the edit controls are replaced by a "Duplicate to edit" affordance, matching the lock enforced by `features/skills/server/access.ts`.
Users without write permissions see a read-only view with no edit controls.
#### References
The References panel lets team members attach existing entity records or documents to a skill as context material. Attachments are stored in `skill_bindings` with `target_kind = 'entity' | 'document'` and `relationship_type = 'references'`. This reuses the existing bindings table extended by migration `20260616000000` — no new table.
#### RBAC
The Skill Library introduces two permission scopes:
| Permission scope | Actions | Who it applies to |
| --- | --- | --- |
| `skills.team.*` | `read`, `run`, `create`, `update`, `delete` | Any user acting on any skill in the tenant |
| `skills.own.*` | `read`, `create`, `update`, `delete` | Applies to skills the user authored (`created_by = userId`) |
Default role grants (applied by migration `20260616000000`):
| Role | Team permissions | Own permissions |
| --- | --- | --- |
| `system_admin` / `tenant_admin` | all | all |
| `editor` | read, run, create, update, delete | all |
| `member` | read, run | read, create, update, delete |
| `viewer` / `guest` | read | read |
`features/skills/server/access.ts` enforces these checks for all skills API writes. The admin gallery at `/admin/skills` remains the authoring/management surface and continues to require admin permissions.
## API Reference
### Server Actions
| Function | Location | Purpose |
| ------------------------------------------------- | ----------------------------------- | --------------------------------------- |
| `listSkills()` | `features/skills/server/actions.ts` | List all skills (global + tenant) |
| `getSkill(id)` | Same | Fetch skill by ID |
| `getSkillBySlug(slug)` | Same | Fetch by slug (tenant-first resolution) |
| `createSkill(input)` | Same | Create a new skill |
| `updateSkill(id, input)` | Same | Update skill fields |
| `deleteSkill(id)` | Same | Delete skill (tenant-scoped) |
| `getAgentSkills(agentId)` | Same | List skills assigned to an agent |
| `assignSkillToAgent(agentId, skillId, priority?)` | Same | Link a skill to an agent |
| `removeSkillFromAgent(agentId, skillId)` | Same | Unlink a skill from an agent |
### Tool Factory
| Function | Location | Purpose |
| ------------------------------- | ------------------------------------- | ------------------------------ |
| `createLoadSkillTool(tenantId)` | `features/skills/lib/skill-loader.ts` | Create AI SDK `loadSkill` tool |
### Prompt Utilities
| Function | Location | Purpose |
| ------------------------- | ------------------------------------- | --------------------------------------------- |
| `buildSkillIndex(skills)` | `features/skills/lib/skill-prompt.ts` | Format skill list for system prompt injection |
### Manifest Summary, Serialization, Readiness & Export
| Function | Location | Purpose |
| --------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------ |
| `summarizeSkillManifest(manifest)` | `features/skills/lib/manifest-summary.ts` | Flatten a manifest to a render-safe `SkillManifestSummary` |
| `summarizeSkillMetadata(metadata)` | Same | Summarize straight from a skill row's `metadata` jsonb |
| `buildSkillManifestPromptLine(summary)` | Same | Compact progressive-disclosure block for the prompt index |
| `serializeSkill(skill)` | `features/skills/lib/skill-serialization.ts` | Attach `manifest` + `manifestSummary` to a `SkillRecord` |
| `serializeSkills(skills)` | Same | List form of `serializeSkill` |
| `resolveSkillReadiness(params)` | `features/skills/server/skill-readiness.ts` | Read-only readiness report for a manifest in a tenant |
| `resolveGoalSkillReadiness(params)` | `features/skills/server/goal-skill-bridge.ts` | Resolve manifest summary + readiness for a goal's `skills[]` |
| `exportSkillPack(skill, format)` | `features/skills/lib/skill-export.ts` | Build a portable skill pack in a `SkillExportFormat` |
| `toExternalSafeSkill(skill)` | Same | Redacted projection for external/MCP callers |
### HTTP API Routes
| Route | Auth | Purpose |
| ------------------------------------------------ | ---------------------------- | -------------------------------------------------- |
| `GET /api/skills` | user / key | List skills, serialized with manifest summaries |
| `GET /api/skills/[id]` | user / key | Fetch one skill, serialized |
| `POST /api/skills` | `skills.team.create` | Create a skill (permission-gated, was admin-only) |
| `PATCH /api/skills/[id]` | `skills.team.update` or own | Update a skill (permission-gated, was admin-only) |
| `DELETE /api/skills/[id]` | `skills.team.delete` or own | Delete a skill (permission-gated, was admin-only) |
| `GET /api/skills/[id]/bindings` | user / key | List bindings for a skill |
| `POST /api/skills/[id]/bindings` | `skills.team.update` or own | Attach a reference binding to a skill |
| `DELETE /api/skills/[id]/bindings/[bindingId]` | `skills.team.update` or own | Remove a binding |
| `GET /api/skills/[id]/readiness` | admin | Resolve operating-layer readiness |
| `GET /api/skills/[id]/export?format=<fmt>` | admin | Download a skill pack in the requested format |
### Access Guard
| Function / Export | Location | Purpose |
| -------------------------------------------- | ------------------------------------- | -------------------------------------------------------------- |
| `checkSkillAccess(skillId, action, ctx)` | `features/skills/server/access.ts` | Permission-gated gate for skill read/run/create/update/delete |
| `CURATED_SKILL_SOURCES` | Same (re-export from `skill-source`) | Constant: source values that lock a skill against user edits |
| `isCuratedSkillSource(source)` | `features/skills/lib/skill-source.ts` | Predicate used by library UI and access guard |
### Manifest
| Function | Location | Purpose |
| ---------------------------------------- | ----------------------------- | -------------------------------------------------------------- |
| `SkillManifestV1Schema` | `features/skills/manifest.ts` | Zod V4 schema for `skills.metadata.manifest` |
| `parseSkillManifest(input)` | Same | Strict parser for callers that want errors |
| `safeParseSkillManifest(input)` | Same | Non-throwing parser for validation/readiness flows |
| `getSkillManifestFromMetadata(metadata)` | Same | Safe helper for `skills.metadata.manifest` with empty fallback |
| `getSkillManifestFromSkill(skill)` | Same | Convenience helper for skill-like records |
### Optimization
| Function | Location | Purpose |
| --------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------- |
| `deriveSkillCriteriaDimensions(contract)` | `features/skills/lib/skill-optimization.ts` | Convert an eval contract to `CriteriaSetDimension[]` (shared by both scoring paths) |
| `scoreSkillCandidate(instructions, contract)` | Same | Deterministic, CI-safe structural score → `RubricScoreResult` |
| `evaluateSkillCandidates(params)` | Same | Score baseline + candidates, diff, rank, pick best, surface gaps |
| `isPromotionAutonomouslyAllowed(contract, s)` | Same | Fail-closed promotion-safety predicate |
| `buildSkillJudgeOptions(params)` | Same | Assemble `JudgeOptions` for the existing `runRubricJudge` |
| `runSkillOptimization(params)` | `features/skills/server/skill-optimizer.ts` | Start a run on the session runtime; proposes candidates, never mutates |
| `promoteSkillCandidate(params)` | Same | The sole, gated writer of `skills.instructions` |
| `optimize-skill` ActionDefinition | `features/actions/library/builtins/optimize-skill.ts` | Dispatch a run via the action-tick / goal-loop spine |
### Import
| Function | Location | Purpose |
| ----------------------------- | ---------------------------------- | -------------------------------------------- |
| `importFromMarkdown(content)` | `features/skills/server/import.ts` | Parse markdown file into `SkillImportResult` |
### Types
| Type | Location | Purpose |
| ------------------- | ---------------------------------- | ----------------------------- |
| `SkillRecord` | `features/skills/types.ts` | Full skill database record |
| `CreateSkillInput` | Same | Creation input shape |
| `UpdateSkillInput` | Same | Partial update input |
| `SkillFileRecord` | Same | Skill file attachment |
| `SkillImportResult` | `features/skills/server/import.ts` | Parsed markdown import result |
| `SkillEvalContract` | `features/skills/manifest.ts` | `manifest.evals` contract (cases, metrics, criteria refs, optimization) |
| `SkillOptimizationPolicy` | Same | Improvement + promotion policy block |
| `SkillPromotionPolicy` | Same | Promotion gate (`human_gate` \| `autonomous_threshold` + threshold) |
| `SkillOptimizationEvaluation` | `features/skills/lib/skill-optimization.ts` | Baseline + ranked scored candidates + gaps |
| `SkillOptimizationRunResult` | `features/skills/server/skill-optimizer.ts` | Result of a run (proposes; `promoted: false`) |
### Components
**Admin surface** (`features/skills/components/`):
| Component | Purpose |
| --------------------- | -------------------------------------------------- |
| `SkillAdmin` | Top-level admin panel |
| `SkillList` | Skill listing with enable/disable, edit, delete |
| `SkillEditor` | Create/edit form |
| `SkillImport` | Markdown file import |
| `SkillManifestEditor` | Structured editor for the operating-layer manifest |
| `SkillReadinessPanel` | Renders the readiness report for a saved skill |
**User-facing Skill Library** (`features/skills/components/library/`):
| Component | Purpose |
| ------------------------ | ------------------------------------------------------------------------- |
| `SkillLibrary` | Top-level library page: search, scope tabs, category filter, card grid |
| `SkillLibraryCard` | Single skill card with capability badges and Run / View actions |
| `SkillManifestBadges` | Compact manifest capability chips (tools, integrations, gates, goal-loop) |
| `SkillDocumentView` | Full detail sheet: Capabilities, Instructions, Governance, References |
| `SkillFrontmatterForm` | Inline edit form for name, category, description, and toggle fields |
| `SkillReferencesPanel` | Lists and manages reference bindings on a skill |
| `SkillReferenceAttach` | Combobox for attaching an entity record or document as a reference |
| `skill-library-helpers` | Pure helpers: tab derivation, category list, card sort (`library/skill-library-helpers.ts`) |
| `skill-document-helpers` | Pure helpers: section extraction, lock detection (`library/skill-document-helpers.ts`) |
| `skill-references-helpers` | Pure helpers: binding shape normalization (`library/skill-references-helpers.ts`) |
## Design Decisions
**Run via chat composer, not a dedicated endpoint.** The Run button seeds `useChatDock().openCompose` with a prefilled prompt rather than calling a new `/api/skills/[id]/run` endpoint. This keeps all execution in the existing chat → session → agent pipeline, avoids a parallel run surface, and lets users see the agent's plan before it executes.
**Curated/platform lock is enforced at the UI layer.** Skills whose `source` matches `CURATED_SKILL_SOURCES` (`"curated"`, `"imported"`, `"system"`) show a "Duplicate to edit" state in the library, not just a 403 from the server. Early UI feedback prevents the user from composing changes that will be rejected.
**`isCuratedSkillSource` lives in a client-safe module.** The predicate is in `features/skills/lib/skill-source.ts` (no server imports) so the library card and document view can use it without pulling server-only modules into the client bundle. `features/skills/server/access.ts` re-exports the same constant for server-side enforcement — one definition, two use sites.
**References reuse `skill_bindings`, not a new table.** Attaching entity records and documents as reference material uses `skill_bindings` with `target_kind = 'entity' | 'document'` and `relationship_type = 'references'`. The table already existed for manifest-declared relationships; extending its CHECK constraints avoids a schema proliferation.
**Own-scope permissions follow the authoring pattern.** `skills.own.*` permissions apply only when `skills.created_by = userId`. This mirrors the pattern used by other user-authored content: users can manage what they created without needing team-wide write grants.
## For Agents
Skills are designed specifically for agents. The workflow:
1. Agent sees a skill index in its system prompt listing available skills
2. When a task matches a skill's domain, the agent calls `loadSkill(slug)`
3. The tool returns the full instructions
4. The agent follows those instructions for the current task
Skills are particularly useful for:
- Standardizing how agents approach specific task types
- Sharing best practices across multiple agents
- Updating agent behavior without modifying system prompts
- Allowing tenant-specific instruction overrides
The `user_invocable` flag allows users to explicitly request a skill in chat (e.g., "use the data-analysis skill on this dataset").
## Related Modules
- **Agent System** (`features/agents/`) -- agents reference skills via config and `agent_skills` table
- **Chat** (`features/chat/`) -- `loadSkill` tool is included in agent tool sets
- **Actions** (`features/actions/`) -- executable work that skill manifests can reference by slug
- **Integrations** (`features/integrations/`) -- external systems and resources required by skill readiness
- **Entities** (`features/entities/`) -- workstream, SOP, goal, and task system entities that skills can support or implement
- **Sessions** (`features/sessions/`) -- runtime trace for skill-backed action execution
- **Evals** (`features/evals/`) -- test and optimization loop for trigger quality, output quality, and performance
- **Admin** -- skill CRUD lives in Admin > Skills tab