Documentation Index
Fetch the complete documentation index at: https://docs.xpander.ai/llms.txt
Use this file to discover all available pages before exploring further.
@on_task registers a function as the agent’s task executor. The decorated function runs every time the platform dispatches a task to this worker: over SSE in production, and via the embedded HTTP server (POST /invoke on port 59321) for local invocations and Cloud Run-style integrations.
The runtime auto-detects whether your function is a regular handler (returns Task) or a streaming handler (async generator that yields TaskUpdateEvent).
Decorator forms
| Parameter | Type | Default | Description |
|---|---|---|---|
configuration | Configuration | None | SDK config for the underlying Events module. Falls back to env vars. |
test_task | LocalTaskTest | None | A simulated task to run locally. The runtime invokes the handler once with this task and exits, instead of subscribing to the SSE stream. |
Required signature
The handler must accept exactly one parameter namedtask. Either positional or keyword form works.
Handler types
The decorator detects which kind you wrote by inspecting whether your function is an async generator.Regular handler
Sync or async, returns the (possibly mutated)Task.
task.asave() yourself unless you need to checkpoint mid-handler.
Streaming handler
Async generator that yieldsTaskUpdateEvents. Used for token-by-token streaming responses.
A streaming handler emits two kinds of events. Each token (or text chunk) from the LLM is yielded as a TaskUpdateEventType.Chunk event, which the platform forwards to subscribers in real time. After the loop ends, the handler yields one final TaskUpdateEventType.TaskFinished event carrying the completed Task object. The platform uses that final event to mark the task complete and persist its result.
POST /invoke only: the SSE listener wraps them in an adapter that consumes the generator and tracks the final Task from the TaskFinished event.
What the runtime does
When you decorate a function with@on_task:
- Validates the signature (must accept
task). - Detects handler type (regular vs streaming).
- Starts an embedded HTTP server on port
59321forPOST /invoke(always, in a daemon thread). Override the port withXPANDER_STREAMING_PORT. - Registers an SSE listener with the
Eventsmodule. The listener:- Reads
XPANDER_API_KEY,XPANDER_ORGANIZATION_ID,XPANDER_AGENT_IDfrom env (raisesModuleExceptionif any are missing). - Subscribes to the platform’s task-dispatch stream.
- Acquires a semaphore (
max_sync_workers=6by default) before dispatching. - Sets task status to
Executingbefore calling your handler. - Calls your handler.
- Persists the returned task; if your handler raised, marks the task
Errorwith the exception string as the result. - Reports metrics if
task.tokensis set. - Re-runs the handler with a continuation prompt if deep planning is enabled (multi-step task plans tracked in
task.deep_planning) and items remain incomplete, up toMAX_PLAN_RETRIES = 5. Pre-retry the runtime triggers session compaction.
- Reads
Test mode
Passtest_task (or use the CLI override) to invoke the handler once locally instead of subscribing to the platform.
With a LocalTaskTest
From the CLI
The decorator also responds to--invoke / --prompt arguments on sys.argv, so you can run any @on_task-decorated module with:
| Flag | Purpose |
|---|---|
--invoke | Switches the runtime into single-task test mode. |
--prompt | Required when --invoke is set. Becomes task.input.text. |
--output_format | One of json, markdown, text. |
--output_schema | JSON-encoded schema string. |
Runtime caveats
- One handler per process. The decorator subscribes to the SSE stream on import. Decorating multiple functions in the same module isn’t useful: the most recently registered handler wins. Use multiple Python processes (or container instances) if you need to host different agents.
- HTTP server starts immediately. The
POST /invokeendpoint is up before the SSE listener finishes connecting. If port59321is in use the runtime logs a warning and continues. Override the port with theXPANDER_STREAMING_PORTenv var. - Synchronous handlers are dispatched on a thread pool. A thread pool of
max_sync_workers=6handles sync handlers concurrently. Async handlers run on the event loop.
Errors raised by the decorator
TypeError: handler doesn’t accepttask, or returns/yields the wrong type.ModuleException: required env vars are missing when the SSE listener starts.

