feat(ptc): programmatic tool calling -- executor, SDK, and E2E tests#408
feat(ptc): programmatic tool calling -- executor, SDK, and E2E tests#408zmanian wants to merge 5 commits intonearai:mainfrom
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a robust system for Programmatic Tool Calling (PTC), significantly enhancing the flexibility and power of the IronClaw platform. By enabling tools to directly invoke other tools, it streamlines complex workflows and reduces reliance on LLM orchestration for sequential or nested operations. The changes span across the orchestrator's API, WASM runtime, and a new Python SDK, providing a comprehensive solution for direct tool interaction with built-in safety and control mechanisms. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant new feature: Programmatic Tool Calling (PTC). The implementation is comprehensive, adding a ToolExecutor, a new orchestrator endpoint, a Python SDK, and integration with WASM host functions. The code is well-structured and includes extensive tests. I've identified a few areas for improvement, primarily concerning timeout logic and panic safety, which I've detailed in the review comments.
| ) | ||
|
|
||
| try: | ||
| with urllib.request.urlopen(req, timeout=max(timeout_secs or 60, 60) + 5) as resp: |
There was a problem hiding this comment.
The client-side timeout calculation for urlopen appears to be incorrect. The use of max(timeout_secs or 60, 60) enforces a minimum timeout of 60 seconds on the client, even if a shorter timeout_secs is provided (e.g., for http_get which defaults to 30s). This could lead to the client waiting longer than expected.
The client timeout should be based on the requested timeout_secs with a small buffer, without enforcing a large minimum. For example, with timeout_secs=30, the client timeout should be around 35 seconds, not 65.
| with urllib.request.urlopen(req, timeout=max(timeout_secs or 60, 60) + 5) as resp: | |
| with urllib.request.urlopen(req, timeout=(timeout_secs if timeout_secs is not None else 60) + 5) as resp: |
There was a problem hiding this comment.
Fixed -- timeout now uses the actual timeout_secs (or 60s default) plus a 5s buffer.
| let timeout = timeout_override | ||
| .unwrap_or_else(|| { | ||
| let tool_timeout = tool.execution_timeout(); | ||
| if tool_timeout > Duration::from_secs(MAX_TIMEOUT_SECS) { | ||
| self.default_timeout | ||
| } else { | ||
| tool_timeout | ||
| } | ||
| }) | ||
| .min(Duration::from_secs(MAX_TIMEOUT_SECS)); |
There was a problem hiding this comment.
The logic for determining the execution timeout is a bit unusual. If a tool specifies a timeout greater than MAX_TIMEOUT_SECS, it falls back to self.default_timeout instead of being capped at MAX_TIMEOUT_SECS. This might be unexpected for a tool author who wants a long-running tool. For example, if a tool specifies a 10-minute timeout, it will get the default 60-second timeout, not the maximum 5-minute timeout.
It would be more intuitive to cap the tool's requested timeout at the maximum allowed value.
let timeout = timeout_override
.unwrap_or_else(|| tool.execution_timeout())
.min(Duration::from_secs(MAX_TIMEOUT_SECS));There was a problem hiding this comment.
Fixed -- now caps at MAX_TIMEOUT_SECS via .min() instead of falling back to default.
| self.tool_nesting_depth += 1; | ||
| let result = resolver(&real_name, params, self.tool_nesting_depth); | ||
| self.tool_nesting_depth -= 1; |
There was a problem hiding this comment.
The manual increment and decrement of self.tool_nesting_depth is not panic-safe. If the resolver call panics, self.tool_nesting_depth will not be decremented. This could lead to subsequent tool_invoke calls within the same WASM execution to fail with a false "Nesting depth exceeded" error.
To make this robust, you could use a RAII guard to ensure the decrement happens even in case of a panic.
Example of a simple guard:
struct DepthGuard<'a>(&'a mut u32);
impl<'a> Drop for DepthGuard<'a> {
fn drop(&mut self) {
*self.0 -= 1;
}
}
// In tool_invoke:
self.tool_nesting_depth += 1;
let _guard = DepthGuard(&mut self.tool_nesting_depth);
let result = resolver(&real_name, params, self.tool_nesting_depth);
// _guard is dropped here, decrementing the counter.
resultThere was a problem hiding this comment.
Fixed -- added a NestingGuard RAII struct that decrements on drop.
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR nearai#408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ToolExecutor for standalone tool dispatch used by both the
orchestrator HTTP RPC endpoint and the WASM tool_invoke host function.
Includes Python SDK for container scripts, WASM test fixture, and
comprehensive E2E test coverage across all PTC paths.
Implementation:
- ToolExecutor with timeout, nesting depth limit, safety sanitization
- Orchestrator POST /worker/{job_id}/tools/call endpoint with SSE events
- WASM tool_invoke host function with alias resolution
- Python SDK (stdlib-only) with call_tool + convenience wrappers
Tests (16 new):
- 6 orchestrator HTTP RPC tests (auth, echo, not-found, timeout, SSE, no-executor)
- 3 executor integration tests (sanitization, invalid params, sequential)
- 4 Python SDK tests (env vars, request format, HTTP error, wrappers)
- 3 WASM E2E tests (echo via alias, alias not granted, no capability)
Refs nearai#407
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…r SDK - Fix WASM tool_invoke production wiring: change tool_executor to a shared Arc<std::sync::RwLock> slot with lazy resolution so WASM tools registered during build_all() can access the executor set afterward - Add nesting_depth field to ToolCallRequest and propagate it through the orchestrator's tool_call_handler into JobContext - Add ptc_script built-in tool: runs Python scripts with ironclaw_tools SDK pre-imported, env-scrubbed subprocess, structured output support - Copy Python SDK into Docker worker image at dist-packages path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR nearai#408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8302787 to
4c6b9ce
Compare
- Fix Python SDK client timeout to use actual timeout_secs + 5s buffer instead of enforcing 60s minimum - Cap tool execution timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - Use RAII guard for tool_nesting_depth to ensure decrement on panic Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Reopening from upstream branch |
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR #408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR #408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR #408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR #408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR #408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR #408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR #408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Python SDK: remove 60s minimum timeout enforcement, respect requested timeout with 5s network buffer - Rust executor: cap timeout at MAX_TIMEOUT_SECS instead of falling back to default when exceeded - WASM wrapper: use RAII guard for nesting depth to prevent leak on panic Addresses Gemini review feedback on PR #408. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Adds Programmatic Tool Calling (PTC) infrastructure from #407 (item 1), enabling tools to invoke other tools without LLM round-trips.
tool_invokehost function.POST /worker/{job_id}/tools/callwith auth, SSE event emission (JobToolUse+JobToolResult), and 503 when executor not configured.tool_invoke: Host function with alias resolution -- WASM tools call by alias (e.g.echo_alias), capabilities map aliases to real tool names.ironclaw_tools.pyfor container scripts. Reads connection details from env vars, providescall_tool()+ convenience wrappers (shell,read_file,write_file,http_get).Test plan
16 new tests across 4 suites:
cargo test orchestrator::api::tests --all-features)cargo test tools::executor::tests --all-features)cd sdk/python && python3 -m unittest test_ironclaw_tools)cargo test tools::wasm::wrapper::tests --all-features -- --ignored)Verification:
Refs #407
Generated with Claude Code