Tools and Permissions
Agent definitions control what tools an agent can use. You can grant access to built-in tools, and the only kind of custom tool you can define yourself is a workflow tool: wrapping an existing workflow with defineWorkflowTool() so the agent can call it. The model only sees the tools you explicitly provide.
Built-in permissions
Grant built-in permissions through metadata.runtimeDetails.permissions.
metadata: {
runtimeDetails: {
permissions: {
'workflow:pdf:read': true,
'workflow:docx:read': true,
'web:fetch': true,
},
},
}
Permission map
| Permission | Tool names |
|---|---|
* | All built-in tools |
web:fetch | webFetch |
web:crawl | webCrawl |
workflow:binary:read | listWorkflowBinaryFiles, readWorkflowBinaryFile |
workflow:binary:write | createWorkflowBinaryOutput |
workflow:pdf:read | extractPdfText |
workflow:docx:read | extractDocxText, extractDocxTables |
workflow:docx:write | replaceDocxText, updateDocxTableCells |
workflow:docx:append | appendDocxText |
Start narrow, and avoid * unless the agent runs in a tightly controlled environment. The supported keys are a closed contract: TypeScript rejects unknown keys, and Kit, dev sync, deploy, and the platform API all validate them again at runtime.
Import the public catalog when you need to render the available choices in your own UI or CLI:
import { BUILTIN_AGENT_PERMISSIONS, type AgentPermissions } from '@culvii/kit';
const availablePermissions = Object.entries(BUILTIN_AGENT_PERMISSIONS);
const permissions: AgentPermissions = { 'workflow:pdf:read': true };
Each catalog entry contains its label, description, and granted tool names. Shell execution is deliberately unavailable to agents.
Permissions prefixed with workflow: only work when the agent is actually running as part of a workflow execution, invoked by a core.agent step. Grant workflow:binary:read to an agent and the tool still won't appear at all if that agent is run standalone, for example from a Console session with no parent workflow. The platform only registers workflow:* tools when a parent workflow execution is present; outside of one, those keys are silently dropped rather than granted. They're not general filesystem or standalone document permissions, so don't rely on them for anything an agent might do on its own.
Permission boundaries
Permissions are scoped to the agent. A primary agent cannot use a tool granted only to a secondary agent, and vice versa. If the primary agent needs to read workflow files, it must delegate to a secondary agent that has the workflow:binary:read permission, or you must grant that permission to the primary agent directly.
Workflow tools
Use defineWorkflowTool() to connect an agent to a workflow:
import { defineWorkflowTool } from '@culvii/kit';
const runSupportWorkflow = defineWorkflowTool({
name: 'runSupportWorkflow',
description: 'Run the approved support workflow for a customer request.',
workflowId: 'workflow-id',
triggerNodeName: 'Manual Trigger',
});
workflowId has to match the target Workflow's own id field exactly. That's the only link between the two; there's no separate lookup or reference resolution, just a string match against the id you gave the Workflow you're wrapping.
triggerNodeName is the name of a manualTrigger step inside that workflow, the exact entry point the tool call starts from. It's typed optional, but it isn't in practice: the platform throws if it's missing when the tool is called. It has to name a manual trigger specifically, since that's the only trigger style a tool call can invoke directly, and it exists because a workflow can have more than one trigger node, so the platform needs to know which one this tool call means.
Attach the tool to an agent definition:
tools: {
runSupportWorkflow,
}
Write clear descriptions, since the model reads the description to decide when to use the tool.
Input schema
Add an inputSchema when the workflow expects structured data. Keep schemas small so the model can easily fill them.
const runSupportWorkflow = defineWorkflowTool({
name: 'runSupportWorkflow',
description: 'Run the approved support workflow for a customer request.',
workflowId: 'workflow-id',
triggerNodeName: 'Manual Trigger',
inputSchema: {
type: 'object',
required: ['customerId', 'priority'],
properties: {
customerId: {
type: 'string',
description: 'The customer id from the request.',
},
priority: {
type: 'string',
enum: ['low', 'normal', 'high'],
},
},
additionalProperties: false,
},
});
Use serializable workflow tool declarations for shared agent definitions. This allows the definitions to cross process and service boundaries.
Tool call timeout
A workflow tool call has 5 minutes to resolve. If the workflow it runs doesn't complete in that time, the platform treats the call as failed and the agent gets an error result rather than waiting indefinitely. This matters most for workflows that pause on a human-in-the-loop step: if a runSupportWorkflow-style tool triggers a workflow that waits on approval, make sure that approval can realistically happen within 5 minutes, or design the workflow to resolve the tool call immediately and handle the wait asynchronously (e.g. notify back via a separate channel) instead of blocking on it.
Combining permissions and workflow tools
An agent can hold built-in permissions and workflow tools at the same time. This one can fetch web pages on its own, and can call runSupportWorkflow when it needs to:
import { MultiAgentEngine, defineWorkflowTool } from '@culvii/kit';
const runSupportWorkflow = defineWorkflowTool({
name: 'runSupportWorkflow',
description: 'Run the approved support workflow for a customer request.',
workflowId: 'support-workflow',
triggerNodeName: 'Manual Trigger',
});
const supportAgent = new MultiAgentEngine({
id: 'support-agent',
name: 'Support Agent',
role: 'primary',
systemPrompt: 'Answer customer questions. Escalate to the support workflow when a case needs human review.',
model: { provider: 'anthropic', modelId: 'claude-sonnet-4-5', credentialId: 'my-anthropic-credential' },
tools: {
runSupportWorkflow,
},
metadata: {
runtimeDetails: {
permissions: {
'web:fetch': true,
},
},
},
secondaryAgents: [],
});