1. Overview
A skill is a self-contained folder that adds tools and/or chat commands to the agent.
skills/, restart.2. Skill anatomy
your-project/
└── skills/
└── my_skill/
├── skill.py ← code: metadata + tools + commands
└── skill.md ← docs: frontmatter + human notes
3. The contract
Hello World
SKILL = {
"name": "greet",
"description": "Say hello.",
}
def hello(name: str = "world") -> str:
return f"Hello, {name}!"
TOOL_SCHEMAS = [{
"type": "function",
"function": {
"name": "hello",
"description": "Return a greeting.",
"parameters": {
"type": "object",
"properties": {"name": {"type": "string"}},
},
},
}]
TOOL_CALLABLES = {"hello": hello}
Chat commands
def _handle_ping(args: list, log) -> None:
log(f"pong {' '.join(args)}", "dim")
COMMANDS = {
"ping": {"handler": _handle_ping, "description": "Reply pong"},
}
Metadata — skill.md
---
name: my_skill
description: One-sentence summary.
version: 1.0.0
author: yourname
---
4. Sandbox
import os
from pathlib import Path
WORKSPACE = Path(os.environ["AGENT_WORKSPACE"]).resolve()
5. Publishing
- Sign in via
agent-code manage - Click Publish
- Others can install
6. Add-on development
Add-ons extend the agent beyond tools and chat commands — they can add UI widgets, replace named services, and subscribe to lifecycle events. Use them when a skill isn't enough. For the top-level overview and comparison table, see the Add-ons page.
Entry point — addon.py
Every add-on lives in <user-data>/addons/<id>/ and provides addon.py and addon.json. addon.py must define a top-level register(api) function; the host calls it exactly once on load.
{
"name": "my-extension",
"version": "1.0.0",
"description": "Adds a custom workspace and alternate Skill Store client",
"author": "Developer"
}
def register(api):
api.register_ui("my-workspace", build_sidebar_button,
location="sidebar", label="My workspace")
api.register_service("skill_store", AlternateSkillStore(),
replaces=True, priority=50)
api.on("tools_loaded", lambda **ctx: None)
UI extension points
register_ui(name, builder, location=..., label=..., description=...) supports sidebar, addons, settings, toolbar, and workspace. The builder receives (parent, app) and must return a Tk / CustomTkinter widget. The host places the returned widget. Keep builders fast — do not block the UI thread.
def build_sidebar_button(parent, app):
import customtkinter as ctk
return ctk.CTkButton(parent, text="My workspace",
command=lambda: app.log_line("Workspace opened", "dim"))
Service extensions
register_service(name, implementation, replaces=False, priority=0, description="") registers a named service implementation. AddonManager.resolve_service(name, default, app=...) returns the highest-priority registered implementation, or the supplied default. Factories can set addon_service_factory = True and accept app.
resolve_service before an add-on can replace a service. Existing services remain the default when no add-on replacement is registered.Lifecycle hooks
api.on("tools_loaded", callback) — subscribe to events. Callbacks receive keyword context, and the return value is ignored.
Trust model
Disabling an add-on prevents its code from being imported on the next load; it does not reverse changes an already-loaded add-on made outside the API. A future signed add-on repository should support signatures, hashes, compatibility metadata, permission declarations, and review before installation.