Skip to main content
Use @task for application-owned queue work. A task is not model-visible; an Agent Tool decides when to defer it.

Define and call a task

From the agent root, create a compilable task starter:
Use --project <agent-root> when running the command from elsewhere. The generated file is suitable when no other module imports the task; use the shared-library pattern below when tools also import it. When a tool needs the task callable, define it once in lib/ and re-export it from tasks/:
If nothing imports the task, you can define it directly in tasks/<name>.py. The file must export exactly one same-named @task callable.

Control a job

The returned TaskHandle supports await handle.status(), await handle.cancel(), and await handle.result(). A handle belongs to the active compiled runtime; there is no public API to reconstruct or list handles by ID. Inside @tool(durable=True), Harnest derives a replay-stable idempotency key when you omit one. Keep task effects idempotent because workers can retry. Use await task.defer(...) without handle.result() for fire-and-forget work. Await a result only from an async @tool(durable=True) when the agent must resume after the Task finishes. An unfinished result requires a Harnest-owned checkpointer.

Runtime requirements

Configure Task and cron storage before serving queued work. That guide shows how to set up a database connection in a shared factory and provide its configuration through the environment. Every replica can run a worker against the same provider. Run these commands from your agent folder after configuring storage:
The selected provider initializes its storage during runtime startup. Compiling a task definition or calling its body directly does not require task storage; starting queued execution does. Missing storage produces an actionable setup error. Calling .defer() from an ordinary imported module without an active compiled runtime raises TaskUnavailableError; call the task body directly for isolated unit tests.

Start a fresh agent session

A task can invoke the compiled root agent without an HTTP call:
tasks/review_report.py
key is optional. When supplied, Harnest derives the same opaque child session and invocation identities on a task retry. User-deferred tasks inherit the calling user and public metadata. Static schedules use Harnest’s automation identity; dynamic schedules run as their creating user with fresh context and empty grants. Calls still pass through storage, plugins, lifecycle, tools, and the selected framework runtime. When .defer() runs under an Agent Runtime Principal, Harnest stores only its permission names and reconstructs a fresh principal for each worker attempt. Agent calls made by the Task inherit that restriction unless trusted task code passes a narrower principal. A Task deferred without an active principal preserves compatibility behavior. Durable external waits return a typed in_progress response. Human approvals and client tools fail closed because their continuation is process-local after the task returns.
An unfinished handle.result() requires an async @tool(durable=True) and a Harnest-owned checkpointer. Harnest resumes framework execution; it does not restore a Python stack.
See Durable execution for cross-replica resume behavior.

Test Task behavior

Call the decorated function directly to unit-test its business logic without a database. Exercise .defer(), retries, cancellation, and durable resume through a compiled smoke test with your chosen persistent provider so the real queue and checkpoint boundaries run together. Custom providers can use the shared conformance suite.