Skip to content

Background Tasks

TasksRuntime runs coroutines in the background while an agent keeps working, and hands you a TaskHandle to check status or collect the result later. It's the primitive behind long-running or fire-and-forget work that shouldn't block the main run.

Submitting work

from neurosurfer.agents.runtime.tasks_runtime import TasksRuntime

runtime = TasksRuntime(max_concurrent=8)

handle = runtime.submit(
    do_expensive_thing(),          # a coroutine
    name="ingest-docs",            # optional; enforces no-overlap by name
    description="Ingesting the docs corpus",
)

submit returns a TaskHandle, or None if a guard blocked it — either the global max_concurrent cap is reached, or a task with the same name is already live (the no-overlap guard). A blocked coroutine is closed cleanly, so you never get an "coroutine was never awaited" warning.

The handle

handle.done            # bool — has it finished?
handle.status          # HandleStatus — running / done / error / cancelled
handle.error           # the exception, if it failed
handle.result_value    # the return value once done (without awaiting)
await handle.result()  # await completion and get the value
handle.cancel()        # request cancellation

Inspecting and shutting down

runtime.active()             # handles still running
runtime.all()                # every handle this runtime has seen
runtime.is_live("ingest")    # is a named task currently running?
runtime.get_live("ingest")   # the live handle for a name, or None
runtime.cancel("ingest")     # cancel a named task
await runtime.shutdown()     # cancel + await all outstanding tasks

Name tasks you don't want to double-start

Passing name= makes submit idempotent-ish: while one "ingest-docs" is live, a second submit with the same name is refused (returns None) instead of running a duplicate.

When to use it

  • Kick off a slow ingest, export, or fetch and keep the conversation responsive.
  • Run several independent jobs under a concurrency cap.
  • Coordinate work that outlives a single agent turn.

For parallelism that's part of the reasoning (analyse N things and aggregate), prefer sub-agents — they nest in the trace and share guardrails. Reach for TasksRuntime when the work is plumbing rather than agent reasoning.