Data Flow

July 10, 2026 ยท View on GitHub

This document describes the request lifecycle from client submission through agent execution to response delivery, including async job management and real-time SSE streaming.

Request Lifecycle

Synchronous requests flow through the NeMo Agent Toolkit FastAPI frontend directly to the agent workflow. Asynchronous requests (deep research) use a Dask cluster for distributed execution with SSE-based progress streaming.

sequenceDiagram
    participant C as Client
    participant API as FastAPI
    participant JS as Job Store
    participant D as Dask Worker
    participant A as Agent Workflow
    participant ES as Event Store
    participant AS as Durable Artifact Store
    participant SSE as SSE Stream

    C->>API: POST /v1/jobs/async/submit
    API->>JS: Create job (SUBMITTED)
    API->>D: Submit task to Dask
    API-->>C: 202 Accepted {job_id}

    C->>API: GET /v1/jobs/async/job/{id}/stream
    API-->>SSE: Open SSE connection

    D->>JS: Update status (RUNNING)
    D->>A: Execute agent workflow

    loop Agent execution
        A->>ES: Store intermediate events
        ES-->>SSE: Push events to client
        SSE-->>C: SSE event data
        opt Successful sandbox execute with artifact capture enabled
            A->>AS: Checkpoint manifest-declared files
            A->>ES: Store artifact.update metadata
            ES-->>SSE: Push artifact.update
            SSE-->>C: File metadata + content URL
        end
    end

    A-->>D: Final result
    opt Artifact capture enabled
        D->>AS: Finalize artifacts (idempotent)
        D->>ES: Store new artifact.update events
    end
    D->>ES: Store final events
    D->>JS: Update status (SUCCESS)
    JS-->>SSE: Push job.status
    SSE-->>C: job.status {status: SUCCESS}

Artifact checkpoints run after successful sandbox execute calls. On a terminal success or failure path, the worker performs one idempotent manifest-plus-directory scan before sandbox cleanup. Cancellation performs that scan only when the provider operation lease is immediately available; otherwise the provider is terminated without waiting and artifacts from earlier checkpoints remain durable.

Async Job States

Jobs progress through the following states:

stateDiagram-v2
    [*] --> SUBMITTED: POST /submit
    SUBMITTED --> RUNNING: Dask worker picks up
    RUNNING --> SUCCESS: Agent completes
    RUNNING --> FAILURE: Unhandled error
    RUNNING --> INTERRUPTED: User cancels
    FAILURE --> [*]
    SUCCESS --> [*]
    INTERRUPTED --> [*]
StateDescription
SUBMITTEDJob created and queued for execution
RUNNINGDask worker is actively executing the agent
SUCCESSAgent completed successfully; final report available
FAILUREAgent encountered an unhandled error
INTERRUPTEDUser requested cancellation using POST /cancel

A background reaper task periodically marks stale RUNNING jobs as FAILURE if they exceed the configured timeout, protecting against ghost jobs from crashed workers.

SSE Event Types

Most lifecycle events use a category.state naming convention aligned with NeMo Agent Toolkit's IntermediateStep structure. The AgentEventCallback (a LangChain callback handler) translates LangChain lifecycle events into this shape. Durable file captures also use artifact.update; rejected candidates use the separate top-level artifact.warning event.

Event TypeCategoryStateDescription
stream.mode----Announces stream mode: polling (catching up), live (real-time), or pubsub (PostgreSQL LISTEN/NOTIFY)
job.statusjob--Job status change; includes status, optional reconnected flag
workflow.startworkflowstartAgent workflow execution begins
workflow.endworkflowendAgent workflow execution completes
llm.startllmstartLLM invocation begins; includes model name and prompt/message count
llm.chunkllmchunkStreaming token chunk from LLM
llm.endllmendLLM invocation completes; includes usage metadata and optional thinking/reasoning
tool.starttoolstartTool execution begins; includes tool name and input
tool.endtoolendTool execution completes; may emit citation_source artifacts for search tools
artifact.updateartifactupdateFile, citation, todo, or output update; durable files contain metadata and a content URL, never bytes
artifact.warning----Durable artifact candidate was rejected; includes its path and the rejection reason
job.updatejobupdateRetry notification when a chain (LLM call) fails and is retried
job.errorjob--Error during job execution
job.heartbeatjob--Periodic heartbeat from Dask worker (every 30s); keeps SSE alive and aids ghost job detection
job.cancelledjob--Job was cancelled by user (emitted from Dask worker on CancelledError)
job.cancellation_requestedjob--Cancellation has been requested for the job
job.shutdownjob--Server is shutting down; client should reconnect

Event Structure

Lifecycle events produced by AgentEventCallback follow the IntermediateStepEvent schema:

{
  "type": "tool.start",
  "id": "uuid-v4",
  "name": "tavily_web_search",
  "timestamp": "2026-02-16T10:30:00Z",
  "data": {
    "input": {"query": "renewable energy GDP impact"},
    "output": null
  },
  "metadata": {}
}

Captured durable files use the same artifact.update envelope as other file updates. Their nested data.type is file, and the payload contains metadata rather than file bytes:

{
  "type": "artifact.update",
  "name": "market-share.png",
  "data": {
    "type": "file",
    "url": "/v1/jobs/async/job/job-uuid/artifacts/artifact-uuid/content",
    "content_url": "/v1/jobs/async/job/job-uuid/artifacts/artifact-uuid/content",
    "file_path": "market-share.png",
    "artifact_id": "artifact-uuid",
    "job_id": "job-uuid",
    "kind": "image",
    "mime_type": "image/png",
    "size_bytes": 184320,
    "sha256": "0000000000000000000000000000000000000000000000000000000000000000",
    "title": "Market share",
    "caption": "Market share by vendor",
    "inline": true
  }
}

An artifact.warning payload instead contains data.path and data.reason for the rejected candidate.

Event-Derived and Durable Artifacts

artifact.update events carry event-derived state used for live UI updates and replay. Their nested data.type is one of the following values. The GET /v1/jobs/async/job/{job_id}/state endpoint reconstructs tool calls, outputs, and citations from those stored events.

Artifact TypeDescription
fileLegacy virtual-filesystem content or durable generated-file metadata with a job-scoped content URL
outputIntermediate output (draft section, summary)
citation_sourceA source URL or reference discovered during research
citation_useAn inline citation placed in the report
todoA research task tracked by TodoListMiddleware

Durable sandbox artifacts are a separate persistence contract for generated files such as charts, CSVs, notebooks, and documents. Capture is opt-in and best-effort. Successful execute calls checkpoint manifest-declared files; success/failure terminal paths perform one final manifest-plus-directory scan, while a busy cancellation skips that scan rather than waiting on the provider. The durable store, not the replayed event, is authoritative for artifact records and bytes. Stored artifact.update events provide metadata-only live and replayed delivery to clients, including the web UI Files tab. A rejected candidate instead emits top-level artifact.warning. List authoritative metadata with GET /v1/jobs/async/job/{job_id}/artifacts and fetch content with GET /v1/jobs/async/job/{job_id}/artifacts/{artifact_id}/content. Refer to the REST API for the complete capture, authorization, retention, and content-serving contract.

Reconnection and Replay

The SSE stream supports seamless reconnection after network interruptions. Every event stored in the EventStore has a monotonically increasing integer ID. Clients track the last received event ID and reconnect using the resume endpoint.

sequenceDiagram
    participant C as Client
    participant API as FastAPI
    participant ES as Event Store

    C->>API: GET /stream
    API-->>C: event id=1 (workflow.start)
    API-->>C: event id=2 (llm.start)
    API-->>C: event id=3 (llm.chunk)

    Note over C,API: Network interruption

    C->>API: GET /stream/{last_event_id=3}
    Note over API,ES: Replay mode: fetch events after id=3

    API-->>C: stream.mode {mode: polling}
    API-->>C: event id=4 (llm.end)
    API-->>C: event id=5 (tool.start)
    API-->>C: event id=6 (tool.end)
    API-->>C: stream.mode {mode: live}
    API-->>C: event id=7 (llm.start)

Replay Behavior

  1. Polling mode: On reconnection, the stream enters polling mode and fetches all events after the provided last_event_id in large batches (up to 10,000 for completed jobs, 1,000 for running jobs) with no polling delay.

  2. Mode transition: Once the polling batch is smaller than the fetch limit (indicating the tail has been reached), the stream emits stream.mode {mode: live} and switches to live polling.

  3. Reconnection flag: The first job.status event after reconnection includes reconnected: true so clients can distinguish reconnection status updates from new status changes.

  4. Completed job replay: If the job already finished before the client reconnects, the stream replays all stored events and immediately sends the terminal job.status event.

Representative API Endpoints

These are the endpoints that drive the core lifecycle shown above. The REST API is the canonical, complete endpoint inventory, including report follow-up, final-report retrieval, durable artifacts, data sources, health checks, and their error and authorization semantics.

MethodEndpointDescription
POST/v1/jobs/async/submitSubmit a new async job
GET/v1/jobs/async/job/{job_id}Get job status
GET/v1/jobs/async/job/{job_id}/streamSSE stream from beginning
GET/v1/jobs/async/job/{job_id}/stream/{last_event_id}SSE stream from event ID (reconnection)
POST/v1/jobs/async/job/{job_id}/cancelCancel a running job
GET/v1/jobs/async/job/{job_id}/stateReconstruct event-derived tool calls, outputs, and citations

Cancellation

When a client sends POST /cancel:

  1. The Job Store sets the job status to INTERRUPTED
  2. A CancellationMonitor running on the Dask worker polls the Job Store at regular intervals (default 1 second)
  3. When the monitor detects INTERRUPTED, it sets an asyncio.Event that the agent workflow can check for cooperative cancellation
  4. The SSE stream sends a final job.status {status: INTERRUPTED} event

Graceful Shutdown

The SSEConnectionManager tracks all active SSE connections. During server shutdown:

  1. signal_shutdown() sets a shared event flag
  2. Active SSE generators check the flag during polling intervals
  3. Each stream emits a job.shutdown event before closing
  4. The connection manager waits for all streams to terminate