Files
clawmates/migrations/0049_skills.sql
T
Omar SobhandClaude Opus 4.7 1153d72d00
ci / gates (push) Successful in 4s
ci / frontend (push) Successful in 38s
ci / rust (push) Failing after 1m28s
ci / e2e (push) Skipped
ci / publish (push) Skipped
slice 3.5a: skills catalog — the "how to think" layer
Introduce the skills catalog: the second half of the two-layer agent
model (skills teach agents HOW to think about a problem; MCP servers
give them the ABILITY to act). Delivery via MCP resources lands in
Slice 3.5b; this slice ships the data model + API surface.

Migration 0049 extends the legacy `skills` table (from 0001_init.sql,
originally a workspace catalog of markdown snippets) with the richer
typing we need — name, when_to_use, tags, source_kind, current_version
— rather than duplicating tables. Also adds:
  - skill_versions       (version history for level-up promotions +
                          rollback; back-pointer via promoted_from
                          JSONB records agent_id / research artifact /
                          brain memory that produced it)
  - template_role_skills (m2m binding skills to team-template roles
                          with pin_in_context + order_idx)
  - agent_skills_ext     (per-agent overlay: include=true adds a skill
                          to the bundle; include=false prunes a
                          template default for this specific agent)

Rust surface:
  - cm_db::repo::skills_catalog with typed Skill/SkillVersion/
    AgentSkillBinding structs + upsert_builtin (idempotent — bumps
    version + appends to skill_versions ONLY when body changes) +
    list_visible/get/get_by_name reads + template + agent binding
    helpers + effective_for_agent (merges template defaults with
    agent overrides, applies exclude precedence, batch-fetches skill
    bodies)
  - cm_api::routes::skills_catalog with:
      GET /api/skills             — list visible
      GET /api/skills/{id}        — detail
      GET /api/claws/{id}/skills  — effective binding (accepts
        template_id + role_slot as query args to merge in template
        defaults)

Follow-ups:
  - Slice 3.5b: clawmates_skills MCP server exposes catalog as MCP
    resources, honoring pin_in_context for auto-injection
  - Slice 3.5c: seed ~40-60 builtin skills across the 6 stacks

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 13:00:56 -07:00

101 lines
4.9 KiB
SQL

-- Slice 3.5a — skills catalog (extends the legacy skills tables from
-- 0001_init.sql rather than duplicating them).
--
-- The two-layer model:
-- Skills teach agents HOW to think about a problem (static, curated
-- markdown, no auth, no observability of the call itself).
-- MCP servers give agents the ABILITY to act (live, governed, audited).
--
-- Delivery: `clawmates_skills` MCP server (Slice 3.5b) exposes skills as
-- MCP resources. Some skills are pinned (always injected into context);
-- the rest are discovered on-demand via resources/list + read lazily.
--
-- This migration keeps the legacy `skills` + `installed_skills` tables
-- intact (they already ship a workspace-scoped catalog with body_md-
-- shaped content) and adds the missing structural pieces:
-- - richer typing columns on `skills` (name, when_to_use, tags,
-- source_kind, current_version, updated_at)
-- - `skill_versions` for level-up promotions + rollback
-- - `template_role_skills` binding skills to team-template roles
-- with an optional pin_in_context flag
-- - `agent_skills_ext` for per-agent overrides (level-up adds
-- personal learnings that don't yet merit the template default set)
--
-- `agent_skills_ext` sits alongside `installed_skills` — the latter
-- is the "user manually installed this" tracker; the new table is the
-- level-up overlay (added, pinned, promoted). Slice 9 folds them.
-- ── Extend `skills` ──────────────────────────────────────────────
ALTER TABLE skills
ADD COLUMN name TEXT,
ADD COLUMN when_to_use TEXT,
ADD COLUMN tags TEXT[] NOT NULL DEFAULT '{}',
ADD COLUMN source_kind TEXT NOT NULL DEFAULT 'hand_authored',
ADD COLUMN current_version INT NOT NULL DEFAULT 1,
ADD COLUMN updated_at TIMESTAMPTZ NOT NULL DEFAULT now();
-- Backfill `name` from `title` for existing rows so the new UNIQUE
-- constraint doesn't reject them.
UPDATE skills SET name = title WHERE name IS NULL;
ALTER TABLE skills ALTER COLUMN name SET NOT NULL;
-- One skill per (workspace scope, name). NULL workspace = builtin/global.
CREATE UNIQUE INDEX skills_name_uniq
ON skills (COALESCE(workspace_id, '00000000-0000-0000-0000-000000000000'::uuid), name);
CREATE INDEX skills_source_idx ON skills (source_kind);
CREATE INDEX skills_tags_gin ON skills USING gin (tags);
-- ── Version history ─────────────────────────────────────────────
CREATE TABLE skill_versions (
skill_id UUID NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
version INT NOT NULL,
body_md TEXT NOT NULL,
description TEXT NOT NULL,
when_to_use TEXT,
-- Back-pointer to the source that produced this version:
-- {"kind":"agent_brain","agent_id":"..."} etc.
promoted_from JSONB,
author_id UUID REFERENCES users(id) ON DELETE SET NULL,
approved_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (skill_id, version)
);
-- ── Template-role bindings ──────────────────────────────────────
CREATE TABLE template_role_skills (
template_id UUID NOT NULL REFERENCES team_templates(id) ON DELETE CASCADE,
slot TEXT NOT NULL,
skill_id UUID NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
-- true → injected at turn start; false → discoverable via MCP
pin_in_context BOOLEAN NOT NULL DEFAULT false,
order_idx INT NOT NULL DEFAULT 0,
PRIMARY KEY (template_id, slot, skill_id),
FOREIGN KEY (template_id, slot)
REFERENCES template_roles(template_id, slot) ON DELETE CASCADE
);
CREATE INDEX template_role_skills_lookup_idx
ON template_role_skills (template_id, slot, order_idx);
-- ── Per-agent overrides (level-up) ──────────────────────────────
--
-- Semantics:
-- included = true → add this skill to the agent's bundle even if
-- the template didn't include it
-- included = false → remove a template-provided skill for THIS
-- agent (level-up "prune what never got used")
CREATE TABLE agent_skills_ext (
agent_id UUID NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
skill_id UUID NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
included BOOLEAN NOT NULL DEFAULT true,
pin_in_context BOOLEAN NOT NULL DEFAULT false,
added_by UUID REFERENCES users(id) ON DELETE SET NULL,
added_reason TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (agent_id, skill_id)
);
CREATE INDEX agent_skills_ext_agent_idx ON agent_skills_ext (agent_id);