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.pycommands: - name: nmap_scan description: Scan one target with nmap input_schema: type: object properties: target: { type: string } required: [target] additionalProperties: false executable: nmap version_probe: [--version] expected_version: 'Nmap version 7.95' argv: [-sV, '{target}'] timeout_seconds: 120 working_directory: workspace adapter_version: 1.0.0skills: - skills/report/hooks: - hooks/observer.pymcp: 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 hooks/ # *.py files exporting @hook-decorated handlers 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 |
commands | no auto-discovery | Explicit command wrapper object |
skills | skills/*/SKILL.md | Path to skill directory |
hooks | hooks/*.py | Path to Python file |
policies | policies/*.py | Path to Python file |
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 |
commands | Tools |
skills | Skills |
hooks | Hooks |
policies | Policies |
mcp | MCP servers |
flags | Flags |
workers | Workers |
outputs | Agent Output |
dependencies, checks | Dependencies & checks |
produces and items are deprecated aliases for outputs. The loader warns when either alias appears. If outputs appears with an alias, outputs takes precedence and the declarations do not merge.
commands
Section titled “commands”commands: - name: nmap_scan description: Scan one target with nmap input_schema: type: object properties: target: { type: string } required: [target] additionalProperties: false executable: nmap version_probe: [--version] expected_version: 'Nmap version 7.95' argv: [-sV, '{target}'] timeout_seconds: 120 working_directory: workspace adapter_version: 1.0.0Rules:
- Command wrappers are explicit. Dreadnode never scans
$PATHor parses commands passed to a broad execution tool. nameuses lowercase snake case and must be unique within the capability.input_schemamust be valid JSON Schema with an object root.executableis one executable path or name. Shell strings and shell executables are rejected.version_probeandargvare non-empty argument arrays. They are not shell command strings.expected_versionis the exact trimmed stdout required from the version probe.timeout_secondsis greater than 0 and no more than 3600 seconds.working_directoryisworkspaceorcapability.adapter_versionis fixed semver, such as1.0.0.- Validation and OCI packaging do not execute the command or version probe.
- The native runtime re-runs the version probe before exposing the tool and before every call. Missing or changed executables remain unavailable.
- Changing a wrapper requires a new capability version. Republishing changed content under the same version fails.
outputs
Section titled “outputs”outputs: [finding, asset, attack_surface]outputs selects platform contracts by identifier. A specialized identifier must already be active in the platform-managed registry; publishing the capability pins the approved immutable version and never creates a type. New registry versions do not move that pin. See Agent Output for the complete grammar, lifecycle, and discovery flow.
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) - Command wrapper uses a shell string or omits required adapter metadata
Validation errors name the offending field and the rule it broke.