Skip to content

agent tool

kadyapam edited this page Oct 17, 2026 · 1 revision

AgentTool and AgentSession (kind: agent)

Source: src/tools/agent.rs · Added in 4.1.0 (#112); cost controls in 4.2.0 (#113).

An AI agent step implemented in Rust. No Python, no persistent agent process. Two frameworks:

framework Backend Billing Tools
claude Anthropic Messages API (POST {endpoint}/v1/messages, anthropic-version: 2023-06-01) API key from auth: declared in tools:, run by the host
claude-code Claude Code CLI subprocess: claude -p --output-format json --no-session-persistence --model <m> the CLI's signed-in account Claude Code's own, granted by allowed_tools / mcp_config

Public surface

pub struct AgentSession { /* config, api_key, client, messages, turns, usage counters */ }
impl AgentSession {
    pub async fn start(config: &ToolConfig, ctx: &ExecutionContext) -> Result<Self, ToolError>;
    pub async fn next_turn(&mut self) -> Result<AgentTurn, ToolError>;
    pub fn submit_tool_results(&mut self, outputs: Vec<AgentToolOutput>);
    pub fn tool_spec(&self, name: &str) -> Option<&AgentToolSpec>;
    pub fn config(&self) -> &AgentConfig;
    pub fn turns(&self) -> u32;
}
pub enum AgentTurn { ToolCalls(Vec<AgentToolCall>), Final(serde_json::Value) }
pub struct AgentTool;   // registry kind "agent"

Also exported: AgentConfig, AgentFramework, AgentToolSpec, AgentToolCall, AgentToolOutput.

Why turn-by-turn

The agent's tools are run by the host, not by this crate. The CLI runs each tool as a sub-playbook, and its runner is synchronous and enters tokio itself, so running tools inside one async loop here would nest block_on. The host drives next_turn → runs the requested tools → submit_tool_results → next_turn. The session's whole state is its JSON message history, which is also the shape a callback-driven distributed runner would persist between blocks.

AgentTool (the registry entry) runs tool-free agents only. It renders config with TemplateEngine, starts a session, and takes one turn. A model that asks for a tool there is an error rather than a silent empty answer.

Config (ToolConfig.config)

Field Applies to Default Notes
framework both required claude | claude-code; anything else fails to parse
model, prompt both required prompt is the first user message (stdin for claude-code)
system both — claude: system; claude-code: --append-system-prompt (keeps Claude Code's own)
max_turns claude 8 must be ≥ 1
max_tokens claude 4096 per call
temperature, endpoint claude — endpoint is the base URL (tests / compatible gateways)
tools claude [] {name, description, input_schema, …binding}; extra keys are a host binding, never sent to the model
thinking claude — 4.2.0. disabled | adaptive → thinking: {type}
effort claude — 4.2.0. low | medium | high → output_config.effort
prompt_cache claude true 4.2.0. Cache breakpoint on the last block of the request
claude_bin, allowed_tools, mcp_config, workdir claude-code claude, none, none, inherited mcp_config is passed with --strict-mcp-config

ToolConfig.auth is required for claude and resolves through AuthResolver: ApiKey or Bearer → x-api-key. It is refused for claude-code, as is tools:. ToolConfig.timeout (default 300 s) bounds each HTTP call (claude) or the whole subprocess (claude-code).

Final value

{ "text": "...", "stop_reason": "end_turn", "truncated": false, "model": "...", "turns": 2,
  "usage": { "input_tokens": 8, "output_tokens": 2667,
             "cache_read_input_tokens": 2126, "cache_creation_input_tokens": 8158 } }

claude-code returns text (the CLI's result), turns (num_turns), usage, "framework": "claude-code" and permission_denials.

Guards, and what each prevents

Guard Prevents
auth required for claude; no environment fallback a step spending credits it never declared
max_turns checked before every call an agent looping without bound
max_tokens truncation is an error when the turn has no text or has tool calls; error names the content block types an empty or half-written run reported as success. Haiku spent a whole summary turn on a thinking block and returned [thinking] only
API errors carry status + message, never the key keys in logs
claude-code removes ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN from the child env a subscription run silently billing API credits
claude-code prompt on stdin, never argv variadic --allowed-tools / --mcp-config swallowing the prompt
tracing spans use .instrument(), not a held enter() guard wrong span attribution across .await
prompt cache marks only the request copy, not stored history breakpoints accumulating past the API's limit

Consumers

  • noetl CLI ≥ 5.1.0 (--runtime local) drives AgentSession from PlaybookRunner and runs each tool's playbook: binding as a sub-playbook. See the cli wiki: kind: agent.
  • worker / server: not wired. The server rejects agent at registration (ai-meta#256).

Related

Home · showcase and tutorials: noetl/agent-playbooks wiki · tracking: noetl/ai-meta#474

Clone this wiki locally