Tools
Digitorn agents call tools through a discovery architecture. The
context_builder module builds the tool index
at bootstrap, the runtime picks one of three injection modes based
on toolset size, and the LLM either receives full schemas, compact
listings, or a small set of meta-tools that let it discover the
rest on demand.
Adaptive tool injection
The injection mode is picked per agent at bootstrap based on the brain's context window vs the actual JSON size of every tool schema.
The algorithm
The result is stored on AgentContext.tool_injection and reused for
every turn. To force a specific mode, set
runtime.tool_injection: direct | compact_direct | discovery in the
YAML; the algorithm is skipped and the forced
mode is used.
Direct mode
Full OpenAI-compatible tool schemas are passed to the LLM - name,
description, complete parameters JSON schema, examples. The LLM
calls tools by name with full parameter knowledge.
Best for apps with ~1-3 modules and small total tool counts (every tool fits comfortably in 20% of the context window).
Compact direct mode
Each tool is listed by name + one-line description (~30 tokens each). The LLM knows which tools exist and can call them directly, but discovers the parameter schema at call time (the runtime fetches it lazily).
Best for apps with 5-12 modules and 60-400 tools.
Discovery mode
Domain tools are hidden behind meta-tools. The agent sees strategic tools directly and discovers domain tools via semantic search.
Meta-tools (injection depends on mode; see builtinsForMode):
| Action | Notes |
|---|---|
search_tools | query / category / no args (list domains) |
get_tool | Full schema |
execute_tool | Execute by name |
run_parallel | tasks: [{tool, args}] |
background_run | Includes optional watch loops |
Also injected when enabled: ask_user, call_app, use_skill,
memory tools, agent + kv.
There are no separate list_categories / browse_category tools;
search_tools covers those modes.
Conditionally direct (load + grant / role rules):
| Action set | Module | Gated by |
|---|---|---|
| Memory tools | memory | Memory enabled |
Agent spawn (agent + kv) | agent_spawn | Coordinator / spawn enabled |
Session cron (schedule) | scheduler | Module + grant |
| File actions | filesystem | Module loaded (YAML alias workspace → filesystem) |
| Direct modules | listed | runtime.direct_modules |
Not separate tools: watch_start family (use
background_run(watch=true)), cron_native tools,
channels.send_message family.
In discovery mode, domain tools sit behind
search_tools / get_tool / execute_tool.
Best for apps with MCP servers, plugin ecosystems, or 400+ tools.
Threshold reference
The thresholds are deterministic given a context window and the actual tool sizes. With the fallback estimator (200 tokens per tool):
| Context window | Direct (≤N tools) | Compact (≤N tools) | Discovery (>N tools) |
|---|---|---|---|
| 8 K | 8 | 53 | 54+ |
| 32 K | 32 | 213 | 214+ |
| 60 K | 60 | 400 | 401+ |
| 128 K | 128 | 853 | 854+ |
| 200 K | 200 | 1 333 | 1 334+ |
When direct_tools is non-empty, the runtime uses the actual JSON
size of every tool schema (4 chars ≈ 1 token), so a small toolset
with very long descriptions can still tip into compact mode.
How discovery works
The semantic index is built at bootstrap from a rich corpus: action FQN + description + tags + parameter names + side effects + aliases (see Semantic search below).
Auto-routing direct calls
If the LLM calls a tool by its short name directly
(filesystem.read({...}) instead of
execute_tool(name="filesystem.read", params={...})), the agent
loop transparently routes it through execute_tool. This happens
in every mode, so the same agent code works whether the LLM saw the
full schema, a compact listing, or only the meta-tools.
Module declaration
Tools come from modules declared under tools.modules. Every entry
is a ModuleBlock.
tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep]
database:
config: {}
constraints:
allowed_actions: [connect, query, disconnect]
The full ModuleBlock field reference (config, setup, constraints, middleware, credential) is in
App Configuration → tools.modules.
Registered and catalog modules are listed in
the index; per-module pages live under
reference/modules/. context_builder is
auto-loaded (do not declare it for ordinary apps).
To inspect any module's actions and parameter schemas, see the per-module reference pages under modules/reference/.
Tool constraints
Two universal keys on ModuleBlock.constraints:
tools:
modules:
filesystem:
constraints:
allowed_actions: [read, glob, grep] # whitelist
database:
constraints:
blocked_actions: [disconnect] # example blacklist
The context_builder builds the agent's tool index with these
constraints applied - blocked / non-allowed actions are invisible
to the LLM. They can still be called from setup: steps, hooks, and
channel pipelines because those run with the daemon's identity, not
the agent's.
Module-specific constraints (anything beyond allowed_actions /
blocked_actions) are validated against the module's
ConstraintSpec declarations.
Native vs text-based tool calling
The runtime chooses native vs text tool calling from the provider /
brain config, with a per-agent override via brain.native_tool_use.
- Native (Anthropic, OpenAI, DeepSeek, Groq, Mistral, Together,
Gemini, xAI, Cerebras, Perplexity, Fireworks): meta-tools and any
direct tools are passed via the API
tools=parameter; the LLM emits structuredtool_calls. The system prompt contains workflow instructions only. - Text-based (Ollama, LM Studio, vLLM): tool schemas are injected into the system prompt; tool calls are parsed from the LLM's text output by the multi-format recovery parser (Agents → Tool-call recovery).
Override per agent via brain.native_tool_use: true | false. See
Agents → Native vs text-based tool calling.
What the system prompt looks like
In native mode:
You are agent "<id>" (role: <role>).
You have access to N tools across M domains.
To find and use tools, you have these meta-tools:
- search_tools: Search over the visible tool index
- get_tool: Full schema for one tool
- execute_tool: Execute a tool with parameters
- run_parallel / background_run / use_skill / call_app / ask_user
Workflow:
1. Discover what is available (search)
2. Get the exact parameter schema before calling
3. Execute the tool with the correct parameters
[Your system_prompt from YAML]
In text-based mode the meta-tools' full JSON schemas are
appended after the workflow block, plus the per-message expected
output format (<tool_call>{json}</tool_call> or equivalent).
Tool name sanitization
OpenAI-compatible APIs require function names to match
^[a-zA-Z0-9_-]+$. Digitorn uses dotted FQNs internally
(filesystem.read); the runtime sanitizes both directions:
- Outbound (to API):
filesystem.read→filesystem__read - Inbound (from API):
filesystem__read→filesystem.read
YAML authors and module developers always write the dotted form; the conversion is invisible.
Semantic search
Discovery mode uses hybrid search combining a semantic index and a keyword inverted index.
- Semantic - embeddings worker + Qdrant, multilingual model
paraphrase-multilingual-MiniLM-L12-v2(384 dims). Supports ~50 languages. - Keyword - inverted index with prefix matching.
- Hybrid scoring - semantic score (×10 weight) + keyword boost (+2-3) for ranking.
The corpus indexed per tool: FQN + description + tags + parameter names + side effects + aliases + synonym expansion. Aliases are declared on the tool definition non-English search queries find the right tool.
Execution primitives
context_builder exposes a small set of primitives that wrap any
module action.
| Category | Action(s) | Gated by |
|---|---|---|
| Parallel | run_parallel | always (when tools enabled) |
| Background / watch loops | background_run | always (when tools enabled) |
| Skills | use_skill | skills enabled |
| App-as-tool | call_app | call_app enabled |
| Human-in-the-loop | ask_user | ask_user enabled |
| Session cron wake-up | scheduler.schedule | module + grant |
| Shared KV | kv | with agent spawn |
| Long-term memory | memory.* | memory enabled |
See Execution Primitives.
Cross-references
- Module configuration block reference: App Configuration → tools
- Built-in tools (delegation, memory, todo): Built-in Tools
- MCP server integration: MCP Servers
- Capabilities (grant / approve / deny): Security
- Per-module reference: modules/index.md