Progress Bar Architecture

This document describes how agent progress reaches the Blazor chat UI. The progress bar is a presentation of execution state; it is not an independent job tracker.

End-to-end flow

Python Agent
  -> completion blocks and completion step
  -> Plan JSON snapshot
  -> Blazor communication queue
  -> C# Session/Plan model
  -> Thinking ContainerMessage
  -> ChatLine.razor progress elements

The normal update path is:

  1. An agent initializes weighted completion blocks with Agent._completion_set_blocks().
  2. The agent starts _start_blazor_progress_updates(goal). A daemon thread samples the agent completion and current block title and calls send_plan_to_blazor() when either changes.
  3. send_plan_to_blazor() locks the plan, increments its revision, serializes the plan, and replaces the pending Blazor queue entry with the newest snapshot.
  4. The Blazor communication thread posts the snapshot to the configured Blazor endpoint. The C# session receives the updated plan and raises its data-change notification.
  5. C# plan handling creates or updates a thinking ContainerMessage. For active leaf goals it stores AgentProgressEntry values containing Completion and CompletionStep.
  6. Chat.razor keeps one visible thinking message. ChatLine.razor reads the current goal progress and renders the aggregate bar and any nested agent bars.

Python completion model

PythonServer/src/core/agents/Agent.py owns the generic completion model:

The generic Blazor publisher samples this state periodically. It sends only when the aggregate completion or step title changes, then sends one final snapshot from _stop_blazor_progress_updates().

Agents that perform work in parallel can update their own completion blocks while the publisher thread runs. The publisher takes agent_lock before reading the state, so a snapshot cannot observe a partially updated block configuration.

Reranker example

AgentReranker.run() uses three weighted stages:

Stage Weight Meaning
Preparing fragments 15% Find and normalize the input fragments and datasets
Reranking fragments 70% Apply semantic reranking or the applicable fallback path
Creating result dataset 15% Persist and expose the result dataset

The reranker starts the publisher with a 0.5-second interval because its work can finish before the general five-second interval produces a visible intermediate update.

Blazor and C# model

The serialized plan is converted into C# Plan and Goal objects. Session.Partial.PlanHandling.cs recursively finds active leaf goals. Each leaf with an active target agent contributes an AgentProgressEntry:

This produces nested progress entries for concurrent or child goals. The C# session also exposes GetProgress(goalId), which calculates completion from completed goals in the local goal tree. That value is a different metric from Python agent completion:

The current RemoteSession.GetProgress() implementation returns 0. Remote rendering therefore depends on the progress data already present in the received plan rather than recalculating the local goal tree.

Razor rendering

Chat.razor suppresses duplicate thinking rows and renders the first visible thinking message. ChatLine.razor then:

  1. Resolves a progress value from the reasoning document when one is active.
  2. Falls back to Session.GetProgress(ChatLineData.GoalId).
  3. Keeps the largest value seen in LastProgressBarValue, preventing visual regression when an older refresh arrives.
  4. Renders the aggregate HTML <progress> element when its value is greater than zero.
  5. Renders one nested <progress> element for each visible AgentProgressEntry.

The chat subscribes to SessionData.OnDataChanged; each plan snapshot causes the component to refresh and the progress bars to be recalculated.

Queue and timing behavior

The Blazor queue is deliberately latest-value-only. When a new plan snapshot is enqueued, older pending snapshots are discarded. This avoids sending stale plans but means intermediate values are not guaranteed to be displayed.

The publisher sends only on completion or step changes. If an agent finishes before the first timer interval, the final update is still sent by _stop_blazor_progress_updates(), but the user may see only the completed state. For short-running agents, use a shorter interval when visible intermediate progress matters.

Failure modes and maintenance rules

Source references