Function tools
TypeScript or Python tools that run sandboxed, return values, write files, and log with levels.
A function tool is code the agent can call: look up the order, sync the calendar, transform the payload. You write it in TypeScript or Python, the platform runs it in a sandbox, and the turn records what it did.
Writing one
A function tool has two parts. Inputs is a JSON Schema for the arguments
the model supplies. The body is plain statements with no function
signature, and input (the arguments) and runtime (the caller) are already
in scope.
{
"type": "object",
"properties": { "city": { "type": "string", "description": "City name" } },
"required": ["city"]
}A tool with no Inputs is called with no arguments.
TypeScript is the default. The body runs inside an async function, so
await and return both work:
const res = await fetch(`https://api.example.com/weather?q=${encodeURIComponent(input.city)}`);
if (!res.ok) throw new Error(`Weather service returned ${res.status}`);
const data = await res.json();
return { city: input.city, tempC: data.temp };Python runs as a script. There's no function to return from, so assign
result instead:
rows = [r for r in input["rows"] if r["amount"] > 0]
result = {"count": len(rows), "total": sum(r["amount"] for r in rows)}A string result reaches the model as is, and anything else is sent as JSON.
A thrown error comes back as Function execution failed: … with its message,
and the model can read it and react. Bodies saved in the older
async (input, runtime) => { … } style still run.
runtime
runtime describes who is calling. It's verified by the platform, holds no
secrets, and is never shown to the model as an input.
| Field | Value |
|---|---|
runtime.user.id, runtime.user.email | The caller |
runtime.user.isAnonymous | true for anonymous visitors |
runtime.user.roles | The caller's roles |
runtime.user.metadata | Free-form user metadata, for example from SSO |
runtime.tenantId | The organization |
Network
TypeScript bodies get a fetch that goes through the egress proxy. It reaches
only the hosts allowed under Outbound access,
and a new org allows none. The response has ok, status, headers,
text(), and json(). Python bodies have no network.
Files
To return a file, write it to the output directory. It's attached to the reply as a download, and the model is told which files arrived.
- Python:
output_path("report.pdf")gives the path, andOUTPUT_DIRis the directory.read_jsonis also available. - TypeScript:
outputPath("report.pdf")andOUTPUT_DIR.
Only top-level files are collected: up to 10 per call, 10 MB each, and 25 MB
in total. A turn delivers at most 10 files across all its tools. The sandbox
image has the same document libraries run_code uses (Tools).
Limits
| Limit | Value |
|---|---|
| Time | 30 s per call. A failed call is never retried. |
| Memory | 512 MB |
| CPU | 0.5 core |
| Processes | 64 |
| Logs | 50 log() lines per call, 4 KB each |
| Output files | 10 per call, 10 MB each, 25 MB total |
Memory, CPU, and process limits are deployment defaults that your operator can change.
Logging from a tool
Call log(message, data?, level?). The level is debug, info, warn, or
error, and the default is info. Each line lands in the
Tracing Logs tab under the calling trace, tagged
[tool:<name>], with data attached as a structured payload:
log('order synced', { orderId: input.orderId, lines: items.length });
log('retrying upstream', undefined, 'warn');
log('payment failed', { reason }, 'error');log("retrying upstream", level="warn")Logging is opt-in, and only log() is forwarded. console.log and print
output is not. After 50 lines, the rest are dropped with a warning. A line
over 4 KB drops its data first, then has its message cut.
Running in the browser
Turn on Run in the user's browser (executeOnClient) and a TypeScript
body runs in the chat page instead of the sandbox. Use it to reach things only
the page has: navigator.geolocation, the clipboard, the real timezone, or
localStorage.
const pos = await new Promise((resolve, reject) =>
navigator.geolocation.getCurrentPosition(resolve, reject),
);
return { lat: pos.coords.latitude, lon: pos.coords.longitude };- It isn't isolated. The code runs with the viewer's session on the page's origin, so the switch lives on the tool, out of chat users' reach.
runtimehasuserandisClient: true, but notenantId.runtime.host.call('method', args)calls the page that embeds the chat (see Channels). Inside Assivy itselfruntime.hostis absent.log()and the proxiedfetcharen't available. The time limit is still 30 seconds.- Python bodies can't run in the browser, so the option is hidden for them.
- Failures, including a permission the user denied, come back to the model as errors.
What the turn records
Every tool call lands in the trace: what went in, what came back, and what it said along the way. Open the turn in Tracing and expand the tool span to see the full story, or filter the Logs tab to the trace id and read it as a narrative.
The agent's transcript mode controls how much of this the user sees inline. By default the user sees the answer plus tool summaries. Verbose mode spells out every call, and thinking mode adds reasoning when the provider returns it.