This document defines the implementation plan for INI-configured llm_query
specializations ("sub-agents") with a strict, non-recursive execution policy.
- Allow users to define specialized LLM tools via config sections.
- Keep configuration surface intentionally small.
- Enforce hard safety policy for subqueries:
- no nested sub-agents
- no recursive
llm_query - no specialization inheritance from subquery context
- Keep behavior deterministic and testable.
- No gRPC/service architecture.
- No full DAG compiler.
- No per-specialization tool allowlist/denylist config.
- No skill inheritance toggles.
Each section with prefix [llm_tool_<tool_name>] defines one specialization.
Required keys:
system_prompt_patchsession_idskill
Optional keys:
context_window(non-negative integer;0means infinite history)
Example:
[llm_tool_code_review_llm]
system_prompt_patch = You are a strict code reviewer focused on correctness and maintainability.
session_id = code_review
skill = code_reviewer
context_window = 8
[llm_tool_explorer_llm]
system_prompt_patch = You explore repository structure and summarize findings with file paths.
session_id = data_explorer
skill = data_explorer
context_window = 20Semantics:
- Presence of section => specialization is registered.
- Absence => specialization is not registered.
- Base behavior is always
llm_query.
For all config-defined subquery tools:
- Execution scope is
SUBQUERY. - Maximum depth is fixed at
1(root -> subquery only). - Subqueries cannot call:
llm_query- any tool registered from
llm_tool_*
ask_userremains disabled in subquery context.- Subqueries do not inherit parent specialization config.
Future extension points may relax these constraints, but not in this phase.
Read [llm_tool_*] sections from INI and validate them.
core/config.h- Add specialization config struct.
- Add loader API (e.g.,
LoadLlmToolSpecializations).
core/config.cpp- Reuse
ParseInioutput to scan section prefixllm_tool_. - Parse required keys + optional
context_window. - Validation:
- non-empty specialization name
- required keys present/non-empty
context_window >= 0if present (0means infinite history)- no duplicate names
- no conflicts with built-in tool names (
llm_query, etc.)
- Reuse
- valid config with multiple specializations loads successfully
- missing
session_id/skill/system_prompt_patchrejected - invalid
context_windowrejected - reserved-name collision rejected
Register specializations as tools before interaction loop starts.
app/main.cpp- After existing
LoadConfigAndApply(...), load specialization config. - Reconcile config-defined specialization tools in the database.
- Register specialization handlers with
ToolExecutor. - Handler maps user input to generalized
InteractionEngine::Queryoptions:- fixed
session_id - fixed
skill - optional
context_window - scope/depth marked as subquery
- fixed
- After existing
- registered tool list includes config-defined specializations
- absent config => no specialization registration
Allow specialization handlers to pass structured query options.
interface/interaction_engine.h/.cpp- Add
QueryOptionsstruct with only:session_idskill- optional
context_window - execution scope/depth metadata
- Preserve existing default behavior for plain
llm_query.
- Add
- existing
llm_querybehavior regression coverage - specialization applies configured session + skill + context window
Enforce non-recursive subquery policy in one place.
tools/tool_executor.*and/or dispatcher boundary- Add execution-context-aware guard:
- if scope is subquery, block
llm_queryand all specialization tools - reject depth > 1
- if scope is subquery, block
- return deterministic
InvalidArgumenterror message
- Add execution-context-aware guard:
- subquery ->
llm_querycall rejected - subquery -> specialization call rejected
- root -> specialization call allowed
Per project guidance, changes touching untrusted structured input should get fuzz coverage. Add the following fuzz tests.
File (proposed): core/config_llm_specializations_fuzz_test.cpp
Target behavior:
- Input: arbitrary bytes treated as INI content.
- Parse with
ParseIni+ specialization loader. - Must never crash/hang.
- Must either:
- return valid parsed specializations, or
- cleanly return error status.
Assertions:
- no crash
- no UB sanitizer failure
- duplicate and malformed sections are rejected deterministically
Seeds:
docs/example_config.inidocs/example_subqueries.ini- minimal/invalid snippets from unit tests
File (proposed): tools/subquery_policy_fuzz_test.cpp
Target behavior:
- Input: random tool names + scope/depth flags + synthetic call arguments.
- Exercise the centralized policy check before dispatch.
- Ensure forbidden combos are rejected cleanly and cannot reach side effects.
Assertions:
- no crash
- forbidden tools in subquery scope always rejected
- depth > 1 always rejected
Seeds:
- known tool names from DB/tool registration tests
- specialization-like names (
llm_tool_foo,code_review_llm)
File (proposed): interface/query_options_fuzz_test.cpp (or in existing test package)
Target behavior:
- Input: random serialized
QueryOptionsfields. - Validate option normalization (context window bounds, session/skill emptiness).
- Confirm malformed options do not reach execution.
Assertions:
- no crash
- invalid options return error
- valid options produce normalized internal form
- Add new fuzz tests in package-local
BUILD.bazelfiles:core/BUILD.bazelfor config fuzz targettools/BUILD.bazelfor policy fuzz target- package owning query options for query fuzz target
Because each user config may define different specialization tool names, the system prompt should guide behavior generically.
-
Add a section under tool-usage guidance:
- "If config-defined LLM specialization tools are available, use them for bounded delegated tasks (review, exploration, summarization) when helpful."
-
Add recursion rule:
- "Never attempt to invoke sub-agent tools from within delegated subquery execution contexts."
-
Add selection rule:
- "Prefer specialized tools when the task matches their role; otherwise use direct tools."
-
Keep deterministic behavior:
- "Do not assume specialization names exist; discover available tools first and adapt."
## LLM Specialization Tools (if present)
- Some environments define additional LLM tools via config (for example,
`code_review_llm`, `explorer_llm`).
- Treat these as bounded delegation tools for focused analysis or review.
- First discover available tools and only use specializations that are present.
- Prefer specializations when task-role fit is strong; otherwise use normal tools.
- Do not attempt recursive delegation through specialization tools.- Add
docs/example_subqueries.iniwith minimal working example. - Link this file from
docs/USERGUIDE.mdand/ordocs/config_impl.md. - Document fixed policy invariants explicitly (no recursion, depth=1).
- INI-defined
[llm_tool_*]sections register specialized tools. - Required fields validated; invalid config fails clearly.
- Subquery recursion is blocked centrally and test-covered.
- Fuzz targets exist for parser, policy boundary, and query options.
system_prompt.mdincludes generic specialization guidance.docs/example_subqueries.iniexists and is referenced in docs.