Add subagent mode via agent-as-tool pattern

Each AgentProfile defines a model and a subset of tools. Profiles are
registered as callable tools in the main harness so the orchestrator LLM
can delegate tasks to specialized subagents. Each invocation runs an
isolated Harness with fresh history; only the final answer is returned,
keeping the orchestrator context clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 18:29:26 -05:00
parent 5a40ebd8d8
commit 07e978f8f9
5 changed files with 88 additions and 1 deletions
+1
View File
@@ -13,6 +13,7 @@ add_library(drharness_lib
src/ollama_client.cpp
src/harness.cpp
tools/shell_tool.cpp
tools/agent_tool.cpp
)
target_include_directories(drharness_lib PUBLIC include tools)
target_link_libraries(drharness_lib
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <string>
#include <vector>
struct AgentProfile {
std::string name;
std::string description;
std::string model;
std::vector<std::string> tool_names;
};
+18 -1
View File
@@ -1,10 +1,24 @@
#include "harness.hpp"
#include "ollama_client.hpp"
#include "tool_registry.hpp"
#include "agent_profile.hpp"
#include "shell_tool.hpp"
#include "agent_tool.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
static const std::vector<AgentProfile> profiles = {
{
.name = "shell_expert",
.description = "An agent that can run shell commands to inspect the system, "
"files, and processes. Delegate multi-step shell tasks here.",
.model = "gemma4:26b",
.tool_names = {"run_shell"}
},
// Add more agent profiles here
};
int main() {
OllamaConfig cfg;
@@ -13,9 +27,12 @@ int main() {
ToolRegistry registry;
registry.register_tool(make_shell_tool());
// Add more tools here:
// Add more base tools here:
// registry.register_tool(make_file_read_tool());
for (const auto& profile : profiles)
registry.register_tool(make_agent_tool(profile, registry, cfg.base_url));
Harness harness(OllamaClient(cfg), registry, {.max_tool_iterations = 16, .verbose = true});
std::cout << "drharness | model: " << cfg.model
+50
View File
@@ -0,0 +1,50 @@
#include "agent_tool.hpp"
#include "harness.hpp"
#include "ollama_client.hpp"
Tool make_agent_tool(const AgentProfile& profile,
const ToolRegistry& registry,
const std::string& base_url) {
return Tool{
.name = "agent_" + profile.name,
.description = profile.description,
.parameters_schema = {
{"type", "object"},
{"properties", {
{"task", {
{"type", "string"},
{"description", "The task to assign to this agent."}
}}
}},
{"required", {"task"}}
},
.execute = [profile, &registry, base_url](const nlohmann::json& args) -> std::string {
if (!args.contains("task") || !args["task"].is_string())
return "Error: missing required parameter 'task'";
ToolRegistry sub_registry;
for (const auto& tool_name : profile.tool_names) {
auto found = registry.find(tool_name);
if (found)
sub_registry.register_tool(**found);
}
OllamaClient client(OllamaConfig{
.base_url = base_url,
.model = profile.model,
.timeout_s = 120
});
Harness harness(std::move(client), sub_registry, {
.max_tool_iterations = 16,
.verbose = false
});
try {
return harness.chat(args["task"].get<std::string>());
} catch (const std::exception& e) {
return std::string("Agent error: ") + e.what();
}
}
};
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "agent_profile.hpp"
#include "tool.hpp"
#include "tool_registry.hpp"
#include <string>
Tool make_agent_tool(const AgentProfile& profile,
const ToolRegistry& registry,
const std::string& base_url);