Orchestration & sub-agents¶
Query Routing¶
When enabled, the assistant classifies each query before processing it and routes it to a specialized tool subset. This reduces noise for the model and improves response quality.
Categories:
| Route | Description | Tools Available |
|---|---|---|
simple_qa |
Greetings, explanations, general knowledge | None (direct LLM answer) |
code |
File ops, code editing, git, shell commands | fs_read, fs_write, file_edit, execute_bash, git, search, etc |
research |
Web search, URL fetching | web_search, web_crawler |
knowledge |
Document reading, indexing, RAG queries | pdf/csv/docx/json readers, RAG tools, fs_read |
full |
Multi-category or ambiguous tasks | All tools (fallback) |
How it works:
- A lightweight LLM call classifies the query into one of the categories above
- The agent node binds only the tools for that category
- If a query spans multiple categories, it routes to
full(all tools) - The classifier prompt is customizable via
ROUTING_PROMPTinprompts.yaml(all prompts live there, separate fromconfig.yaml)
Configuration:
# config.yaml — toggle
ENABLE_ROUTING: true
# prompts.yaml — the classifier prompt
ROUTING_PROMPT: |
# Custom classifier prompt (optional, has a sensible default)
...
Orchestrator-Workers¶
When enabled alongside routing, tasks classified as full (spanning multiple categories) are automatically decomposed into focused subtasks executed by specialized workers.
How it works:
- Orchestrator: An LLM call decomposes the complex query into subtasks, each assigned a category (code, research, knowledge, etc.) and an optional
depends_on(which earlier subtasks it needs). - Workers: Each subtask runs with only the tools for its category. Subtasks are scheduled by their dependencies — independent subtasks run in parallel (bounded by
SUBAGENT_MAX_CONCURRENCY), while a subtask that declared adepends_onwaits for exactly those results (threaded into its prompt). A purely linear plan still runs in order. - Aggregator: If there were multiple subtasks, a final LLM call synthesizes all worker results into a single coherent response.
Example flow for "Read this PDF and write a summary to a file" (a dependency chain):
Orchestrator decomposes into:
[Step 1/2: Read and summarize the PDF document] → knowledge worker
[Step 2/2: Write the summary to summary.md] → code worker (depends_on step 1)
[Synthesizing results...] → aggregator
Example flow for "Investigate X and Y independently" (parallel):
Orchestrator decomposes into (no dependencies between them):
[Running 2 steps in parallel]
[Step 1/2: Investigate X] → worker ┐ run
[Step 2/2: Investigate Y] → worker ┘ together
[Synthesizing results...] → aggregator
Configuration:
# config.yaml — toggles
ENABLE_ROUTING: true # Required
ENABLE_ORCHESTRATION: true # Activates orchestrator for 'full' route
# prompts.yaml — customize the prompts (optional; sensible defaults bundled)
# ORCHESTRATOR_PROMPT: | # decomposition prompt (teaches the depends_on field)
# AGGREGATOR_PROMPT: | # synthesis prompt
# LLM — bounds how many independent subtasks run at once (shared with sub-agents)
LLM:
SUBAGENT_MAX_CONCURRENCY: 4 # 1 = force sequential
When orchestration is disabled, full routes use all tools in a single agent loop (the previous behavior). No regression. Distinct from model-initiated sub-agents (spawn_agent): the orchestrator is framework-driven (it decomposes complex full queries for you), while sub-agents are the model's own on-demand delegation — both now share the same bounded concurrency engine.
Sub-agents (spawn_agent)¶
Distinct from the orchestrator (which the framework drives), sub-agents are
model-initiated: the assistant can call the spawn_agent tool to hand a
self-contained task to a fresh sub-agent that runs in its own isolated
context and returns only its final report. The sub-agent's intermediate
tool calls never enter the main conversation, so the assistant's context stays
clean during search-heavy or multi-step work.
How it works:
- The assistant calls
spawn_agent(agent_type, prompt, description)with a complete, self-contained brief (the sub-agent doesn't see the conversation). - The sub-agent runs its own model↔tool loop with its type's tool allowlist and system prompt, then returns a concise report.
- The assistant summarizes that report for you (the raw result isn't shown directly).
The sub-agent runs quietly: its internal steps (searches, file reads,
reasoning) don't stream to your terminal — you see a compact status line
(… N sub-agents running) while it works, and only its final report comes back.
For independent investigations the assistant can spawn several sub-agents in
one turn and they run in parallel (bounded by LLM.SUBAGENT_MAX_CONCURRENCY,
default 4; set it to 1 to force sequential). A destructive tool used inside a
sub-agent still asks for confirmation the same way the main assistant does.
Background sub-agents. For a long task you don't want to wait on, the assistant can run a sub-agent in the background: the call returns right away and you keep working. When it finishes, its report surfaces automatically — if you're idle, the assistant speaks up on its own to deliver it; if you're mid-conversation, it's folded into your next turn. Because a background sub-agent has no terminal to prompt on, it automatically skips any destructive tool that isn't already approved — so background work is safe by default; use it for read-only investigation or when the needed actions were pre-approved. The assistant can also resume a finished sub-agent with a follow-up ("now also check the tests"), which continues it with its prior work as context — this works even after restarting the app or loading a saved conversation, because each run's brief and report are saved to disk.
Built-in agent types:
| Type | Tools | Use for |
|---|---|---|
general-purpose |
All tools | Multi-step tasks that may research and make changes |
explore |
Read-only (search/read) | Locating code, tracing how things work, gathering context |
plan |
Read-only (search/read) | Investigating, then returning a step-by-step plan |
explore and plan cannot edit files or run commands. Nested spawning is
blocked (a sub-agent can't spawn its own sub-agents). Each type's system prompt
lives in prompts.yaml (SUBAGENT_GENERAL_PROMPT, SUBAGENT_EXPLORE_PROMPT,
SUBAGENT_PLAN_PROMPT) and can be customized.
Custom agent types. Define your own by dropping a markdown file in
~/.mnemoai/agents/ (one file per agent, e.g. reviewer.md):
---
name: reviewer # optional; defaults to the filename (reviewer)
description: Reviews a diff for bugs and style issues. Read-only.
tools: fs_read, grep_search, glob_search # optional allowlist; omit or "*" = all tools
---
You are a meticulous code reviewer. Given a diff or set of files, identify
bugs, edge cases, and style issues, then return a concise prioritized report.
descriptionis required (it's what the assistant reads to decide when to spawn the type);nameandtoolsare optional.toolsmay be a YAML list or a comma-separated string; omit it (or use*) for the full toolset.fs_read/describe_imageare always available andspawn_agentis always removed (no nested spawning), regardless of the list.- The markdown body is the sub-agent's system prompt.
- A custom type overrides a built-in of the same name.
- Bad or incomplete files are skipped (reported, never fatal).
There's no /agents command: agents are authored as files and discovered
automatically. All available types (built-in + custom) are listed to the
assistant, which decides when to spawn one — you don't invoke them directly.