Skip to content

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.

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)
name: threat-hunting
version: 0.1.0
description: 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]

Shell-runtime tools read JSON from stdin and return JSON on stdout.

tools/lookup_indicator.py
import json
import os
import 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()

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);
});