Agents¶
Overview¶
IPW profiles multi-turn agent workloads through pluggable agent harnesses. Each agent wraps an existing framework and adds energy telemetry instrumentation. All agents inherit from BaseAgent and are registered with AgentRegistry.
Available Agents¶
| Agent ID | Framework | Install Extra | Use Case |
|---|---|---|---|
react |
Agno | ipw[react] |
General tool-augmented reasoning |
openhands |
OpenHands SDK | ipw[openhands] |
Autonomous task execution, coding |
terminus |
terminal-bench | ipw[terminus] |
Terminal/CLI task benchmarking |
terminus-tb |
terminal-bench | ipw[terminus] |
TerminalBench native (Docker managed by runner) |
Event Types¶
The EventRecorder captures timestamped events during agent execution, correlated with energy telemetry to compute per-action energy costs.
| Event Type | Description |
|---|---|
lm_inference_start / lm_inference_end |
LLM call boundaries |
tool_call_start / tool_call_end |
Tool invocation boundaries |
prefill_start / prefill_end |
Prefill phase (if detectable) |
decode_start / decode_end |
Decode phase (if detectable) |
submodel_call_start / submodel_call_end |
Sub-model calls from MCP tools |
ReAct (Agno)¶
The ReAct agent uses Agno to implement Reasoning + Acting (ReAct) style tool-augmented reasoning. It wraps Agno's Agent class and instruments tool calls for energy tracking.
| Parameter | Type | Default | Description |
|---|---|---|---|
model |
Any | required | Agno Model instance (e.g., OpenAIChat) |
tools |
list[Callable] | required | List of callable tool functions |
instructions |
str | built-in | Custom system instructions |
max_turns |
int | -- | Maximum reasoning iterations |
OpenHands¶
The OpenHands agent uses the OpenHands SDK for autonomous task execution with per-tool energy tracking. It is designed for complex, multi-step tasks such as software engineering, research, and document analysis.
| Parameter | Type | Default | Description |
|---|---|---|---|
model |
Any | required | LLM model instance |
tools |
list | None | OpenHands Tool specs |
mcp_tools |
dict | None | MCP server instances for sub-queries |
max_turns |
int | 20 | Maximum iterations per run |
Terminus¶
The Terminus agent uses terminal-bench to run tasks inside Docker containers with tmux, enabling benchmarking of terminal/CLI task execution.
Prerequisites: Docker Engine installed and running; current user in the docker group (or sudo access).
| Parameter | Type | Default | Description |
|---|---|---|---|
model |
str | required | Model name (e.g., "gpt-4o") |
docker_image |
str | "ubuntu:22.04" |
Docker image for the container |
container_name |
str | "terminus-container" |
Name for the Docker container |
max_turns |
int | -- | Maximum agent turns |
MCP Tools¶
Model Context Protocol servers provide tool capabilities to agents. Each MCP server wraps an external service with a standard interface.
Inference servers -- wrap LLM APIs so agents can make sub-queries:
| Server | Backend |
|---|---|
OpenAIServer |
OpenAI API |
AnthropicServer |
Anthropic API |
GeminiServer |
Google Gemini API |
OllamaServer |
Local Ollama instance |
VLLMServer |
Local vLLM instance |
OpenRouterServer |
OpenRouter API |
Retrieval servers -- provide document retrieval for RAG-style agents:
| Server | Method |
|---|---|
BM25Server |
Sparse BM25 retrieval |
DenseServer |
Dense vector retrieval |
GrepServer |
Grep-based text search |
HybridServer |
Combined BM25 + dense retrieval |
Writing a Custom Agent¶
See Extending IPW for how to implement and register a custom agent.
Native ReAct (react-native)¶
react-native is the new ReAct implementation backed by the Executor. It
replaces the Agno-based react for new benchmarks. Key differences:
| react (Agno) | react-native | |
|---|---|---|
| Loop owner | Agno Agent | IPW Executor |
| Parser | Agno tool spec | structured-text (THOUGHT/ACTION/INPUT) |
| Retry | Agno internal | Executor: up to 3 attempts, exponential backoff |
| Parallel tools | Agno's dispatch | asyncio.gather with side_effect_conflict fallback |
| Telemetry | Per-tool _record_event | EventBus + EnergyAttribution subscriber |
Usage¶
System tools¶
shell_exec— bash via subprocess (side-effect-conflict, no network)git_tool— allowlisted git subcommands (status / diff / log / show / branch / add / commit / checkout / rev-parse / ls-files / stash)apply_patch— apply unified diff via GNU patchhttp_request— HTTP via httpx (network required)repl— persistent Python REPL subprocess (state persists across calls within a single agent instance)code_interpreter_docker— sandboxed one-shot Python execution in a Docker container (--network=none --memory=512m --cpus=1)
For ipw run --agent react-native, all registered tools are made available to
the agent (SWE-Bench tasks, for example, lean on shell_exec, git_tool,
apply_patch, and repl).
Retry policy¶
The Executor distinguishes two error classes via classify_error:
- RetryableError (
TimeoutError,ConnectionError, network glitches) — retried up to 3 times with exponential backoff (1s / 2s / 4s). After exhaustion, the error is reraised asRetryExhaustedError(distinct from a fatal-from-startFatalError). - FatalError (malformed agent output, missing tool, assertion errors, default for unknown exceptions) — no retry; the run aborts immediately.
Each retry publishes a RETRY_ATTEMPT event on the bus so telemetry can
distinguish "transient retry survived" from "fatal-from-attempt-1" runs.
Parallel tool dispatch¶
When a turn emits multiple tool calls, the Executor runs them concurrently
via asyncio.gather. Tools tagged side_effect_conflict=True (shell_exec,
git_tool, apply_patch, repl) force sequential dispatch within a turn — two
shell commands stepping on each other's workspace would be a real bug,
not a feature.
Migration from react¶
Agno's react is preserved for existing benchmark configurations and is
unaffected by this change. New benchmark work should target react-native
to get retry, parallel dispatch, and bus-driven telemetry. GAIA, TerminalBench,
and FRAMES also run on the new infra.
Browser and PDF tools¶
Three tools support web/document tasks. They are optional dependencies
and ship under the [browser] and [pdf] extras:
uv pip install -e "intelligence-per-watt[browser,pdf]"
python -m playwright install chromium # one-time, ~200MB
Available tools¶
browser— Headless Chromium fetch via Playwright. Returns rendered page text (page.inner_text("body")). Parameters:url,wait_for(CSS selector),timeout(seconds, default 30). Network required.browser_axtree— Same Playwright stack, but returns the accessibility tree as indented text (role + name + value per node). Better than raw HTML for agent navigation tasks. Same parameters asbrowser.pdf_tool— Extract text and metadata from a PDF. Parameters:path(local file or http(s) URL),page_range(e.g."1-5"or"1,3,5"),extract_metadata(bool, default False). Uses pdfplumber + pypdf.
GAIA + react-native¶
When invoked via react-native, GAIA web/document tasks use the browser, PDF,
shell, and repl tools:
FRAMES uses browser, browser_axtree, shell_exec, repl (no PDFs).
Workspace isolation¶
All these tool calls execute inside a per-query temp dir managed by
AgenticRunner._run_with_executor. The browser tool's downloads, the PDF
tool's URL-fetched temp files, and any shell-side effects stay inside that temp
dir, which is cleaned up automatically after each query.
Image and audio tools¶
Two multimodal tools support GAIA's image/audio tasks. Install via the
[multimodal] extra:
Available tools¶
image_tool— Analyze a local image file (or URL) and answer a question about it using a vision model (gpt-4o-mini). Parameters:path(image file or http(s) URL),question(default "Describe this image in detail."). Supports png, jpg/jpeg, gif, webp. Images larger than ~2000px are downscaled before encoding. Network required.audio_tool— Transcribe a local audio file to text via OpenAI Whisper. Parameters:path(audio file),language(optional ISO code). Supports mp3, wav, m4a, ogg, flac, webm; max 25MB. Network required.
Divergence from OpenJarvis¶
OpenJarvis's image_tool is an image generation tool (DALL-E). GAIA needs
image understanding — analyzing images that appear in tasks — so IPW's
image_tool was built fresh as a vision-understanding tool rather than ported.
The audio tool IS a direct port of OpenJarvis's Whisper transcription tool.
GAIA full tool set¶
When invoked via react-native, GAIA uses:
This covers GAIA's text, web, document, image, and audio task types. All tool calls run inside the per-query temp workspace and are bounded by the Executor's per-step (300s) and per-tool (120s) timeouts.
docker_shell_exec, FRAMES, and the wrapper-agent bridge¶
This adds the 12th Tier-1 tool, wires FRAMES through react-native, and bridges
the SDK-wrapped agents (OpenHands, Terminus) onto the EventBus.
docker_shell_exec¶
docker_shell_exec— run a shell command in a freshdocker run --rmcontainer, distinct fromcode_interpreter_docker(which runs Python). Parameters:command,image(defaultubuntu:22.04),timeout(seconds). Hardened by default:--network=none --memory=512m --cpus=1. When the Executor sets a per-query workspace (_default_cwd), the directory is mounted at/workspace(-v {cwd}:/workspace -w /workspace) so file side effects stay contained. Requires Docker; unit tests skip without it.
The full 12-tool set: shell_exec, git_tool, apply_patch, http_request,
repl, code_interpreter_docker, browser, browser_axtree, pdf_tool,
image_tool, audio_tool, docker_shell_exec.
FRAMES on react-native¶
FRAMES (web-research QA) runs through the react-native + Executor stack with
the browser, browser_axtree, shell_exec, and repl tools.
Wrapper-agent telemetry bridge (best-effort)¶
WrapperAgentBridge (ipw.telemetry.wrapper_agent_bridge) republishes an
SDK-wrapped agent's event stream onto the IPW EventBus — AGENT_START/END,
TURN_START/END, TOOL_CALL_START/END, LM_INFERENCE_START/END — assigning
paired correlation_ids so EnergyAttribution pairs tool/LM energy windows
just as it does for native agents.
- OpenHands mode (
open_turn_on_start=False): each action/observation pair is one bus turn; retry is task-level (whole-task retry on error status). - Terminus mode (
open_turn_on_start=True): the whole container run is a single window (start()→TURN_START,finish()→TURN_END).
The bridge never imports openhands-sdk or terminal-bench; default_classify
duck-types both stub dicts and OpenHands-shaped objects (class names containing
Action/Observation). Fidelity is intentionally lower than native agents
(spec §4.7) — the right tradeoff for SDK-wrapped harnesses. Live-SDK wiring of
the bridge into OpenHands.run / Terminus.run is a documented follow-up; the
legacy non-bus agent path is unaffected.
Final benchmark coverage¶
All four target benchmarks now run with unified energy telemetry:
| Benchmark | Agent | Tools |
|---|---|---|
| SWE-Bench | react-native |
shell, git, patch, repl |
| GAIA | react-native |
browser, axtree, pdf, image, audio, shell, repl |
| FRAMES | react-native |
browser, axtree, shell, repl |
| TerminalBench | terminus-tb |
(terminal env) |
react-native benchmarks get telemetry via the native EventBus path;
TerminalBench via terminus-tb plus the WrapperAgentBridge where applicable.
Robustness and run-time controls¶
Agent termination robustness¶
The react-native loop was strengthened to stop a real failure mode: on
multi-hop tasks the agent could call a tool every turn until max_turns without
ever emitting FINAL_ANSWER, yielding an empty answer. Three changes fixed it:
- Chain continuity —
_build_messagesnow replays the agent's own priorTHOUGHT/ACTIONoutputs (not just tool observations), so the model can build on its reasoning across turns instead of re-deriving and looping. - Turn-budget awareness — the
Executorexposesturn_index/max_turnsonExecutorContext, and the agent injects a per-turn budget hint ("you are on turn N of M") plus a strong final-turn directive. - Forced final — on the last turn the agent always returns a non-empty
FINAL_ANSWER(best-effort from its current reasoning) rather than spending the turn on another tool call.
Effect on a FRAMES sample (gpt-4o-mini): completion rate rose from ~1-in-4 to all instances answering, with clean concise answers.
Retry + dedicated-hardware controls¶
Two ipw run flags (see profiling) surface existing infra:
--max-retries N— per-turn retry budget on transient errors (0disables retry; default 3). The CLI count is retries; the runner/Executor uses the attempt countN + 1.--require-dedicated-hardware— turns the startup preflight (which samples baseline GPU/CPU utilization) into strict mode: the run aborts if competing workloads are detected, instead of only flaggingshared_device_warning. Use this for publishable energy numbers — whole-device measurement inflates per-query attribution in proportion to any contaminating workload.
Stress + per-tool energy validation¶
- Concurrency stress (
stressmarker) — runs 20 queries throughAgenticRunneratconcurrency=20with isolated per-query agents, asserting index-ordered results and zero cross-talk (validates the isolation that multi-query energy profiling depends on). - Per-tool energy attribution (
test_per_tool_energy_integration.py) — confirms each local-compute tool (shell_exec,repl,code_interpreter_docker) emits anENERGY_ATTRIBUTEDevent with non-zero joules end-to-end; a hardware-integration test checks a real CPU-spin window against the live energy monitor.
CI¶
.github/workflows/core-infra-regression.yml runs the offline unit-level
regression subset on every PR touching execution/, telemetry/, or tools/.
Tests that need a running server or GPU are marked integration and excluded
from CI; the concurrency stress test (stress) is opt-in.