Documentation Index
Fetch the complete documentation index at: https://docs.agentium.in/llms.txt
Use this file to discover all available pages before exploring further.
Async HandleId Pattern
The problem
Some tool calls take 5–60 seconds: video rendering, batch jobs, slow third-party APIs, large file downloads. If the tool blocks synchronously, the agent loop blocks too. That’s bad:- The user sees a frozen UI.
- Streaming providers may close the connection on timeout.
- The agent can’t do anything else while it waits — it can’t even tell the user “this will take a minute”.
defineAsyncTool
renderVideo({ script: "..." }), the tool returns immediately:
execute() is running in a fire-and-forget background promise. When it finishes, the result is cached on RunContext.sessionState["__asyncHandles"] keyed by the handle.
Config
| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | required | Tool name |
description | string | required | The framework auto-appends "[Async] Returns a handle..." to help the model understand the contract |
parameters | z.ZodObject | required | Zod schema (same as defineTool) |
execute | (args, ctx) => Promise<string | ToolResult> | required | The slow work. Runs in the background. |
ttlSeconds | number | 600 | After this many seconds the cached result is dropped. Subsequent pollResult calls return status: "expired". |
createPollResultTool()
Add this once to your agent’s tool list. The LLM calls it to retrieve results.
pollResult parameters
pollResult return values
| status | When | Other fields |
|---|---|---|
"pending" | Background work still in flight | handle |
"done" | Work completed successfully | handle, result: <execute return value> |
"error" | Background work threw | handle, error: <message> |
"expired" | Result was older than ttlSeconds | handle |
"not-found" | Handle doesn’t exist (typo, wrong session) | handle |
waitMs semantics
pollResult(handle, waitMs: 10000) polls every 100ms for up to 10 seconds. If the result arrives mid-poll, it returns immediately with status: "done". If the deadline expires with the work still pending, it returns status: "pending" (the LLM should call again).
Capped at 30000ms to prevent the LLM from blocking forever.
Complete example
Internal storage
Handles live onRunContext.sessionState["__asyncHandles"] as a Map<string, HandleEntry>. Each entry tracks:
RunContext. For multi-run handle persistence, serialize sessionState between runs via your session manager.
Composition with other patterns
Async + Memory Pointers
When the async result is itself huge:pollResult returns status: "done", result: <huge text>, the auto-pointer converter wraps that result in an art: pointer. The agent then calls getArtifact(pointer) if it needs the bytes.
Async + BullMQ background queue
For work that needs to survive process restart, push the real execution to BullMQ via@agentium/queue and store the BullMQ job ID as the handle:
When to use
- API calls > 5 seconds
- Video / audio / image generation
- Large data downloads
- Anything that benefits from the LLM doing something else while waiting
When NOT to use
- Sub-second tools — the handle overhead isn’t worth it
- Tools whose result the LLM needs to reason about immediately
- Tools called inside a tight workflow loop where everything is sequential anyway
See also
- Memory Pointer Pattern — pair with this for large async results
@agentium/queue— durable BullMQ-backed jobs- Tool Loop Detection — catch agents that poll forever