AI Agents

Agent Tool Call Guardrails

Autonomous agents can spiral — a single planning loop may make thousands of tool calls in minutes. Rate-limit each tool type per session with a sliding window before the agent hits your cloud bill.

Before & After

Without RLAAS

Runaway Agents Are Invisible Until the Bill Arrives

  • An infinite reasoning loop triggers thousands of web_search calls in under 5 minutes
  • No per-session limit — one rogue agent starves all other sessions
  • No circuit breaker — you find out about the incident on the next cloud invoice
  • Changing the limit requires redeploying the agent framework code
# ✗ No guardrail — agent calls tools freely class ToolExecutor: def execute(self, session_id: str, tool: str, args: dict): # No check — a loop can call this 10,000x/min return self.tools[tool].run(args) # upstream API billed per call
With RLAAS

Per-Tool, Per-Session Rate Limits — Retry Hints for Agents

  • 20 web_search calls per session per minute — enforced at the tool dispatcher
  • 100 total tool calls per session per hour — hard cap before agent terminates
  • Returns retry_after so the agent can back off gracefully and retry
  • Change limits per tool type live — no redeploy of the agent framework
# ✓ RLAAS enforces per-tool, per-session limits client = RlaasClient(base_url="http://rlaas:8080") class ToolExecutor: def execute(self, session_id: str, tool: str, args: dict): decision = client.check(CheckRequest( user_id=session_id, resource=f"tool:{tool}" )) if not decision.allowed: raise ToolRateLimited( tool=tool, retry_after=decision.retry_after ) return self.tools[tool].run(args)

How It Works

Policy Configuration

# One policy per tool type
{
  "id": "agent-web-search-per-session",
  "resource": "tool:web_search",
  "algorithm": "sliding_window_counter",
  "config": {
    "limit": 20,
    "window_seconds": 60
  },
  "action_deny": "reject",
  "metadata": {
    "description": "20 web searches/min per agent session"
  }
}

Request Flow

  1. Check RLAAS before each tool call — pass resource: "tool:web_search" and user_id: sessionID
  2. If allowed — the tool executes normally
  3. If denied — throw a structured error; the agent reads retry_after and backs off
  4. Add a session-level total cap — a second policy on resource: "tool:*" limits total calls per session per hour
  5. Update limits live — PATCH the policy to tighten or loosen limits without redeploying the agent

SDK Examples

Gate every tool call through RLAAS and propagate the retry hint back to the agent.

// gate tool calls for autonomous agent sessions func (e *ToolExecutor) Execute(ctx context.Context, sessionID, tool string, args map[string]any) (any, error) { decision, err := e.rlaas.Check(ctx, &rlaas.CheckRequest{ UserID: sessionID, Resource: "tool:" + tool, }) if err != nil { return nil, err } if !decision.Allowed { return nil, &ToolRateLimitError{ Tool: tool, RetryAfter: decision.RetryAfter, } } return e.tools[tool].Run(ctx, args) }
from rlaas_sdk import RlaasClient, CheckRequest client = RlaasClient(base_url="http://rlaas:8080") class ToolExecutor: def execute(self, session_id: str, tool: str, args: dict): decision = client.check(CheckRequest( user_id=session_id, resource=f"tool:{tool}", )) if not decision.allowed: raise ToolRateLimited( tool=tool, retry_after=decision.retry_after, ) return self.tools[tool].run(args)
import { RlaasClient } from '@rlaas/sdk'; const rlaas = new RlaasClient({ baseUrl: 'http://rlaas:8080' }); async function executeTool(sessionId: string, tool: string, args: unknown) { const decision = await rlaas.check({ userId: sessionId, resource: `tool:${tool}`, }); if (!decision.allowed) { throw new ToolRateLimitError({ tool, retryAfter: decision.retryAfter }); } return tools[tool].run(args); }
// guard agent tool calls (Node.js) const { RlaasClient } = require('@rlaas/node-sdk'); const client = new RlaasClient('http://rlaas:8080'); async function guardedToolCall(agentId, toolName, args) { const decision = await client.check({ user_id: agentId, resource: 'agent:tool-calls', }); if (!decision.allowed) { return { error: 'Tool call limit reached for this step', retry_after: decision.retry_after }; } return await tools[toolName](args); }
// gate agent tool calls through RLAAS import io.rlaas.sdk.RlaasClient; import io.rlaas.sdk.model.CheckRequest; import io.rlaas.sdk.model.Decision; RlaasClient rlaas = new RlaasClient("http://rlaas:8080"); public Object executeTool(String sessionId, String tool, Map<String, Object> args) { Decision decision = rlaas.checkLimit( new CheckRequest(sessionId, "tool:" + tool) ); if (!decision.isAllowed()) { throw new ToolRateLimitException(tool, decision.getRetryAfter()); } return tools.get(tool).run(args); }
// gate agent tool calls through RLAAS using Rlaas.Sdk; using Rlaas.Sdk.Models; var rlaas = new RlaasClient("http://rlaas:8080"); async Task<object> ExecuteToolAsync(string sessionId, string tool, Dictionary<string, object> args) { var decision = await rlaas.CheckLimitAsync( new CheckRequest(sessionId, $"tool:{tool}") ); if (!decision.Allowed) throw new ToolRateLimitException(tool, decision.RetryAfter); return await _tools[tool].RunAsync(args); }
// guard agent tool calls (C++) #include "rlaas/client.h" rlaas::Client client("http://rlaas:8080"); json guarded_tool_call(const std::string& agent_id, const std::string& tool, const json& args) { rlaas::CheckRequest req; req.user_id = agent_id; req.resource = "agent:tool-calls"; auto decision = client.check(req); if (!decision.allowed) throw GuardrailError("Tool call limit reached"); return tools.at(tool)(args); }
// guard agent tool calls (Rust) use rlaas_sdk::{Client, CheckRequest}; let client = Client::new("http://rlaas:8080"); async fn guarded_tool_call( client: &Client, agent_id: &str, tool: &str, args: Value, ) -> Result<Value> { let decision = client.check(&CheckRequest { user_id: agent_id.into(), resource: "agent:tool-calls".into(), ..Default::default() }).await?; if !decision.allowed { return Err(anyhow!("Tool call limit reached")); } tools.call(tool, args).await }
# guard agent tool calls (Ruby) require 'rlaas_sdk' client = Rlaas::Client.new('http://rlaas:8080') def guarded_tool_call(agent_id, tool_name, args) decision = client.check( user_id: agent_id, resource: 'agent:tool-calls' ) raise GuardrailError, 'Tool call limit reached' unless decision.allowed tools[tool_name].call(args) end