Custom Capabilities
Capabilities are portable bundles that combine a system prompt, tools, hooks, and optional skills into a single package. They are the main unit of extensibility when you want a domain-specific agent setup that can be loaded on demand.
What a capability contains
Section titled “What a capability contains”A capability is defined by a capability.yaml manifest and optional supporting files:
- System prompt:
system-prompt.md(optional) to extend or replace the base prompt - Tools: shell or HTTP tools exposed by the manifest
- Hooks, scorers, stop conditions: optional runtime behaviors
- Skills: bundled skill packs (a
skills/directory or custom path)
Capability manifest (capability.yaml)
Section titled “Capability manifest (capability.yaml)”name: threat-huntingversion: 0.1.0description: Threat hunting tools + skills for indicator triage.
skills: true
config: intel_api_key: type: secret env: INTEL_API_KEY required: true
tools: - name: lookup_indicator description: Look up an indicator in the intel service. runtime: shell entry: tools/lookup_indicator.py parameters: type: object properties: indicator: type: string description: IP, domain, or hash. limit: type: number required: [indicator]Implement a tool entry script
Section titled “Implement a tool entry script”Shell-runtime tools read JSON from stdin and return JSON on stdout.
import jsonimport osimport sys
def main() -> None: payload = json.load(sys.stdin) indicator = payload.get("parameters", {}).get("indicator") api_key = payload.get("config", {}).get("intel_api_key") or os.environ.get("INTEL_API_KEY")
if not indicator: print(json.dumps({"error": "Missing indicator"})) return
# Replace this stub with real API calls. result = { "indicator": indicator, "verdict": "suspicious", "source": "example-intel", "api_key_set": bool(api_key), }
print(json.dumps({"result": result}))
if __name__ == "__main__": main()Load and register a capability
Section titled “Load and register a capability”Use loadCapability to parse the manifest and wrapCapability to turn tools/hooks into SDK
primitives. Capability tools are already AI SDK tool() instances, so you can pass them straight
into an agent’s tool map.
import { anthropic } from '@ai-sdk/anthropic';import { createAgent, createGenerator, loadCapability, wrapCapability } from '@dreadnode/agents';
async function main(): Promise<void> { const generator = await createGenerator(anthropic('claude-sonnet-4-20250514')); const loaded = await loadCapability('./capabilities/threat-hunting'); const wrapped = wrapCapability(loaded);
const capabilityTools = Object.fromEntries( wrapped.tools.map((tool) => { const meta = tool as Record<string, unknown>; const name = (meta._capName as string) ?? (meta.name as string); return [name, tool]; }) );
const agent = createAgent({ name: 'threat-hunter', generator, systemPrompt: 'You are a threat hunting assistant.', hooks: wrapped.hooks, generateOptions: { tools: capabilityTools }, });
const result = await agent.run({ input: 'Check 8.8.8.8 for suspicious activity.' }); console.log(result.trajectory.lastMessage?.text ?? 'No output');}
main().catch((error) => { console.error(error); process.exit(1);});