Tool Substrate Reference¶
Tools are the primary way agents interact with the Security Intelligence Fabric (SIF) and external systems. They are policy-aware by design.
Using tools¶
Always call tools through ToolExecutor.call():
from zak.core.tools.substrate import ToolExecutor
import zak.core.tools.builtins as tools
result = ToolExecutor.call(tools.list_assets, context=context)
ToolExecutor.call() enforces three things before executing:
1. Capability check — the tool's action_id must be in capabilities.tools
2. Policy check — the action_id must pass the PolicyEngine (not in deny-list, in allow-list, etc.)
3. Audit emission — a tool_called event is emitted before execution and a tool_result event after
If the action is denied at step 1 or 2, a PermissionError is raised and an audit event is logged.
Built-in platform tools¶
These tools are available to all agents without any additional setup.
SIF Read Tools¶
| Tool function | action_id |
Arguments | Returns |
|---|---|---|---|
tools.read_asset |
read_asset |
asset_id: str |
dict \| None |
tools.list_assets |
list_assets |
(none) | list[dict] |
tools.list_vulnerabilities |
list_vulnerabilities |
(none) | list[dict] |
tools.list_vendors |
list_vendors |
(none) | list[dict] |
tools.list_controls |
list_controls |
(none) | list[dict] |
tools.list_identities |
list_identities |
(none) | list[dict] |
tools.list_risks |
list_risks |
(none) | list[dict] |
tools.list_ai_models |
list_ai_models |
(none) | list[dict] |
All read tools are automatically scoped to the current tenant — you never pass tenant_id explicitly.
Risk Tools¶
| Tool function | action_id |
Arguments | Returns |
|---|---|---|---|
tools.compute_risk |
compute_risk |
criticality, exposure, exploitability, control_effectiveness, privilege_level |
dict |
compute_risk returns:
{
"risk_score": 7.2, # 0.0–10.0 scaled score
"risk_level": "high", # "low" | "medium" | "high" | "critical"
"raw_score": 0.72, # unscaled input to formula
}
Criticality values: "low", "medium", "high", "critical"
Exposure values: "internet_facing", "external", "internal", "air_gapped"
Privilege level values: "none", "low", "medium", "admin"
SIF Write Tools¶
| Tool function | action_id |
Arguments | Returns |
|---|---|---|---|
tools.write_risk_node |
write_risk_node |
risk_node: RiskNode |
None |
Orchestration Tools¶
| Tool function | action_id |
Arguments | Returns |
|---|---|---|---|
orchestration.spawn_agent |
spawn_agent |
domain: str, environment: str |
dict (child agent result) |
The spawn_agent tool allows parent agents (especially LLM-powered ones) to delegate sub-tasks to child agents. See Multi-Agent Orchestration for details.
Creating custom tools¶
Use @zak_tool to create your own tools:
from zak.core.runtime.agent import AgentContext
from zak.core.tools.substrate import zak_tool
@zak_tool(
name="lookup_threat_intel",
description="Fetch threat intelligence for a domain or IP",
action_id="lookup_threat_intel", # used in YAML capabilities.tools and allowed_actions
tags=["threat_intel", "read"],
)
def lookup_threat_intel(context: AgentContext, target: str) -> dict:
"""Query your threat intel provider."""
# context is injected automatically when using ToolExecutor.call()
# context.tenant_id, context.trace_id, context.dsl all available
response = your_intel_provider.query(target)
return {"threat_score": response.score, "incidents": response.incidents}
Rules for custom tools:
- The function must be importable before ToolExecutor.call() is used
- action_id must match exactly what's in capabilities.tools and allowed_actions in the YAML
- If your function accepts a context parameter, it is automatically injected by ToolExecutor
- Use descriptive tags — they appear in zak tools listings
Declaring tools in YAML¶
capabilities:
tools:
- list_assets # built-in
- compute_risk # built-in
- lookup_threat_intel # custom
boundaries:
allowed_actions:
- list_assets
- compute_risk
- lookup_threat_intel # must also appear here
Important: A tool listed in
capabilities.toolsbut notallowed_actionswill be blocked by the policy engine (not the capability check). Be consistent.
ToolRegistry — discovery¶
To enumerate all registered tools programmatically:
from zak.core.tools.substrate import ToolRegistry
reg = ToolRegistry.get()
print(reg.summary())
for tool_meta in reg.all_tools():
print(tool_meta.action_id, tool_meta.description)
Error handling¶
try:
result = ToolExecutor.call(tools.list_assets, context=context)
except PermissionError as e:
# Policy denied or not in capabilities.tools
print(f"Tool blocked: {e}")
except ValueError as e:
# Function is not a @zak_tool
print(f"Not a tool: {e}")
AgentExecutor catches PermissionError automatically if the tool is called inside execute() and records it as a policy block audit event.