Files
goclaw/docs/16-skill-publishing.md
T
Viet TranandGoon ace07509b7 feat(skills): system skills integration — toggle, dep checking, per-item install (#161)
* feat(infra): add runtime package support for skills

Install nodejs, npm, pandoc, github-cli + pre-install Python packages
(openpyxl, pandas, python-pptx, markitdown) and Node packages
(docx, pptxgenjs). Configure runtime dirs for agent pip/npm installs
with PIP_TARGET, NPM_CONFIG_PREFIX, NODE_PATH to enable dynamic
package installation in read-only container environment.

* feat(infra): add bundled skills with runtime package support

- Add 5 bundled skills: docx, pdf, pptx, xlsx, skill-creator from container skills-store
- Wire GOCLAW_BUILTIN_SKILLS_DIR env var in gateway and CLI
- Support optional runtime packages alongside dynamic skill loading
- Update Dockerfile to COPY bundled-skills at /app/bundled-skills/
- Add PIP_CACHE_DIR in docker-entrypoint.sh for clean pip installs
- Document bundled skills in 14-skills-runtime.md section 6

* feat(infra): remove ai-multimodal skill directory from bundled skills

Remove the ai-multimodal skill package as part of consolidating runtime
package support for bundled skills. This directory is no longer needed
in the bundled skills structure.

* feat(ci): add semantic release and Docker Hub publishing

Add go-semantic-release workflow to auto-create semver tags on merge to
main. Extend docker-publish to push all variants to both GHCR and
Docker Hub (digitop/goclaw).

* feat(skills): add system skills infrastructure with is_system column, dep scanning, and seeder

- Migration 000017: add is_system boolean column with partial index
- Store layer: UpsertSystemSkill, delete protection, IsSystemSkill
- ListAccessible auto-includes system skills (no grants needed)
- ListWithGrantStatus returns is_system field
- Dependency scanner: auto-detect deps from scripts/ or skill-manifest.json
- Dependency checker: verify system binaries, Python/Node packages
- Seeder: seed bundled skills into DB on startup (idempotent via hash)
- Gateway wiring: GOCLAW_BUNDLED_SKILLS_DIR env for bundled skills
- HTTP: delete guard (403), slug conflict check (409), rescan-deps endpoint
- UI: System badge, hide delete for system skills, rescan deps button
- Agent skills tab: "Always available" for system skills
- i18n: en/vi/zh keys for system skills, deps scanning

* feat(skills): conditional system prompt, skill manifests, and Zip Slip fix

- System prompt: only show package list when python3/node are available
- Add skill-manifest.json for pdf, docx, xlsx, pptx bundled skills
- Fix Zip Slip vulnerability in office/unpack.py (all 3 copies)

* refactor(skills): extract shared office code to _shared/ and deduplicate

Move office scripts (pack, unpack, validate, schemas, validators) from
duplicated copies in docx/xlsx/pptx to skills/_shared/office/ with
symlinks. Remove soffice.py (non-functional in containers) and update
SKILL.md references to use soffice binary directly. Update seeder
copyDir to follow symlinks.

Removes ~45K lines of duplicate code across 3 skills.

* fix(skills): address code review findings for system skills integration

- H1: Remove dead symlink branch in copyDir (filepath.Walk follows symlinks)
- H3: Fix rescan-deps to query ALL skills (including archived) and re-activate
  when deps become available; add ListAllSkills() + Status field to SkillInfo
- H4: Add Status field to SkillCreateParams, stop overloading Visibility
- M1: Batch Python/Node dep checks into single subprocess per runtime
- M4: Add rows.Err() check in ListSkills to prevent caching partial results

* feat(skills): async dep checking with realtime WS events

Split Seed() into sync DB upsert + async CheckDepsAsync() goroutine.
Gateway startup no longer blocks on Python/Node subprocess dep checks.

- Seed() returns seeded skills list, all initially status="active"
- CheckDepsAsync() runs in background, emits skill.deps.checked per-skill
- skill.deps.complete event emitted when all checks finish
- Each failed dep check: archives skill + BumpVersion() for immediate
  cache invalidation so next agent turn picks up the change
- UI: use-query-invalidation listens to skill.deps.* events → auto-refresh
  skills list in realtime

* feat(skills): system skills integration with toggle, dep checking, and per-item install

- Add is_system, deps, enabled columns to skills table (migration 017)
- Seed bundled core skills (pdf, docx, pptx, xlsx, skill-creator) on startup
- PYTHONPATH-based dep detection — eliminates false positives from local modules
- Per-item dep install UI with individual status (installing/success/error)
- Enable/disable toggle for core and custom skills (independent of dep status)
- Re-run dep check when skill is toggled back on
- Inline skill thresholds: 40 skills / 5000 tokens before switching to search mode
- Fix UpsertSystemSkill: backfill null file_hash without bumping DB version
- Remove redundant skill-manifest.json files (replaced by deps JSONB column)
- Show author from frontmatter in custom skills tab
- Runtime checker for python3/pip3/node/npm availability
- WS events for dep checking/installing progress
- docs: add 15-core-skills-system.md, 16-skill-publishing.md

---------

Co-authored-by: Goon <duy@wearetopgroup.com>
2026-03-12 09:20:41 +07:00

12 KiB

16 - Skill Publishing System

How agents create, register, and manage skills programmatically through the publish_skill builtin tool, working in tandem with the skill-creator core skill.


1. Overview

The skill publishing system bridges the gap between skill creation (filesystem) and skill management (database). It consists of two components:

Component Type Purpose
skill-creator Core skill (bundled) Guides agents through skill design, implementation, testing, and optimization
publish_skill Builtin tool Registers a skill directory in the database, copies files to managed store, auto-grants to creating agent

Without publish_skill, skills created by agents exist only on the filesystem and are invisible to the database-backed skill management system (no search, no grants, no UI visibility).


2. End-to-End Flow

Agent receives request to create a skill
    │
    ▼
┌─────────────────────────────────────┐
│  1. skill-creator skill activated   │
│     Agent reads SKILL.md guidance   │
│     Creates files via write_file:   │
│       skills/my-skill/SKILL.md      │
│       skills/my-skill/scripts/      │
│       skills/my-skill/references/   │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  2. publish_skill tool called       │
│     publish_skill(path: "skills/    │
│       my-skill")                    │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────────────────────┐
│  3. Tool executes:                                  │
│     a. Validate SKILL.md + parse frontmatter        │
│     b. Derive slug, validate format                 │
│     c. Check system skill conflict                  │
│     d. Compute SHA-256 hash                         │
│     e. Copy dir → skills-store/{slug}/{version}/    │
│     f. INSERT/UPSERT into skills table              │
│     g. Auto-grant to calling agent                  │
│     h. Scan + report missing dependencies           │
│     i. Bump loader cache version                    │
│     j. Generate embedding (async)                   │
└──────────────┬──────────────────────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  4. Result returned to agent:       │
│     - Skill ID, slug, version       │
│     - Grant confirmation            │
│     - Dep warnings (if any)         │
└─────────────────────────────────────┘

3. publish_skill Tool

Parameters

Parameter Type Required Default Description
path string yes - Path to skill directory containing SKILL.md (absolute or relative to workspace)

Activation Conditions

The tool is registered at gateway startup when:

  1. pgStores.Skills is available (PostgreSQL skill store initialized)
  2. PGSkillStore has at least one managed directory (skills-store/)
  3. Skills loader is initialized

The tool appears in every agent's tool set — no per-agent configuration needed. Can be toggled via the builtin tools admin UI.

Context Values Used

Context Key Source Purpose
store.UserIDFromContext(ctx) WS connect / HTTP header Skill owner + grant source
store.AgentIDFromContext(ctx) Agent loop Agent to auto-grant access to
ToolWorkspaceFromCtx(ctx) Tool registry Resolve relative paths

SKILL.md Frontmatter Requirements

---
name: my-skill-name          # REQUIRED — display name
description: What it does     # Recommended — used for search + auto-activation
slug: my-skill-name           # Optional — derived from name if absent
---
  • name is mandatory; tool returns error if missing
  • slug auto-derived via Slugify(name) if not specified
  • Slug must match ^[a-z0-9][a-z0-9-]*[a-z0-9]$

4. Core Logic Details

4.1 Slug Validation

name: "My Awesome Skill"
  → Slugify → "my-awesome-skill"
  → SlugRegexp check → ✓ valid

Rejects: leading/trailing hyphens, uppercase, special chars, spaces.

4.2 System Skill Conflict Check

Prevents overwriting bundled skills (pdf, xlsx, docx, pptx, skill-creator, etc.):

if t.skills.IsSystemSkill(slug) {
    return ErrorResult("slug conflicts with a system skill")
}

4.3 Versioned Storage

Skills are stored in versioned directories. Re-publishing the same slug increments the version:

skills-store/
├── my-skill/
│   ├── 1/
│   │   ├── SKILL.md
│   │   └── scripts/
│   └── 2/          ← re-publish creates new version
│       ├── SKILL.md
│       └── scripts/

GetNextVersion(slug) queries MAX(version) from the skills table (includes archived skills).

4.4 Database Upsert

Uses CreateSkillManaged() with ON CONFLICT(slug) DO UPDATE:

  • New slug → INSERT with visibility = 'private'
  • Existing slug → UPDATE name, description, version, file_path, file_hash
  • Archived skill re-published → status reset to 'active'
  • Embedding generated asynchronously after insert/update

4.5 Auto-Grant

When the calling agent has a valid AgentID in context:

GrantToAgent(ctx, skillID, agentID, version, userID)

This also auto-promotes skill visibility from private → internal, making it accessible via ListAccessible() for the granted agent.

4.6 Dependency Scanning

After publishing, the tool runs static analysis on the skill's scripts/ directory:

  1. ScanSkillDeps — detects required binaries, Python imports, Node packages
  2. CheckSkillDeps — verifies each dependency is available on the system
  3. If missing deps found:
    • Stored in deps JSONB column via StoreMissingDeps()
    • Warning returned to agent with specific missing packages
    • Agent is guided to install via exec (pip/npm) or inform the user

Unlike the HTTP upload handler, the tool does not archive the skill on missing deps — it warns and lets the agent decide.

4.7 Directory Copy Security

Check Action
.. in relative path Skip (prevent traversal)
Symlinks Skip (prevent escape)
System artifacts Skip (.DS_Store, __MACOSX, Thumbs.db, etc.)
Total dir size > 20 MB Reject with error

5. skill-creator Skill

Activation Triggers

The skill-creator is a bundled system skill with a "pushy" description that triggers on:

  • Creating new skills or extending agent capabilities
  • Skill scripts, references, benchmark optimization
  • Description optimization and eval testing

Creation Workflow

  1. Capture Intent — what, when, output
  2. Research — best practices via docs-seeker
  3. Plan — identify scripts, references, assets
  4. Initialize — scripts/init_skill.py <name> --path <dir>
  5. Write — implement SKILL.md + resources
  6. Test & Evaluate — eval suite with parallel runs
  7. Optimize Description — AI-powered trigger optimization
  8. Publish — publish_skill(path: "skills/<name>")
  9. Package (optional) — ZIP for external distribution
  10. Iterate — refine from feedback

Skill File Structure

skills/<skill-name>/
├── SKILL.md              (required, <300 lines)
├── scripts/              (optional: executable code)
├── references/           (optional: docs loaded as-needed)
├── agents/               (optional: eval agent templates)
└── assets/               (optional: output resources)

Key Constraints

Resource Limit
Description ≤1024 chars
SKILL.md <300 lines
Each reference <300 lines
Scripts No limit (executed, not loaded into context)

6. Database Schema

skills table (relevant columns)

Column Type Purpose
id UUID Primary key
slug VARCHAR(255) UNIQUE Canonical identifier
name VARCHAR(255) Display name
description TEXT Auto-activation trigger text
owner_id VARCHAR(255) User who created (or "system")
visibility VARCHAR(10) private → internal (on grant) → public
version INT Increments on re-publish
status VARCHAR(20) active or archived
is_system BOOLEAN True for bundled skills
enabled BOOLEAN Admin toggle
file_path TEXT Filesystem path to versioned dir
file_hash VARCHAR(64) SHA-256 of SKILL.md
deps JSONB {"missing": ["pip:opencv", "python3"]}
frontmatter JSONB Parsed YAML metadata
embedding vector(1536) pgvector for similarity search

skill_agent_grants table

Column Type Purpose
skill_id UUID FK References skills
agent_id UUID FK References agents
pinned_version INT Stored but not used — agent always uses latest
granted_by VARCHAR User who granted

7. Visibility & Access Model

publish_skill creates with visibility = "private"
        │
        ▼
GrantToAgent auto-promotes → "internal"
        │
        ▼
ListAccessible query includes:
  - is_system = true          (all system skills)
  - visibility = 'public'     (anyone)
  - visibility = 'private'    (owner only)
  - visibility = 'internal'   (agents/users with grants)

Revoking the last grant auto-demotes internal → private (atomic SQL).


8. Cache Invalidation

After publishing, two caches are bumped:

  1. PGSkillStore cache — BumpVersion() sets version = time.Now().UnixMilli(), invalidating the ListSkills() cache (TTL 5min + version check)
  2. Skills Loader cache — loader.BumpVersion() invalidates the filesystem-based skill index used for system prompt injection

Next agent turn picks up the new skill in its tool set.


File Purpose
internal/tools/publish_skill.go Tool implementation
internal/skills/helpers.go Shared helpers: ParseSkillFrontmatter, Slugify, IsSystemArtifact, SlugRegexp
internal/store/pg/skills.go DB operations: CreateSkillManaged, GetNextVersion, IsSystemSkill, StoreMissingDeps
internal/store/pg/skills_grants.go GrantToAgent, RevokeFromAgent, ListAccessible
internal/skills/loader.go Filesystem skill loader with priority hierarchy
internal/skills/seeder.go System skill seeder (bundled → DB)
internal/skills/dep_scanner.go Static analysis for skill dependencies
internal/skills/dep_checker.go Runtime dependency verification
internal/http/skills_upload.go HTTP ZIP upload handler (alternative to publish_skill)
cmd/gateway.go Tool registration
cmd/gateway_builtin_tools.go Builtin tool seed data
skills/skill-creator/SKILL.md Core skill instructions