-- 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);