Summary
Show the current WorkflowState (current_phase, iteration, is_complete, termination_reason, payload) in the GUI during a run — both as a compact status bar and as a detailed State tab.
Display Components
1. Status Bar (compact, always visible)
A narrow bar between the toolbar and the main columns frame, showing the most important fields at a glance:
[●] Phase: loop | Iteration: 3 / 10 | Complete: X | Term: —
- Phase is color-coded: preparation (blue), loop (green), finalization (orange), idle (gray)
- Iteration shows current / max
- A small colored dot indicates running / stopped / error state
- Updates in near-real-time during execution
2. State Tab (detailed)
A third tab State in the existing _output_notebook (alongside Agent Preview and Live Output), showing the full state as a key-value table:
current_phase loop
iteration 3 / 10
is_complete False
termination_reason
payload { ... } ← formatted JSON, collapsible if large
- Read-only key-value list
- Payload rendered as syntax-highlighted JSON in a scrollable text widget
- Toggleable auto-scroll to latest state on update
- Shows placeholder "No state data yet" when idle
Technical Communication Approaches
Three options were considered for feeding state data from the engine thread to the GUI:
A) Queue-based (Recommended)
How: Pass an optional state_callback to ExecutionEngine.__init__. The engine calls state_callback(state.to_dict()) after every self.state.merge(). The GUI passes a lambda that puts ("state", data) onto the existing _log_queue. The _poll_log_queue handler processes "state" messages to update both widgets.
Changes required:
core/engine.py: ~3 lines — store state_callback, call it after merge
ui/app.py: ~15 lines — new queue message type, update two widgets
- No new polling, no new threads, no file I/O
Pros: Thread-safe (queue), minimal engine changes, piggybacks on existing architecture, no race conditions, no file I/O latency.
Cons: Slightly more coupling between engine and GUI (one callback).
B) Timer-based Polling
How: In _poll_log_queue (every 100ms), also read self._engine.state fields directly.
Changes required: Only ui/app.py — no engine changes.
Pros: Simplest to implement.
Cons: Race condition (engine thread mutates state while GUI reads — pragmatically safe under GIL, but not guaranteed); reads state even when nothing changed.
C) File Watcher (on .openloop/state_update.json)
How: A periodic timer (every 500ms) checks the mtime of .openloop/state_update.json and re-reads it if changed.
Changes required: Only ui/app.py — no engine changes.
Pros: Decoupled; also useful for debugging post-mortem.
Cons: File I/O every cycle; state only captured after agent writes the file (less granular than after merge); polling for file changes adds complexity; the file path depends on workdir which can change.
Implementation Plan
Phase 1 — Status Bar
- Add
state_callback parameter to ExecutionEngine.__init__ and invoke after merge()
- Add
("state", data) handling in _poll_log_queue
- Create
StatusBar frame widget in _build_ui (between toolbar and columns)
- Wire state updates to status bar labels
- Add color coding and status dot
Phase 2 — State Tab
- Add third tab to
_output_notebook
- Build key-value display with formatted JSON payload renderer
- Wire same state updates to the tab
- Add placeholder text for pre-run / post-run state
- Add auto-scroll toggle
Open Questions
- Should the status bar also show the payload size or a payload preview?
- Should the State tab allow copying the full state JSON to clipboard?
- Auto-scroll in State tab: always on or toggleable?
- How to handle large payloads — truncate in status bar, full in tab?
Summary
Show the current
WorkflowState(current_phase,iteration,is_complete,termination_reason,payload) in the GUI during a run — both as a compact status bar and as a detailedStatetab.Display Components
1. Status Bar (compact, always visible)
A narrow bar between the toolbar and the main columns frame, showing the most important fields at a glance:
2. State Tab (detailed)
A third tab
Statein the existing_output_notebook(alongsideAgent PreviewandLive Output), showing the full state as a key-value table:Technical Communication Approaches
Three options were considered for feeding state data from the engine thread to the GUI:
A) Queue-based (Recommended)
How: Pass an optional
state_callbacktoExecutionEngine.__init__. The engine callsstate_callback(state.to_dict())after everyself.state.merge(). The GUI passes a lambda that puts("state", data)onto the existing_log_queue. The_poll_log_queuehandler processes"state"messages to update both widgets.Changes required:
core/engine.py: ~3 lines — storestate_callback, call it after mergeui/app.py: ~15 lines — new queue message type, update two widgetsPros: Thread-safe (queue), minimal engine changes, piggybacks on existing architecture, no race conditions, no file I/O latency.
Cons: Slightly more coupling between engine and GUI (one callback).
B) Timer-based Polling
How: In
_poll_log_queue(every 100ms), also readself._engine.statefields directly.Changes required: Only
ui/app.py— no engine changes.Pros: Simplest to implement.
Cons: Race condition (engine thread mutates state while GUI reads — pragmatically safe under GIL, but not guaranteed); reads state even when nothing changed.
C) File Watcher (on
.openloop/state_update.json)How: A periodic timer (every 500ms) checks the mtime of
.openloop/state_update.jsonand re-reads it if changed.Changes required: Only
ui/app.py— no engine changes.Pros: Decoupled; also useful for debugging post-mortem.
Cons: File I/O every cycle; state only captured after agent writes the file (less granular than after merge); polling for file changes adds complexity; the file path depends on
workdirwhich can change.Implementation Plan
Phase 1 — Status Bar
state_callbackparameter toExecutionEngine.__init__and invoke aftermerge()("state", data)handling in_poll_log_queueStatusBarframe widget in_build_ui(between toolbar and columns)Phase 2 — State Tab
_output_notebookOpen Questions