The shell tool would hang indefinitely when running commands that spawn
background processes (e.g., 'sleep 10 &'). This was because:
1. Background processes inherit stdout, which points to our pipe
2. The original code read in a loop when POLLIN was detected
3. After reading available data, the second read() would block waiting
for more data that never comes (since background process just sleeps)
The fix changes the POLLIN handler to read only once, then continue
polling. If POLLHUP is eventually set (when the background process is
killed by timeout), we'll read all remaining data then.
Additionally, FD_CLOEXEC is set on pipe file descriptors to prevent
inheritance during exec() calls (though this doesn't help with fork()
which is how shells spawn background processes).
Fixes issue 205 - failing unit tests.
Add periodic ctrlc_request checks during directory traversal and file
processing in the grep_search tool. This allows the tool to respect user
cancellation (Ctrl+C) during recursive directory scanning of large codebases.
- Changed process_file to return bool indicating cancellation
- Added ctrlc_request check in directory iteration loop
- Added ctrlc_request check in file processing loop
- Returns early with cancellation message when cancelled
Closes issue #189
- Added base_url and model fields to HarnessConfig in json_types.hpp
- Removed base_url and model from OllamaConfig in ollama_client.hpp
- Updated OllamaClient constructor to accept base_url and model as separate parameters
- Updated main.cpp to use HarnessConfig for base_url and model
- Updated agent_tool.cpp to use new OllamaClient constructor
- Added test_harness_config.sh to verify the new config fields
- Updated README.md to document the config fields
The shell tool was only reading once when data was available, which caused
data loss when the child process produced more output than the 4KB buffer.
Fixed by reading in a loop until EOF is reached in both the POLLHUP and
POLLIN handlers.
Resolved merge conflicts:
- CMakeLists.txt: Added both rebase_engine/utility and base64_util sources
- src/harness.cpp: Combined Content type with proper error handling
- src/main.cpp: Merged includes, test flags, and CLI parsing
- Renamed conflicting flags: -B→-Z (build_rebase), -T→-W (edit_prompt), -b→-K (base64_decode)
- tests/CMakeLists.txt: Added all test targets from both branches
- src/rebase_engine.cpp: Fixed to use get_content_string() for Content variant type
- Updated test files to use new flag names (-Z, -W, -K)
Change the required parameter name from 'filePath' to 'path' in the
read_multi_modal tool's JSON schema to match the property definition
and the ReadFileToolParams struct field name.
Replace std::any-based parameter passing with compile-time type-safe adapter pattern.
- Add create_tool_execute<T>() template function in tool.hpp
- Remove convert_params field from Tool struct
- Change execute field type from std::function<std::string(const std::any&)>
to std::function<std::string(const nlohmann::json&)>
- Change ToolRegistry::invoke() return type from std::expected to std::string
- Update all tool implementations to use create_tool_execute<ParamsType>()
- Remove #include <any> from tool.hpp and json_types.hpp
- Simplify harness.cpp invoke caller
All 27 tests pass.
Convert EditToolParams, ReadFileToolParams, SkillToolParams, and
AgentTaskParams from using json_to_##Type() function calls to the
json_to<Type>() template specialization syntax.
Updated files:
- tools/edit_tool.cpp
- tools/file_read_tool.cpp
- src/skill.cpp
- tools/agent_tool.cpp
- src/main.cpp (-R flag handler)
Update ListFilesToolParams, GrepToolParams, and ShellToolParams
call sites to use the template json_to<T>() function instead of
the old json_to_##Type() style.
- tools/file_list_tool.cpp: Use json_to<ListFilesToolParams>()
- tools/grep_tool.cpp: Use json_to<GrepToolParams>()
- tools/shell_tool.cpp: Use json_to<ShellToolParams>()
- src/main.cpp: Update -R flag handler for these three types
- Add make_regex_safely() helper to json_types.hpp that wraps std::regex
construction and returns std::expected<std::regex, std::string>
- Replace try/catch for regex compilation with make_regex_safely()
- Replace try/catch for filesystem operations with std::error_code overloads
(following the pattern in skill.cpp)
All acceptance criteria met:
- No try/catch blocks in tools/grep_tool.cpp
- Invalid regex patterns produce clear error messages
- Filesystem errors during directory iteration produce clear error messages
- test_tool_grep.sh passes
- No throw statements in modified file
Change the Tool interface to support error handling during parameter conversion:
- ToolParamsConverter now returns std::expected<std::any, std::string>
- ToolRegistry::invoke() handles conversion errors and returns them as tool result strings
- Replace NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE with DRH_DEFINE_TYPE_REQUIRED for SkillToolParams and AgentTaskParams
- edit_tool uses json_to_EditToolParams() instead of args.get<EditToolParams>()
- agent_tool uses json_to_AgentTaskParams() instead of args.get<AgentTaskParams>()
- Add test for missing required field error handling
- Updated Harness::chat() to return std::expected<std::string, std::string>
- Client errors are retried up to 20 times, then returns 'max retries exceeded'
- Tool invocation errors are returned as tool result string to LLM
- Max tool iterations returns 'max tool iterations exceeded' error
- Removed try/catch block for JSON parsing, using parse_json_safely() instead
- Updated main.cpp to handle expected return type from chat()
- Updated agent_tool.cpp to handle expected return type from chat()
- Added tests for client retry exhaustion, max tool iterations, and tool invocation errors
- register_tool() returns std::expected<void, std::string> for duplicate detection
- invoke() returns std::expected<std::string, std::string> for unknown tool errors
- create_subregistry() returns std::expected<ToolRegistry, std::string> for missing tools
- All throw statements removed from tool_registry.cpp
- Updated main.cpp to use expect_or_exit() for startup tool registration
- Updated harness.cpp and agent_tool.cpp to handle new return types
All 26 existing tests pass.
Subagents now print their activity to stderr with a '>' prefix per nesting
level, making it easy to trace arbitrarily deep agent chains. A file-scope
depth counter (RAII-guarded) tracks the current call depth at runtime so
the correct prefix is always computed regardless of how deeply agents nest.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
C++20 agentic loop targeting a local Ollama instance via the OpenAI-compatible
/v1/chat/completions API. Includes an extensible tool registry pattern and a
shell command runner as the first tool. Builds with CMake + system libcurl +
nlohmann-json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>