Reflects the new agent hierarchy (BaseAgent helpers, ToolUsingAgent intermediate base, NativeReActAgent, NativeOpenHandsAgent, RLMAgent, OpenHandsAgent SDK) across all architecture, user-guide, extending, contributing, roadmap, and API reference pages. Removes CustomAgent references and adds Mermaid diagrams for new agent types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
19 KiB
Agents
Agents are the agentic logic layer of OpenJarvis. They determine how a query is processed -- whether it goes directly to a model, through a tool-calling loop, via ReAct reasoning, CodeAct code execution, recursive decomposition, or an external agent runtime. All agents implement the BaseAgent ABC and are registered via the AgentRegistry.
Overview
| Agent | Registry Key | accepts_tools |
Multi-turn | Description |
|---|---|---|---|---|
SimpleAgent |
simple |
No | No | Single-turn query-to-response |
OrchestratorAgent |
orchestrator |
Yes | Yes | Multi-turn tool-calling loop (function_calling + structured) |
NativeReActAgent |
native_react |
Yes | Yes | Thought-Action-Observation loop |
NativeOpenHandsAgent |
native_openhands |
Yes | Yes | CodeAct-style code execution + tool calls |
RLMAgent |
rlm |
Yes | Yes | Recursive LM with persistent REPL |
OpenHandsAgent |
openhands |
No | Yes | Wraps real openhands-sdk |
OpenClawAgent |
openclaw |
Yes | Yes | External agent via HTTP or subprocess |
BaseAgent ABC
All agents extend the abstract BaseAgent class.
from abc import ABC, abstractmethod
from openjarvis.agents._stubs import AgentContext, AgentResult
class BaseAgent(ABC):
agent_id: str
accepts_tools: bool = False
def __init__(
self,
engine: InferenceEngine,
model: str,
*,
bus: Optional[EventBus] = None,
temperature: float = 0.7,
max_tokens: int = 1024,
) -> None: ...
@abstractmethod
def run(
self,
input: str,
context: AgentContext | None = None,
**kwargs,
) -> AgentResult:
"""Execute the agent on the given input."""
The accepts_tools class attribute controls whether an agent can receive tools via --tools on the CLI or tools= in the SDK. Agents with accepts_tools = False ignore tool arguments.
BaseAgent also provides concrete helper methods (_emit_turn_start, _emit_turn_end, _build_messages, _generate, _max_turns_result, _strip_think_tags) that subclasses use to avoid duplicating common logic. See the architecture docs for details.
ToolUsingAgent is an intermediate base class (extends BaseAgent) that sets accepts_tools = True and adds a ToolExecutor and max_turns loop limit. All tool-using agents extend this class.
AgentContext
The runtime context handed to an agent on each invocation.
| Field | Type | Description |
|---|---|---|
conversation |
Conversation |
Message history (pre-filled with context if memory injection is active) |
tools |
list[str] |
Tool names available to the agent |
memory_results |
list[Any] |
Pre-fetched memory retrieval results |
metadata |
dict[str, Any] |
Arbitrary metadata for the run |
AgentResult
The result returned after an agent completes a run.
| Field | Type | Description |
|---|---|---|
content |
str |
The final response text |
tool_results |
list[ToolResult] |
Results from tool executions during the run |
turns |
int |
Number of turns (inference calls) taken |
metadata |
dict[str, Any] |
Arbitrary metadata about the run |
SimpleAgent
The SimpleAgent is a single-turn agent that sends the query directly to the inference engine and returns the response. It does not support tool calling.
How it works:
- Builds a message list from the conversation context (if provided) plus the user query.
- Calls the inference engine via
_generate(). - Returns the response as an
AgentResultwithturns=1.
Constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
engine |
InferenceEngine |
-- | The inference engine to use |
model |
str |
-- | Model identifier |
bus |
EventBus |
None |
Event bus for telemetry |
temperature |
float |
0.7 |
Sampling temperature |
max_tokens |
int |
1024 |
Maximum tokens to generate |
When to use: For straightforward question-answering without tool calling or multi-turn reasoning.
OrchestratorAgent
The OrchestratorAgent is a multi-turn agent that implements a tool-calling loop. It is the primary agent for queries that require computation, knowledge retrieval, or structured reasoning. Extends ToolUsingAgent.
How it works:
- Builds the initial message list from context and the user query.
- Sends messages with tool definitions (OpenAI function-calling format) to the engine.
- If the engine responds with
tool_calls, theToolExecutordispatches each call. - Tool results are appended as
TOOLmessages and the loop continues. - If no
tool_callsare returned, the response is treated as the final answer. - The loop stops after
max_turnsiterations (default: 10), returning whatever content is available along with amax_turns_exceededmetadata flag.
Constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
engine |
InferenceEngine |
-- | The inference engine to use |
model |
str |
-- | Model identifier |
tools |
list[BaseTool] |
[] |
Tool instances to make available |
bus |
EventBus |
None |
Event bus for telemetry |
max_turns |
int |
10 |
Maximum number of tool-calling turns |
temperature |
float |
0.7 |
Sampling temperature |
max_tokens |
int |
1024 |
Maximum tokens to generate |
mode |
str |
"function_calling" |
Tool-calling mode (function_calling or structured) |
system_prompt |
str |
None |
Custom system prompt |
When to use: For queries that need calculation, memory search, sub-model calls, file reading, or multi-step reasoning.
!!! info "Tool-Calling Loop"
The orchestrator follows the OpenAI function-calling convention. The engine must support returning tool_calls in its response for the loop to engage. If tools are provided but the engine does not return any tool calls, the agent behaves like a single-turn agent.
NativeReActAgent
The NativeReActAgent implements a Thought-Action-Observation loop following the ReAct pattern. It prompts the LLM to produce structured output (Thought:, Action:, Action Input:, Final Answer:) and parses the response to drive tool execution. Extends ToolUsingAgent.
How it works:
- Builds a system prompt listing available tool names.
- Generates a response and parses the ReAct-structured output.
- If a
Final Answer:is found, returns it. - If an
Action:is found, executes the tool and feeds the result back as anObservation:. - Loops until a final answer is produced or
max_turnsis exceeded.
Constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
engine |
InferenceEngine |
-- | The inference engine to use |
model |
str |
-- | Model identifier |
tools |
list[BaseTool] |
[] |
Tool instances to make available |
bus |
EventBus |
None |
Event bus for telemetry |
max_turns |
int |
10 |
Maximum number of reasoning turns |
temperature |
float |
0.7 |
Sampling temperature |
max_tokens |
int |
1024 |
Maximum tokens to generate |
When to use: For queries that benefit from explicit step-by-step reasoning with tool use, where you want visibility into the agent's thought process.
!!! note "Backward compatibility"
The registry alias "react" maps to NativeReActAgent. The old import from openjarvis.agents.react import ReActAgent also still works.
NativeOpenHandsAgent
The NativeOpenHandsAgent is a CodeAct-style agent that generates and executes Python code alongside structured tool calls. It can also pre-fetch URL content from user input to provide direct context to the LLM. Extends ToolUsingAgent.
How it works:
- Builds a detailed system prompt with tool descriptions and code execution instructions.
- Pre-fetches any URLs in the user input, inlining the content directly.
- For each turn, generates a response and attempts to extract code blocks or tool calls.
- Code is executed via
code_interpreter; tool calls are dispatched viaToolExecutor. - If neither is found, returns the content as the final answer.
Constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
engine |
InferenceEngine |
-- | The inference engine to use |
model |
str |
-- | Model identifier |
tools |
list[BaseTool] |
[] |
Tool instances to make available |
bus |
EventBus |
None |
Event bus for telemetry |
max_turns |
int |
3 |
Maximum number of turns |
temperature |
float |
0.7 |
Sampling temperature |
max_tokens |
int |
2048 |
Maximum tokens to generate |
When to use: For queries involving URL content, code execution, or tasks where the LLM can write and run Python to solve the problem.
RLMAgent
The RLMAgent implements recursive decomposition via a persistent REPL, based on the RLM paper. Context is stored as a Python variable rather than injected into the prompt, enabling processing of arbitrarily long inputs through recursive sub-LM calls. Extends ToolUsingAgent.
How it works:
- Creates a persistent REPL with
llm_query()andllm_batch()callbacks. - Injects context from
AgentContextinto the REPL as a variable. - Generates code and executes it in the REPL.
- If
FINAL(value)is called, returns the value as the final answer. - If no code block is found, treats the content as a direct text answer.
Constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
engine |
InferenceEngine |
-- | The inference engine to use |
model |
str |
-- | Model identifier |
tools |
list[BaseTool] |
[] |
Tool instances (optional) |
bus |
EventBus |
None |
Event bus for telemetry |
max_turns |
int |
10 |
Maximum number of code-execute turns |
temperature |
float |
0.7 |
Sampling temperature |
max_tokens |
int |
2048 |
Maximum tokens to generate |
sub_model |
str |
same as model |
Model for sub-LM calls |
sub_temperature |
float |
0.3 |
Temperature for sub-LM calls |
sub_max_tokens |
int |
1024 |
Max tokens for sub-LM calls |
max_output_chars |
int |
10000 |
Max REPL output characters |
system_prompt |
str |
RLM_SYSTEM_PROMPT |
Override the system prompt |
When to use: For long-context tasks that benefit from recursive decomposition, such as summarizing large documents, processing structured data, or tasks that require programmatic manipulation of context.
OpenHandsAgent (SDK)
The OpenHandsAgent wraps the real openhands-sdk package for AI-driven software development. Extends BaseAgent directly (tool management is handled by the SDK internally).
How it works:
- Imports
openhands.sdkat runtime. - Creates an LLM, Agent, and Conversation from the SDK.
- Sends the input and runs the conversation.
- Returns the final message content.
Constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
engine |
InferenceEngine |
-- | The inference engine (fallback) |
model |
str |
-- | Model identifier |
bus |
EventBus |
None |
Event bus for telemetry |
temperature |
float |
0.7 |
Sampling temperature |
max_tokens |
int |
1024 |
Maximum tokens to generate |
workspace |
str |
os.getcwd() |
Working directory for the agent |
api_key |
str |
$LLM_API_KEY |
API key for the LLM provider |
When to use: For software development tasks (debugging, code editing, test fixing) where the OpenHands SDK provides a full development agent runtime.
!!! warning "Optional dependency"
Requires openhands-sdk (pip install openjarvis[openhands]) and Python 3.12+.
OpenClawAgent
The OpenClawAgent wraps the OpenClaw Pi agent runtime, communicating via either HTTP or subprocess transport. It supports tool calling through the OpenClaw protocol.
How it works:
- Checks transport health.
- Sends a
QUERYprotocol message through the transport. - If the response is a
TOOL_CALL, dispatches the tool locally viaToolExecutor. - Sends the tool result back as a
TOOL_RESULTmessage. - Continues the tool-call loop until the response is a final answer or error (up to 10 turns).
Constructor parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
engine |
Any |
None |
Inference engine (fallback/provider) |
model |
str |
"" |
Model identifier |
transport |
OpenClawTransport |
None |
Pre-configured transport (overrides mode) |
mode |
str |
"http" |
Transport mode: "http" or "subprocess" |
bus |
EventBus |
None |
Event bus for telemetry |
Transport modes:
- HTTP (
HttpTransport): Sends HTTP POST requests to an OpenClaw server. - Subprocess (
SubprocessTransport): Spawns a Node.js process and communicates via stdin/stdout using JSON-line protocol.
!!! warning "Node.js Requirement" The subprocess transport mode requires Node.js 22+ to be installed on the system.
Using Agents
Via CLI
# Simple agent
jarvis ask --agent simple "What is the capital of France?"
# Orchestrator with tools
jarvis ask --agent orchestrator --tools calculator,think "What is sqrt(256)?"
# NativeReActAgent
jarvis ask --agent native_react --tools calculator "What is 2+2?"
# ReAct alias (same as native_react)
jarvis ask --agent react --tools calculator,think "Solve step by step: 15% of 340"
# NativeOpenHandsAgent
jarvis ask --agent native_openhands --tools calculator,web_search "Summarize example.com"
# RLMAgent
jarvis ask --agent rlm "Summarize this long document"
# OpenHands SDK agent
jarvis ask --agent openhands "Fix the bug in test_utils.py"
# OpenClaw agent
jarvis ask --agent openclaw "Tell me a story"
Via Python SDK
from openjarvis import Jarvis
j = Jarvis()
# Simple agent
response = j.ask("Hello", agent="simple")
# Orchestrator with tools
response = j.ask(
"Calculate 15% of 340",
agent="orchestrator",
tools=["calculator"],
)
# NativeReActAgent with tools
response = j.ask(
"What is sqrt(256)?",
agent="native_react",
tools=["calculator", "think"],
)
# Full result with tool details
result = j.ask_full(
"What is the square root of 144?",
agent="orchestrator",
tools=["calculator", "think"],
)
print(result["content"])
print(result["turns"])
print(result["tool_results"])
j.close()
Agent Registration
Agents are registered via the @AgentRegistry.register() decorator. This makes them discoverable by name at runtime:
from openjarvis.core.registry import AgentRegistry
# Check if an agent is registered
AgentRegistry.contains("orchestrator") # True
# Get the agent class
agent_cls = AgentRegistry.get("orchestrator")
# List all registered agent keys
AgentRegistry.keys()
# ["simple", "orchestrator", "native_react", "react", "native_openhands", "rlm", "openhands"]
Event Bus Integration
All agents publish events on the EventBus when a bus is provided:
| Event | When |
|---|---|
AGENT_TURN_START |
At the beginning of a run (via _emit_turn_start) |
AGENT_TURN_END |
At the end of a run (via _emit_turn_end) |
TOOL_CALL_START |
Before each tool execution (ToolUsingAgent subclasses) |
TOOL_CALL_END |
After each tool execution (ToolUsingAgent subclasses) |
!!! info "Inference events"
INFERENCE_START / INFERENCE_END events are published by the InstrumentedEngine wrapper, not by agents directly. This keeps telemetry opt-in and transparent to agent code.
These events enable the telemetry and trace systems to record detailed interaction data automatically.