Skills
Skills are user-created tools that extend Zubo's capabilities. Each skill is a single TypeScript file that the agent can invoke during conversations to perform actions, fetch data, or interact with external services. Skills are hot-loaded — you can add, edit, or remove them without restarting Zubo. User-installed skills run in sandboxed subprocesses for security, ensuring they cannot access Zubo's internal state or database.
Quick Start
The fastest way to create a skill is with the built-in scaffolding command:
zubo skills new
This interactive wizard walks you through choosing a name, writing a description, and defining parameters. It generates a ready-to-use handler.ts file in your skills directory.
Even easier — just tell Zubo what you need in plain language:
"Build a skill that checks the weather for a location"
Zubo will generate the entire skill file for you, including parameter definitions, error handling, and a working implementation. You can then refine it by chatting: "Add a units parameter that accepts celsius or fahrenheit."
Single-File Format
Every skill is a single TypeScript file with two exports: a skill configuration object and a default handler function. Here is a complete example:
export const skill = {
name: "weather",
description: "Get current weather for a location",
params: {
location: { type: "string", description: "City or coordinates", required: true },
units: { type: "string", description: "celsius or fahrenheit" }
}
};
export default async function (input: Record<string, unknown>): Promise<string> {
const location = input.location as string;
const units = (input.units as string) || "metric";
const res = await fetch(`https://api.example.com/weather?q=${location}&units=${units}`);
return JSON.stringify(await res.json());
}
The skill object tells the AI what this tool does and what parameters it accepts. The default export is the function that runs when the skill is invoked. This is all you need — no boilerplate, no build step, no configuration files.
Skill Config
The exported skill object defines metadata that Zubo uses to register the tool with the LLM:
| Field | Required | Description |
|---|---|---|
name | Yes | Unique identifier. Lowercase letters, numbers, and underscores only ([a-z0-9_]+). This is how the AI references your skill internally. |
description | Yes | A natural-language explanation shown to the AI. The LLM uses this to decide when to invoke your skill, so be specific and descriptive. |
params | No | An object mapping parameter names to their definitions (see Params Format below). If omitted, the skill accepts no parameters. |
Params Format
Each key in the params object is a parameter name. The value is an object with the following fields:
| Field | Type | Description |
|---|---|---|
type | string | The data type: "string", "number", or "boolean". Determines how the LLM formats the argument. |
description | string | Explains what this parameter does. Shown to the AI to help it provide the correct value. |
required | boolean | Whether the parameter must be provided. Defaults to false if omitted. |
The params are automatically converted to JSON Schema and passed to the LLM as part of the tool definition. You do not need to write JSON Schema yourself — Zubo handles the conversion.
Handler Function
The handler is the default export of your skill file. It is the function that executes when the AI decides to use your skill.
- Must be the default export — use
export default async function - Signature:
async (input: Record<string, unknown>) => Promise<string> - Must return a string — JSON is recommended for structured data. If the handler returns a non-string value, Zubo will automatically coerce it to JSON via
JSON.stringify(). - Access params via
input.paramName— cast to the appropriate type as needed (e.g.,input.location as string) - Available APIs:
fetch(), Bun APIs, built-in Node/Bun modules (fs,path,crypto, etc.) - Cannot import from Zubo internals — skills run in a sandboxed subprocess and have no access to Zubo's core modules, database, or session state
Accessing Secrets
Skills often need API keys or credentials to call external services. Zubo provides a secure way to pass secrets to skill handlers via environment variables:
export default async function (input: Record<string, unknown>): Promise<string> {
const apiKey = process.env.ZUBO_SECRET_WEATHER_API_KEY;
if (!apiKey) return JSON.stringify({ error: "Weather API key not configured" });
const location = input.location as string;
const res = await fetch(
`https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${location}`
);
return JSON.stringify(await res.json());
}
Secrets referenced in the handler code are automatically passed to the sandboxed subprocess as environment variables with the ZUBO_SECRET_ prefix. Only the secrets that your handler actually references are injected — other secrets are not exposed.
Set secrets via the dashboard or by telling Zubo in chat:
"Store my weather API key: abc123"
You can also use the CLI: zubo secrets set WEATHER_API_KEY abc123
Skill Directory Structure
All skills live under the Zubo workspace directory. Each skill gets its own folder containing a handler.ts file:
~/.zubo/workspace/skills/
├── weather/
│ └── handler.ts # Single-file skill
├── calculator/
│ ├── SKILL.md # Legacy format (still supported)
│ └── handler.ts
├── email_sender/
│ └── handler.ts
└── stock_price/
└── handler.ts
When Zubo starts (or when a skill is added/modified), it scans this directory and hot-loads all valid skills. You can manually place files here or use zubo skills new to scaffold them.
Sandbox Behavior
Security is a core concern when running user-authored code. Zubo enforces the following sandbox rules for user-installed skills:
- Isolated subprocesses — each skill invocation runs in its own Bun subprocess, fully isolated from the main Zubo process
- 30-second timeout — skills are killed after 30 seconds by default. Configure this via
sandbox.timeoutMsin your config - Minimal secrets — only secrets that the handler code explicitly references (via
process.env.ZUBO_SECRET_*) are passed to the subprocess - No internal access — sandboxed skills cannot access Zubo's database, session state, memory store, or any other internal APIs
- Built-in tools are NOT sandboxed — tools like
get_current_datetime,memory_write,memory_search, and other built-in tools run in the main process. Only user-created and registry-installed skills are sandboxed.
Skill Registry
The Zubo skill registry is a community-driven marketplace where you can discover, install, and publish skills:
- Browse skills:
zubo search <query>or use the dashboard's Skills tab - Install a skill:
zubo install <skill-name>or click "Install" in the dashboard - Publish your skill:
zubo publish <skill-name>to share it with the community - Automatic sandboxing: all registry-installed skills are sandboxed by default, just like locally created skills
You can also browse and install skills by chatting with Zubo: "Search for a skill that sends emails" or "Install the github_issues skill."
Managing Skills
Use the CLI to manage your installed skills:
zubo skills list # List all installed skills
zubo skills new # Create a new skill interactively
zubo skills remove # Remove an installed skill
zubo install weather # Install a skill from the registry
zubo search "email" # Search the registry for skills
zubo publish my_skill # Publish your skill to the registry
Or manage skills conversationally by telling Zubo:
- "List my skills"
- "Remove the weather skill"
- "Build a skill that converts currencies"
- "Search for a skill that checks DNS records"
Built-in Tools Reference
Zubo ships with a comprehensive set of built-in tools that are always available. These are not sandboxed — they run in the main process and have full access to Zubo's internals.
| Tool | Description | Permission |
|---|---|---|
get_current_datetime | Get current date and time with timezone | auto |
memory_write | Save a fact or note to persistent memory | auto |
memory_search | Search memory using semantic and full-text search | auto |
manage_skills | Create, list, or remove user skills | auto |
secret_set | Store a secret (API key, token, etc.) | auto |
secret_list | List all stored secret names | auto |
secret_delete | Delete a stored secret | confirm |
cron_create | Create a scheduled task (cron job) | auto |
cron_list | List all scheduled tasks | auto |
cron_delete | Delete a scheduled task | auto |
connect_service | Connect an external integration (GitHub, Google, etc.) | auto |
delegate | Delegate a task to a sub-agent | auto |
manage_agents | Create, list, or remove sub-agents | auto |
manage_workflows | Create and manage multi-step workflows | auto |
manage_teams | Manage agent teams and coordination | auto |
manage_triggers | Manage proactive triggers and alerts | auto |
run_workflow | Execute a defined workflow | auto |
skill_registry | Search and install skills from the registry | auto |
shell | Execute shell commands on the host system | confirm |
file_read | Read the contents of a file | auto |
file_write | Write or append content to a file | confirm |
web_search | Search the web via DuckDuckGo | auto |
url_fetch | Fetch and extract content from a web page | auto |
http_request | Make a generic HTTP request (GET, POST, etc.) | auto |
Tools marked confirm require user confirmation before execution. Tools marked auto run immediately without prompting.
Legacy Format (SKILL.md)
Earlier versions of Zubo used a Markdown-based skill definition file called SKILL.md. This format is still fully supported for backward compatibility:
# weather
Get current weather for a location.
## Input Schema
```json
{
"type": "object",
"properties": {
"location": { "type": "string", "description": "City or coordinates" }
},
"required": ["location"]
}
```
If both a SKILL.md file and an exported skill configuration object exist in the same skill directory, the SKILL.md definition takes precedence. For new skills, the inline export const skill format is recommended as it keeps everything in a single file.
Best Practices
- Keep skills focused — one tool, one purpose. A skill that does too many things makes it harder for the AI to know when to use it.
- Write clear descriptions — the AI relies on your description to decide when to invoke the skill. Be specific: "Get current weather for a city" is better than "Weather stuff."
- Return JSON strings for structured data — this makes it easy for the AI to parse and present results to the user.
- Handle errors gracefully — return error messages as JSON strings instead of throwing exceptions. For example:
return JSON.stringify({ error: "API key not configured" }) - Use
process.envfor API keys — never hardcode secrets in your skill files. UseZUBO_SECRET_*environment variables. - Test by chatting — the easiest way to test a skill is to ask Zubo to use it: "Use the weather skill for London"
- Keep response sizes reasonable — extremely large responses consume context window tokens. Summarize or paginate when dealing with large datasets.