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:
- An agent initializes weighted completion blocks with
Agent._completion_set_blocks(). - The agent starts
_start_blazor_progress_updates(goal). A daemon thread samples the agent completion and current block title and callssend_plan_to_blazor()when either changes. send_plan_to_blazor()locks the plan, increments its revision, serializes the plan, and replaces the pending Blazor queue entry with the newest snapshot.- 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.
- C# plan handling creates or updates a thinking
ContainerMessage. For active leaf goals it storesAgentProgressEntryvalues containingCompletionandCompletionStep. Chat.razorkeeps one visible thinking message.ChatLine.razorreads 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:
agent_completion_blocksstores progress for each block, normalized to0.0through1.0.agent_completion_blocks_weightsdetermines each block's contribution to the aggregate value.agent_completion_current_blockselects the active block.agent_completion_block_titlesstores the human-readable current step._completion_compute()calculates the weighted aggregate completion._completion_track()only moves a block forward. A later lower value is ignored._completion_end_all_blocks()forces every block to complete.
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:
Completion: the agent's normalized completion value.CompletionStep: the current Python block title.
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:
- Agent completion measures work inside the current agent, such as reranking batches.
- Goal-tree progress measures how many goals in a selected subtree are completed.
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:
- Resolves a progress value from the reasoning document when one is active.
- Falls back to
Session.GetProgress(ChatLineData.GoalId). - Keeps the largest value seen in
LastProgressBarValue, preventing visual regression when an older refresh arrives. - Renders the aggregate HTML
<progress>element when its value is greater than zero. - Renders one nested
<progress>element for each visibleAgentProgressEntry.
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
- An agent must start and stop the publisher around its execution, preferably with
try/finally. - Updating completion blocks without starting the publisher changes Python state but does not notify Blazor.
- Calling
send_plan_to_blazor()too frequently can create unnecessary plan serialization and HTTP traffic; the publisher's change detection should remain enabled. - Progress values must remain in the normalized range
0.0to1.0in Python and0to100in Razor's HTML<progress>control. - New UI progress sources should document whether they represent agent completion, goal-tree completion, document progress, or reasoning-document progress. These metrics should not be silently mixed.
Source references
- Python completion and publisher:
PythonServer/src/core/agents/Agent.py - Plan snapshot transport:
PythonServer/src/core/base/InputOutput.py - Reranker stages:
PythonServer/src/core/agents/AgentReranker.py - C# agent progress value object:
ThalabusInterfaces/Data/Sessions/AgentProgressEntry.cs - C# active-leaf collection:
ThalabusCore/Data/Sessions/Session.Partial.PlanHandling.cs - Local goal-tree progress:
ThalabusCore/Data/Sessions/Session.Partial.Configuration.cs - Chat thinking-row selection:
ThalabusWeb/RazorComponents/ChatRazorComponents/Chat.razor - Progress bar rendering and fallback calculation:
ThalabusWeb/RazorComponents/ChatRazorComponents/ChatLine.razor