Skip to main content

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):

ActionNotes
search_toolsquery / category / no args (list domains)
get_toolFull schema
execute_toolExecute by name
run_paralleltasks: [{tool, args}]
background_runIncludes 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 setModuleGated by
Memory toolsmemoryMemory enabled
Agent spawn (agent + kv)agent_spawnCoordinator / spawn enabled
Session cron (schedule)schedulerModule + grant
File actionsfilesystemModule loaded (YAML alias workspacefilesystem)
Direct moduleslistedruntime.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 windowDirect (≤N tools)Compact (≤N tools)Discovery (>N tools)
8 K85354+
32 K32213214+
60 K60400401+
128 K128853854+
200 K2001 3331 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.

yaml
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:

yaml
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 structured tool_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:

text
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.readfilesystem__read
  • Inbound (from API): filesystem__readfilesystem.read

YAML authors and module developers always write the dotted form; the conversion is invisible.

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.

CategoryAction(s)Gated by
Parallelrun_parallelalways (when tools enabled)
Background / watch loopsbackground_runalways (when tools enabled)
Skillsuse_skillskills enabled
App-as-toolcall_appcall_app enabled
Human-in-the-loopask_userask_user enabled
Session cron wake-upscheduler.schedulemodule + grant
Shared KVkvwith agent spawn
Long-term memorymemory.*memory enabled

See Execution Primitives.

Cross-references