* 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>
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:
pgStores.Skillsis available (PostgreSQL skill store initialized)PGSkillStorehas at least one managed directory (skills-store/)- 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
---
nameis mandatory; tool returns error if missingslugauto-derived viaSlugify(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:
- ScanSkillDeps — detects required binaries, Python imports, Node packages
- CheckSkillDeps — verifies each dependency is available on the system
- If missing deps found:
- Stored in
depsJSONB column viaStoreMissingDeps() - Warning returned to agent with specific missing packages
- Agent is guided to install via
exec(pip/npm) or inform the user
- Stored in
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
- Capture Intent — what, when, output
- Research — best practices via docs-seeker
- Plan — identify scripts, references, assets
- Initialize —
scripts/init_skill.py <name> --path <dir> - Write — implement SKILL.md + resources
- Test & Evaluate — eval suite with parallel runs
- Optimize Description — AI-powered trigger optimization
- Publish —
publish_skill(path: "skills/<name>") - Package (optional) — ZIP for external distribution
- 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:
- PGSkillStore cache —
BumpVersion()setsversion = time.Now().UnixMilli(), invalidating theListSkills()cache (TTL 5min + version check) - 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.
9. Related Files
| 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 |