Edit

Long-running agent API reference (preview)

This reference lists the AgentServer SDK surface for building long-running, resilient hosted agents: the resilient task primitives, the resilient Responses options, and the streaming registry. For the concepts behind these APIs, see Resilience for long-running hosted agents. For task-oriented walkthroughs, see the how-to guides.

Note

Long-running agents are in preview. The APIs on this page are from the azure-ai-agentserver packages and are subject to change.

Packages

The APIs on this page require at least the following package versions. The Python packages are generally available; the .NET packages are in preview—install them with --prerelease.

Protocol Python (minimum version) .NET (minimum version)
Core (tasks, streaming, storage) azure-ai-agentserver-core ≥ 2.0.0 Azure.AI.AgentServer.Core ≥ 1.0.0-beta.28
Responses azure-ai-agentserver-responses ≥ 2.0.0 Azure.AI.AgentServer.Responses ≥ 1.0.0-beta.8
Invocations azure-ai-agentserver-invocations ≥ 1.0.0 Azure.AI.AgentServer.Invocations ≥ 1.0.0-beta.6

Note

Use the latest available version. The versions listed are the minimums that include the resilient task, resilient Responses, and streaming-registry APIs described here.

Resilient tasks

The task primitives make a unit of work crash-resilient. Declaring a task automatically enables the startup recovery scan.

Declare a task

from datetime import timedelta
from azure.ai.agentserver.core.tasks import task, multi_turn_task, TaskContext, RetryPolicy

@task(name="summarize", timeout=timedelta(minutes=10), retry=RetryPolicy())
async def summarize(ctx: TaskContext[str]) -> str:
    ...

@multi_turn_task(name="chat", steerable=True)
async def chat(ctx: TaskContext[dict]) -> dict:
    ...
Decorator Purpose
@task(*, name, title=None, timeout=None, retry=None) One-shot resilient work. One input produces one result.
@multi_turn_task(*, name, title=None, timeout=None, retry=None, steerable=False) A conversation chain that stays suspended between turns. Set steerable=True to queue a new turn behind the active one.

Run a task

# One-shot: run returns the output directly.
result = await summarize.run(input="...")

# Multi-turn: same task_id resumes the chain; if_last_input_id enforces ordering.
r = await chat.run(task_id="conv-7", input={"msg": "hi"}, if_last_input_id=prev_id)

# Fire-and-track: start returns a TaskRun handle.
run = await chat.start(task_id="conv-7", input={"msg": "hi"})
Call Returns Notes
await task.run(*, input, task_id=None, if_last_input_id=None) Output Runs to completion and returns the result.
task.start(*, input, task_id=None, if_last_input_id=None) TaskRun[Output] Returns a handle you can await or cancel.

TaskContext

The handler receives a TaskContext describing the current attempt.

Python (TaskContext[Input]) C# (TaskContext<T>) Description
input Input The value the caller passed. Persisted before the handler runs.
task_id TaskId The durable work identity.
input_id InputId The per-turn/per-input identity.
entry_mode EntryMode fresh, resumed, or recovered.
metadata Metadata Small durable key-value state (TaskMetadata).
retry_attempt RetryAttempt 0 on the first try.
RecoveryCount Number of crash recoveries for this attempt.
is_steered_turn IsSteeredTurn True if this turn was promoted from the steering queue.
pending_input_count PendingInputCount How many newer turns are queued.
cancel Cancellation Cooperative cancel signal (any cause).
cancel_requested CancelRequested Cause: an explicit cancel was requested.
timeout_exceeded TimeoutExceeded Cause: the per-task timeout fired.
shutdown Shutdown The container is shutting down.
await ctx.exit_for_recovery() await ctx.ExitForRecoveryAsync() Defer unfinished work; leaves the record in progress for a later lifetime.

entry_mode / EntryMode values:

Value Meaning
fresh / Fresh First execution for this (task_id, input_id).
resumed / Resumed A subsequent turn of an existing chain.
recovered / Recovered A previous lifetime ran this attempt and didn't finish; re-invoked with the persisted input.

TaskRun

The handle returned by start / StartAsync.

Python (TaskRun[Output]) C# (TaskRun<T>) Description
task_id TaskId The work identity.
input_id InputId The input identity.
metadata Metadata Live metadata reference while in flight.
is_queued IsQueued True if this input was queued behind an active steerable turn.
await run.result() await run.GetResultAsync() Await the output.
await run.cancel() await run.CancelAsync() Cooperatively cancel.
await run run.GetAwaiter() Awaitable directly.

Retry policy

Retries apply to handler failures, not to crash recovery. Crash recovery re-enters the same attempt and doesn't consume retry budget.

from azure.ai.agentserver.core.tasks import RetryPolicy

RetryPolicy(
    initial_delay=timedelta(seconds=1),   # base delay
    backoff_coefficient=2.0,              # multiplier per attempt (>= 1.0)
    max_delay=timedelta(seconds=60),      # cap
    max_attempts=3,                       # total tries, including the first
)
RetryPolicy.no_retry()                    # single attempt

Delay formula: min(initial_delay * backoff_coefficient ** attempt, max_delay).

Task status and exceptions

TaskStatus (C#) / status values: Pending, InProgress, Suspended, Completed.

Python exception C# exception Raised when
TaskConflictError TaskConflictException A concurrent non-steerable start hit an in-flight task.
TaskCancelled TaskCancelledException The run was cancelled.
TaskFailed TaskFailedException The handler failed terminally.
TaskDeferred TaskDeferredException Work was deferred for recovery.
SteeringQueueFull SteeringQueueFullException The steering queue is at capacity.
LastInputIdPreconditionFailed LastInputIdPreconditionFailedException if_last_input_id / IfLastInputId didn't match.
InputTooLarge InputTooLargeException The input exceeded the task payload limit (~10 MiB).

Force-enable recovery

Declaring a task auto-enables the recovery scan. Force-enable it when tasks are registered lazily after host startup.

from azure.ai.agentserver.core.tasks import set_resilient_tasks_enabled
set_resilient_tasks_enabled(True)   # call at import time, before host startup

Resilient Responses options

Set on ResponsesServerOptions when constructing the host. Both flags default to off.

from azure.ai.agentserver.responses import ResponsesAgentServerHost, ResponsesServerOptions

app = ResponsesAgentServerHost(
    options=ResponsesServerOptions(
        resilient_background=True,       # re-invoke stored background responses after a crash
        steerable_conversations=True,    # queue a new turn instead of 409 conversation_locked
        default_fetch_history_count=100,
        sse_keep_alive_interval_seconds=None,
        shutdown_grace_period_seconds=10,
    ),
)
Python C# Default Description
resilient_background ResilientBackground False Re-invoke the handler on restart. Applies only to store=true, background=true responses.
steerable_conversations SteerableConversations False Queue a concurrent turn instead of rejecting it.
default_fetch_history_count DefaultFetchHistoryCount 100 Max history items hydrated per turn.
sse_keep_alive_interval_seconds None SSE keep-alive interval.
shutdown_grace_period_seconds 10 Seconds to wait for in-flight work on shutdown.

Recovery-aware ResponseContext

Available on the response handler's context when resilience is enabled.

Python C# Description
context.is_recovery IsRecovery True when the handler was re-invoked after a crash.
context.persisted_response PersistedResponse The last durably checkpointed response snapshot.
context.conversation_chain_metadata ConversationChainMetadata Small cross-turn references and watermarks.
ConversationChainId Stable identity of the conversation chain.
context.is_steered_turn IsSteeredTurn True if this turn was promoted from the steering queue.
context.pending_input_count PendingInputCount Newer turns waiting in the queue.
await context.exit_for_recovery() await ExitForRecoveryAsync() Defer for recovery on shutdown; leaves the response in_progress.

Streaming registry

Pick one backing at startup, then look streams up by a per-turn id.

from azure.ai.agentserver.core.streaming import streams

streams.use_in_memory_live()                                        # no replay, no restart survival
streams.use_in_memory_replay(cursor_fn=lambda e: e["n"], ttl_seconds=600)
streams.use_file_backed_replay(storage_dir=Path("/streams"),
                               cursor_fn=lambda e: e["n"])

stream = await streams.get_or_create(invocation_id)
await stream.emit({"n": 0, "delta": "..."})
async for event in stream.subscribe(after=last_cursor):
    ...
await stream.close()
Configurator Replay Survives restart
use_in_memory_live() No No
use_in_memory_replay(*, cursor_fn=None, ttl_seconds=None) Yes, within TTL No
use_file_backed_replay(*, storage_dir=None, cursor_fn=None, ttl_seconds=None, serializer=None, deserializer=None) Yes Yes

Registry operations: await streams.get_or_create(id), await streams.delete(id). Pass cursor_fn to enable subscribe(after=...) and last_cursor().

Streaming exceptions: EventStreamClosedError/EventStreamClosedException, EventStreamNotFoundError/EventStreamNotFoundException, and the base EventStreamError/EventStreamException.