Manifest
capability.yaml structure, every field, validation rules, and auto-discovery behavior.
A capability is a directory with a capability.yaml at the root. The manifest declares the capability’s identity and points at its components; everything else is convention-driven.
schema: 1name: threat-huntingversion: 0.1.0description: Triage and report on threat indicators.
agents: - agents/triage.mdtools: - tools/intel.pyskills: - skills/report/mcp: servers: intel-server: command: node args: [mcp/intel.js]flags: verbose: description: Emit extra diagnostic output default: falseworkers: bridge: path: workers/bridge.pydependencies: python: [requests] scripts: [scripts/setup.sh]checks: - name: python-available command: python --versionUnknown top-level keys are ignored silently — useful for future-proofing, but a typo in an optional key won’t error.
Required fields
Section titled “Required fields”| Field | Type | Rule |
|---|---|---|
schema | integer | Must equal 1. Any other value is a validation error. |
name | string | Matches ^[a-z0-9][a-z0-9-]*$. Becomes the capability’s registry name. |
version | string | Semver X.Y.Z. Prereleases not accepted at publish time. |
description | string | Non-empty. Shown in the catalog and TUI. |
Directory layout
Section titled “Directory layout”The conventional layout mirrors the manifest sections:
threat-hunting/ capability.yaml agents/ # *.md files with frontmatter tools/ # *.py files exporting @tool functions skills/ # subdirectories with SKILL.md workers/ # *.py files defining Worker instances mcp/ # scripts or configs for inline MCP servers scripts/ # setup scripts referenced by dependencies.scripts .mcp.json # optional file-based MCP server configNone of these directories is required. The loader only cares about what the manifest references or auto-discovers.
Auto-discovery
Section titled “Auto-discovery”Component fields follow three states:
| Value | Behavior |
|---|---|
| Omitted | Auto-discover from the conventional directory. |
| Explicit list | Load exactly what’s listed; skip auto-discovery. |
Empty [] | Disable the component type entirely. |
# Auto-discover agents/, tools/, skills/agents: # (omit entirely)tools: # (omit entirely)
# Load only these filesagents: - agents/triage.md - agents/responder.md
# Disable tools even if tools/ existstools: []| Field | Auto-discovery source | Entry type |
|---|---|---|
agents | agents/*.md | Path to markdown file |
tools | tools/*.py | Path to Python file |
skills | skills/*/SKILL.md | Path to skill directory |
mcp | .mcp.json or mcp.json | See mcp below |
workers | no auto-discovery | Named map — see workers |
Component sections
Section titled “Component sections”Each component has its own page covering behavior and authoring. The schema fields below define what you put under that key in capability.yaml.
| Section | Companion page |
|---|---|
agents | Agents |
tools | Tools |
skills | Skills |
mcp | MCP servers |
flags | Flags |
workers | Workers |
dependencies, checks | Dependencies & checks |
mcp: files: # list of .mcp.json / mcp.json files - .mcp.json servers: # inline server definitions <name>: command: string # stdio transport args: [string] env: { <key>: string } cwd: string url: string # streamable-http transport headers: { <key>: string } timeout: number # seconds init_timeout: number # seconds when: [string] # flag namesRules:
- Exactly one of
commandorurlper server. Both is an error, neither is an error. when:is valid on inline servers only. File-loaded servers cannot usewhen:.${CAPABILITY_ROOT}resolves at parse time.${VAR}and${VAR:-default}resolve at connect time.- On name conflicts between file and inline, inline wins.
flags: <name>: description: string # required, non-empty default: bool # optional, defaults to falseRules:
- Flag names match
^[a-z0-9]([a-z0-9-]*[a-z0-9])?$. - Max 16 flags per capability.
- Unknown fields on a flag entry are a validation error.
workers
Section titled “workers”workers: <name>: # in-process path: string # path to .py file relative to capability root # subprocess command: string args: [string] env: { <key>: string } # gating when: [string] # flag namesRules:
- Exactly one of
path:orcommand:. Both is a validation error. <name>matches^[a-z0-9][a-z0-9-]*$.- In-process:
pathmust point to a file exporting a module-levelWorkerinstance. - Subprocess:
commandis the executable;argsandenvare optional.
dependencies
Section titled “dependencies”dependencies: python: [string] # pip requirement strings packages: [string] # apt package names scripts: [string] # shell scripts, paths relative to capability rootSandbox-only. Local installs ignore this section.
checks
Section titled “checks”checks: - name: string command: stringRules:
- Runs at capability load time.
- 5-second timeout per check.
- Exit 0 = pass, non-zero = fail.
- Failed checks surface in the TUI capability manager but do not block load.
Catalog metadata
Section titled “Catalog metadata”Optional fields that affect the registry listing but nothing at runtime:
author: Security Teamlicense: MITrepository: https://github.com/acme/threat-huntingkeywords: [dfir, triage, indicators]| Field | Type | Notes |
|---|---|---|
author | string | Free-form attribution. |
license | string | SPDX identifier or free-form. |
repository | string | URL. |
keywords | [string] | Searchable tags. |
Validation
Section titled “Validation”Common errors:
namecontains invalid characters — must match^[a-z0-9][a-z0-9-]*$- Referenced path doesn’t exist (
agents/triage.mdmissing) - Flag name referenced in
when:not declared inflags: - Worker has both
path:andcommand:set (mutually exclusive) - File-loaded MCP server uses
when:(not allowed — inline only)
Validation errors name the offending field and the rule it broke.