> ## Documentation Index
> Fetch the complete documentation index at: https://docs.powabase.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agents & Tools

> Agents are LLM-powered conversational entities that use a ReAct loop to reason, call tools, and respond. They support eight builtin tools, custom HTTP tools, MCP server integration, session-based memory, hook-based middleware, and a human-in-the-loop approval flow.

## What is an Agent?

An Agent wraps an LLM with a system prompt, temperature, a set of tools, and optional knowledge bases. When you send a message, the agent enters a ReAct (Reason + Act) loop: the LLM decides whether it needs to call a tool, executes the tool if needed, observes the result, and either calls another tool or generates a final response. This loop continues for up to 25 steps (configurable per run). On the final step, tools are withheld so the LLM is forced to produce a text response.

## The ReAct Loop

<Frame caption="Agent ReAct loop: reason, act, observe, respond">
  <img src="https://mintcdn.com/powabase/B-6sRgQRCSKyNrPg/diagrams/agent-react-loop.svg?fit=max&auto=format&n=B-6sRgQRCSKyNrPg&q=85&s=9af7518d2e7fecd31a6387fb45245416" alt="ReAct loop diagram: User Message → LLM (Reason) → Tool Call? If yes → Execute Tool → Tool Result → back to LLM. If no → Generate Response → Stream to User." width="778" height="255" data-path="diagrams/agent-react-loop.svg" />
</Frame>

Each iteration of the loop is a step. Within a step, the agent can call multiple tools: concurrency-safe tools (like knowledge base search) run in parallel via a thread pool, while other tools execute sequentially. If the agent makes the same tool call with identical arguments three times in a row, the platform detects a doom loop and terminates the run. During streaming, each step is visible as SSE events: step\_started, tool\_call, tool\_result, step\_completed, and chunk events for the final response.

### Context Management

The agent automatically manages its context window. Before each LLM call, it estimates the token count and, if nearing the model's context limit, triggers proactive compaction: first pruning old tool results (replacing them with placeholders while keeping the last 3 user turns), then if still over the limit, summarizing the older conversation using a lightweight LLM (gpt-4.1-nano). If the LLM returns a prompt\_too\_long error, the agent retries with compaction. If the LLM output is truncated, the agent injects a "continue where you left off" message and retries up to 3 times.

## Tool Types

Agents can use three categories of tools: builtin tools provided by the platform, custom tools that call your HTTP endpoints, and MCP server tools discovered at runtime.

### Builtin Tools

The platform provides eight builtin tools. Assign them to an agent by name via POST /api/agents/\{id}/tools. The database tools include schema-level access control: when assigning them, you configure which schemas and tables the agent can access.

| Tool            | Description                                                                                                                                                                                                      | Constraints                                                                                                                                                                     |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| database\_query | Execute read-only SQL SELECT queries against the project database                                                                                                                                                | Must start with SELECT. Multi-statement blocked (no semicolons). Results capped at 50,000 chars. Rollback after read.                                                           |
| database\_write | Execute INSERT, UPDATE, and DELETE operations                                                                                                                                                                    | UPDATE requires a WHERE clause (no mass updates). DELETE requires a WHERE clause (no mass deletes). All identifiers validated against injection.                                |
| http\_request   | Make HTTP requests to external APIs                                                                                                                                                                              | Response capped at 10,000 chars. 30-second timeout. **No SSRF validation** — see warning below.                                                                                 |
| code\_execute   | Run Python or JavaScript in a sandboxed environment                                                                                                                                                              | Delegated to external sandbox service. Default 30-second timeout, configurable per call.                                                                                        |
| storage\_read   | List and download files from project storage buckets                                                                                                                                                             | Binary files return a signed URL instead of content. Supports list (with prefix/limit/offset) and download operations.                                                          |
| storage\_write  | Upload text content to project storage buckets                                                                                                                                                                   | UTF-8 text content only. Returns path and file size.                                                                                                                            |
| web\_search     | Search the web using Exa.ai. Five `search_type` modes (`auto`, `neural`, `keyword`, plus the agentic `deep` and `deep-reasoning` tiers), domain filters, date ranges, category filter, and content depth control | Results capped at 20K-50K chars by content mode. 1-10 results. Requires EXA\_API\_KEY. `deep` / `deep-reasoning` are slower and bill at higher tiers — see below.               |
| web\_scrape     | Extract content from web pages as clean markdown, with optional AI vision image analysis                                                                                                                         | Results capped at 200K chars. Supports markdown/HTML/links formats. include\_images uses gpt-4.1-mini vision. Direct image URLs bypass Firecrawl. Requires FIRECRAWL\_API\_KEY. |

<Warning>
  **`http_request` has no SSRF protection.** Unlike Custom Tools (below), the builtin `http_request` tool calls the URL the agent supplies directly, with no allow-list and no validation against internal addresses. An agent given `http_request` can reach RFC1918 ranges, `localhost`, and cloud metadata endpoints (e.g. `169.254.169.254`). Only enable `http_request` for agents whose model + prompt you trust, or use a Custom Tool with a fixed `endpoint` instead. Custom Tools enforce SSRF via `validate_url` on the endpoint URL.
</Warning>

#### Web search modes (`search_type`)

The `web_search` tool accepts a `search_type` argument that selects how Exa runs the query. The first three are standard single-pass searches; the last two run Exa's **agentic deep search**, which iterates over multiple queries and reads more pages for higher-quality results, at the cost of latency and credits.

| `search_type`      | What it does                                                                   | Relative cost |
| ------------------ | ------------------------------------------------------------------------------ | ------------- |
| `auto` *(default)* | Exa picks neural or keyword per query                                          | Standard      |
| `neural`           | Semantic / meaning-based search                                                | Standard      |
| `keyword`          | Exact term matching                                                            | Standard      |
| `deep`             | Agentic deep search — slower, broader, higher quality                          | Higher        |
| `deep-reasoning`   | Agentic deep search with an added reasoning pass — slowest and highest quality | Highest       |

The other `web_search` arguments apply to every mode: `num_results` (1–10, default 5), `include_domains` / `exclude_domains`, `start_date` / `end_date` (ISO 8601 published-date bounds), `category` (`company`, `news`, `research paper`, `tweet`, `github`, `wikipedia`, `personal site`), and `content_mode` (`highlights` default, `compact_text`, or `full_text`).

<Note>
  **Billing follows the mode.** A standard search bills the `web_search` action; `deep` bills `web_search_deep` and `deep-reasoning` bills `web_search_deep_reasoning`, both priced higher than a standard search. The tier is resolved from the actual `search_type` sent to Exa, including when you pin `search_type` in an agent's tool config, so a deep search always bills the deep rate, never the standard one. Exact per-action prices live on the [pricing page](https://powabase.ai). If the search fails for a platform-side reason (5xx, 429, timeout, or a missing `EXA_API_KEY`), the call is **not** billed; only tenant-fault 4xx errors stay billed.
</Note>

#### Tool prerequisites

Some builtin tools need an API key or sandbox configured before they'll work. If the prerequisite is missing, the tool returns a JSON error message to the agent (e.g. `"Exa API key not configured. Set it in Settings > Tools."`) rather than throwing.

| Tool                                | What it needs                                                                       | Where to set it                                                                                                                                                                       |
| ----------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `database_query` / `database_write` | —                                                                                   | No setup. Uses the project's superuser connection automatically.                                                                                                                      |
| `http_request`                      | —                                                                                   | No setup.                                                                                                                                                                             |
| `code_execute`                      | A code sandbox service (`CODE_SANDBOX_URL`, optional `CODE_SANDBOX_API_KEY`)        | Platform-level env var — set by the platform team for managed cloud; self-host operators configure their own sandbox. Without it the tool returns `"Code sandbox is not configured"`. |
| `storage_read` / `storage_write`    | —                                                                                   | No setup. Uses the project's Storage service.                                                                                                                                         |
| `web_search`                        | `EXA_API_KEY` setting                                                               | Studio → Settings → Tools, or `PUT /api/settings/EXA_API_KEY`                                                                                                                         |
| `web_scrape`                        | `FIRECRAWL_API_KEY` setting (and optional `VISION_MODEL` if `include_images: true`) | Studio → Settings → Tools, or `PUT /api/settings/FIRECRAWL_API_KEY`. `VISION_MODEL` defaults to `gpt-4.1-mini`.                                                                       |

If you're enabling these via API rather than the Studio, set them with `PUT /api/settings/{key}`. See [Settings API](/api-reference/settings).

### Custom Tools

Custom tools call your own HTTP endpoints. You define the tool with a name, description, JSON Schema for inputs, an endpoint URL, HTTP method, and optional headers. When the agent decides to use the tool, the platform POSTs the tool arguments as JSON to your endpoint and returns the response to the agent. Responses are capped at 10,000 characters. Timeout is 30 seconds. SSRF validation prevents the agent from calling internal network addresses.

### MCP Servers

MCP (Model Context Protocol) servers let you connect agents to external tool providers. Add an MCP server URL to your agent, and at the start of each run the platform sends a `tools/list` JSON-RPC request to discover available tools. Discovered tools are namespaced (`mcp__{server_name}__{tool_name}`) and added to the agent's tool set alongside builtin and custom tools. Tool calls are executed via `tools/call` JSON-RPC requests. Timeout is 30 seconds for both discovery and per tool call.

**Transport:** the platform's MCP client speaks JSON-RPC over **HTTP POST** to the server URL. (The DB schema accepts a `transport` field with default `http`; other values like `sse` are stored but not consumed by the current client.) Use a server that exposes JSON-RPC over HTTP; most MCP server frameworks do this by default.

**Headers:** any `headers` you configure on the server (typically for auth) are sent on both `tools/list` and `tools/call` requests. There's no per-call header injection.

**enabled flag:** if you set `enabled: false` on an MCP server, discovery skips it entirely: its tools aren't added and no `tools/call` requests fire. Use this to temporarily disconnect a server without removing it.

**Failure handling:** if `tools/list` fails (network error, non-200, JSON-RPC error), the agent run **continues without that server's tools** (fail-open). The failure is logged but not surfaced as a run error.

**Tool annotations:** the platform reads `annotations.readOnlyHint`, `destructiveHint`, and `openWorldHint` from each discovered tool and uses them for concurrency / safety planning. A `readOnlyHint: true` tool may be called in parallel with other read-only tools.

### Knowledge Base Search Tool

When you link a knowledge base to an agent, the platform automatically creates a knowledge\_search tool. The agent can call this tool with a natural language query to search the KB using whatever retrieval strategy is configured (vector, hybrid, full-text, or tree search). If multiple KBs are linked, a single knowledge\_search tool is created with a knowledge\_base\_names filter parameter so the agent can target specific KBs. The search tool is concurrency-safe and read-only, so it runs in parallel with other safe tools.

## Sessions & Memory

Every agent conversation happens within a session. A session stores a sequence of runs, each containing the user input, assistant response, tool calls, tool results, and usage statistics. When you pass a session\_id with a new message, the platform loads all completed runs from that session and reconstructs the full message history for the LLM. Sessions persist until explicitly deleted, enabling long-running multi-turn conversations. New sessions are created automatically if no session\_id is provided; the start SSE event returns the generated session\_id for future use.

## Hooks & Middleware

Hooks let you intercept agent execution at **discrete lifecycle boundaries** with three types of middleware: HTTP webhooks, rule-based policies, and human approval gates. Each hook is configured with an `event` (when it fires), a `type` (what it does), an optional `matcher` (which tool to target by name), and a `config` object.

| Event           | When it fires                                                                                          | Can block?                                         | Can modify?                                        |
| --------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------- | -------------------------------------------------- |
| `OnRunStart`    | Before the run begins processing                                                                       | Yes (fails the entire run)                         | No                                                 |
| `PreToolUse`    | Before each tool execution                                                                             | Yes (returns an error to the LLM)                  | Yes (`modified_input` replaces the tool arguments) |
| `OnDelegation`  | Before a delegate-tool call, in multi-agent setups (fires after `PreToolUse`, only for delegate tools) | Yes (blocks the delegation)                        | No                                                 |
| `PostToolUse`   | After each tool execution                                                                              | No                                                 | Yes (`modified_output` replaces the tool result)   |
| `PreResponse`   | After the ReAct loop, before returning the final content                                               | Yes (replaces the response with a blocked message) | Yes (`modified_output` replaces the final content) |
| `OnRunComplete` | After successful completion (fire-and-forget)                                                          | No                                                 | No                                                 |

| Hook type  | Behavior                                                                                                                                                                                                                                                                                     |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `http`     | POSTs the event payload to a webhook URL and applies the JSON response (see contract below). Fail-open on any error (non-200, timeout, or unreachable). Timeout defaults to 5s (`config.timeout_seconds`); custom `config.headers` are sent with the request, and the URL is SSRF-validated. |
| `rule`     | Evaluates conditions locally against the tool arguments (or, for output events, `{"output": ...}`). Supports operators `CONTAINS`, `STARTS_WITH`, `MATCHES` (regex), and `IN`. First matching deny rule wins.                                                                                |
| `approval` | Pauses execution and emits an `approval_requested` SSE event. Blocks until the approve endpoint is called or `config.timeout` (default 300s) expires.                                                                                                                                        |

<Warning>
  **Hooks are not stream middleware.** Every event fires at a lifecycle boundary; they do not see the token stream chunk-by-chunk. Even `PreResponse` runs *after* the ReAct loop completes, on the fully assembled content; by then a streaming client has already received every `chunk` event, so a hook cannot rewrite, inject, or intercept tokens mid-stream. If you need to transform streamed output (e.g. rewriting inline markers into rich content as it flows to the client), do it in your own application or proxy layer that sits between Powabase's SSE stream and your end users, not in a hook.
</Warning>

### HTTP hook contract

When an `http` hook fires, Powabase sends a `POST` to `config.url` with this body:

```json theme={null}
{
  "event": "PreToolUse",
  "tool_name": "database_query",
  "data": { "query": "SELECT ..." },
  "output": "..."
}
```

`tool_name` is `""` for non-tool events (`OnRunStart`, `PreResponse`, `OnRunComplete`). `data` carries the event's input: the tool arguments for tool events, `{"message": "<user input>"}` for `OnRunStart`, `{}` for `PreResponse`. `output` is present only when there is one (`PostToolUse` → the tool result, `PreResponse` → the assembled content).

Your endpoint must return `200` with a JSON body; any other status (or a timeout/error) is treated as **allow** (fail-open):

```json theme={null}
{
  "action": "allow",
  "message": "optional reason, surfaced when action is deny",
  "modified_input": { "query": "SELECT ... LIMIT 100" },
  "modified_output": "redacted result"
}
```

* `action` defaults to `"allow"`; return `"deny"` to block (only on blockable events; see the table).
* `modified_input` replaces the tool arguments (`PreToolUse` only).
* `modified_output` replaces the tool result (`PostToolUse`) or the final content (`PreResponse`).

### Hook record fields

A hook is created with `event` and `type` (both required) and a `config` object (required, may be `{}`). Optional: `matcher` (a tool name; omit to match all tools), `enabled` (default `true`), and `position` (an integer that orders hook execution, default `0`). Hooks are immutable once created: to change one, delete it and add a new one. See the [Agents API reference](/api-reference/agents#hooks) for the CRUD endpoints.

<Note>
  The API stores `event` and `type` exactly as sent and does **not** validate them against the lists above. A hook with an unrecognized event or type is created successfully but **never fires**, so a typo fails silently. Use the exact values documented here.
</Note>

## Human-in-the-Loop Approval

The approval flow is implemented as a PreToolUse hook of type approval. When the agent tries to call a matching tool, execution pauses: the SSE stream emits an approval\_requested event with the tool name and arguments, then the thread blocks waiting for a decision. Your application calls POST /api/agents/runs/\{run\_id}/approve with \{approved: true} or \{approved: false}. On approval, the tool executes normally and the stream resumes. On rejection, the tool call is skipped and the agent receives a denial message so it can choose an alternative approach. If no decision arrives within 300 seconds (configurable), the tool call is blocked with a timeout error.

<Tip>
  **Scope approval to specific tools**

  Set the hook's matcher field to a specific tool name (e.g., "database\_write") to only require approval for that tool. Omit matcher to require approval for all tool calls.
</Tip>

## Streaming

The streaming endpoint (POST /api/agents/\{id}/run/stream) returns Server-Sent Events for the entire run lifecycle. When tools are present, the agent runs the full ReAct loop and events are emitted as they occur. When no tools are assigned, the agent streams LLM tokens directly as chunk events. If the client disconnects mid-stream, the platform detects the GeneratorExit, sets the abort signal to stop the agent, and spawns a background thread to persist the partial run.

## Sync (Non-Streaming) Run

The sync endpoint (POST /api/agents/\{id}/run) runs the agent without streaming and returns the full response as JSON. In this mode, no tools are loaded and no ReAct loop runs: the agent makes a single LLM call with the conversation context. This is useful for simple question-answering where tool use isn't needed.

## Limits & Safeguards

| Constraint                  | Default           | Notes                                                                                 |
| --------------------------- | ----------------- | ------------------------------------------------------------------------------------- |
| Max ReAct steps             | 25                | Configurable per run. On the final step, tools are withheld to force a text response. |
| Doom loop detection         | 3 identical calls | If the last 3 tool calls have the same name and arguments, the run fails.             |
| Output truncation recovery  | 3 retries         | If the LLM output is truncated, the agent retries with a continuation prompt.         |
| Context compaction failures | 3 attempts        | After 3 failed compaction attempts, the agent stops trying to compact.                |
| LLM retries                 | 3                 | Automatic retry on transient LLM errors.                                              |
| Custom/MCP tool timeout     | 30 seconds        | Per-tool execution timeout.                                                           |
| Tool result truncation      | 50,000 chars      | Results beyond this are truncated. KB search and delegation have no limit.            |
| Approval timeout            | 300 seconds       | Configurable via hook config. Blocks tool execution until decision arrives.           |
| Max orchestration depth     | 3                 | Prevents infinite recursive delegation between agents.                                |

## Next Steps

<CardGroup cols={2}>
  <Card title="Build an Agent" href="/guides/build-agent">
    Create an agent, assign tools, and start chatting.
  </Card>

  <Card title="Advanced Agent Config" href="/guides/advanced-agent-config">
    Add MCP servers, hooks, and approval flows.
  </Card>

  <Card title="Multi-Agent Orchestration" href="/concepts/orchestrations-concept">
    Coordinate multiple agents for complex tasks.
  </Card>

  <Card title="Agents API Reference" href="/api-reference/agents">
    Full endpoint documentation.
  </Card>
</CardGroup>
