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

# Custom tool ctx

> How sandboxed custom code tools call built-in tools via the injected ctx object.

# Custom tool ctx

Every custom **code** tool runs in a sandbox and receives two parameters: `input` (your declared parameters) and `ctx` (the platform's tool context). Through `ctx.tools.<name>(...)` your code can call any [built-in tool](/builtin-tools) — `send_email`, `web_search`, `calculate`, `surface_file`, and the rest — without re-implementing them.

This page covers the `ctx` object itself. For the result-shape contract (what your `handle()` return value does — what the agent sees, what the user sees, `_render` / `_interactive` / `_llm`), see [Custom tools → Tool results](/sidebar-menu/tools#tool-results-what-the-agent-sees-vs-what-the-user-sees).

## Handler shape

Every code tool must export a `handle(input, ctx)` function.

### JavaScript

```js theme={null}
export async function handle(input, ctx) {
  // input  — the parameters you defined on the tool
  // ctx    — the platform-injected tool context
  return { ok: true };
}
```

Either `export async function handle(...)` or plain `async function handle(...)` works — the sandbox accepts both.

### Python

```python theme={null}
async def handle(input, ctx):
    return { 'ok': True }
```

Sync `def handle(input, ctx):` also works; coroutines are awaited.

## The ctx object

| Field                     | Type           | Description                                                                                                               |
| ------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `ctx.agentId`             | string         | The agent's stable id.                                                                                                    |
| `ctx.agentName`           | string         | Human-readable agent name.                                                                                                |
| `ctx.powerUserId`         | string \| null | The calling subscriber id, or `null` when the caller is the agent owner.                                                  |
| `ctx.toolCallId`          | string         | The LLM tool-call id for this invocation. Useful when correlating logs.                                                   |
| `ctx.conversationId`      | string \| null | The chat session id, if available.                                                                                        |
| `ctx.tools.<name>(input)` | function       | Invoke any enabled [built-in tool](/builtin-tools). Returns a promise that resolves to the tool's normal result envelope. |
| `ctx.audit(event, data?)` | function       | Append a structured audit row to the activity log, scoped to this tool call.                                              |

## Calling a built-in tool

```js theme={null}
export async function handle(input, ctx) {
  const recipient = input.email;
  const search = await ctx.tools.web_search({
    query: `${input.topic} site:${input.domain}`,
    count: 3,
  });

  await ctx.audit('found_sources', { count: search.length });

  return await ctx.tools.send_email({
    to: recipient,
    subject: `Re: ${input.topic}`,
    body_text: `Hi — here's what we found:\n\n${JSON.stringify(search, null, 2)}`,
  });
}
```

Each `ctx.tools.<name>` call is dispatched server-side with the same validation, allowlists, recipient checks and audit logging that apply when the LLM calls the tool — all of it applies identically whether the LLM or your code is the caller.

## Errors

If a built-in tool returns an error envelope (`{ error, details }`), `ctx.tools.<name>(...)` resolves with that envelope — it does **not** throw. Treat any object with an `error` field as failure:

```js theme={null}
const result = await ctx.tools.send_email({ to: '...', subject: '...', body_text: '...' });
if (result.error) {
  return { ok: false, reason: result.error, details: result.details };
}
return { ok: true, message_id: result.message_id };
```

If the platform connection itself fails (for example a transient network problem, or the call could not be dispatched) the call **does** throw — wrap in `try/catch` if you need resilience.

## Limits

* **30-second** wall-clock for the whole sandbox execution.
* **50 cross-tool calls** per invocation. Recursion (a tool calling itself directly or indirectly) hits this guard quickly and aborts with a structured error.
* Each invocation is isolated and any credentials do not outlive the call.
* Call built-in tools via `ctx.tools` so the platform handles audit logging and cost accounting for you.

## Why this exists

Without `ctx`, every custom tool would need to re-implement what the platform already does — send email, sign download URLs, validate render specs, manage interaction rows. With `ctx`, your code composes the platform's primitives the same way the LLM does — and inherits the same guardrails for free.
