Skip to content

fix(webui+memory): close post-turn utility process groups (memory memorization renders as in-flight "Processing...") - #1805

Open
Zenetusken wants to merge 1 commit into
agent0ai:mainfrom
Zenetusken:fix/post-turn-utility-group-completion
Open

fix(webui+memory): close post-turn utility process groups (memory memorization renders as in-flight "Processing...")#1805
Zenetusken wants to merge 1 commit into
agent0ai:mainfrom
Zenetusken:fix/post-turn-utility-group-completion

Conversation

@Zenetusken

Copy link
Copy Markdown

Problem

After a turn fully completes (final response delivered, status bar on "Waiting for input"), the memory plugin's post-turn memorization jobs render as an in-flight phase: a process group titled "Processing..." with a UTL badge appears, its utility items ("No useful information to memorize." / "No successful solutions to memorize.") keep the shiny in-flight animation, and the group never gets an END badge. The turn is over, but the UI says it isn't.

Root cause

The monologue_end extensions _50_memorize_fragments and _51_memorize_solutions log a utility item synchronously at loop end and fill it in seconds later from a background task (DeferredTask). By then the turn's process group was already completed by the final response, so drawProcessStep opens a new process group for these items. That group:

  1. contains no agent-type steps, so updateProcessGroupHeader never replaces the "Processing..." placeholder title (it only reads headings from agent steps), and
  2. is never marked complete — completion only happens when a response attaches to the group, a user message arrives, or a step with kvps.finished is drawn (drawMessageInfo). Utility items carry no terminal marker, so nothing ever closes the group.

An uncompleted group keeps shiny-text on its newest step (removed only on completion), which is the perpetual flashing, and never receives the END badge.

A secondary status-bar issue: the utility item creation and its error-path warning log use the default update_progress="persistent". The error path in particular logs a new warning item after monologue_end reset progress to "Waiting for input", pinning the progress line to the warning with progress_active=true until the next turn.

Fix

Backend (plugins/_memory/extensions/python/monologue_end/_50/_51):

  • every terminal update path now emits finished=True (early returns for empty/invalid/unparseable results, a single post-loop marker after successful inserts, and the exception path);
  • the log item is created with update_progress="none", the terminal marker uses update_progress="none", and the error-path warning no longer hijacks the status bar.

Frontend (webui/js/messages.js):

  • drawMessageUtil calls completeLastProcessGroup() when a utility step arrives with kvps.finished (mirroring drawMessageInfo); the finished kvp is hidden from the displayed data;
  • updateProcessGroupHeader and the full-log classifier fall back to the last step's title when a group has no agent steps, so utility-only groups show e.g. "Memorization completed: …" instead of "Processing...".

Turn-start preload utilities are unaffected: they still join the next turn's group via the existing utility-only promotion path, and mixed groups still title from the last agent step.

Tests

tests/test_post_turn_utility_group.py (9 tests):

  • runs the real memorize() coroutines of both extensions against stubbed agents: empty result, successful insert (non-consolidation), and provider-error paths each emit exactly one terminal finished marker, entries are inserted before the marker, and warnings stay off the status bar;
  • asserts execute() creates the log item with update_progress="none";
  • extracts the real updateProcessGroupHeader/isProcessGroupComplete/completeLastProcessGroup from messages.js into a Node fake-DOM harness: a utility-only group titles from its last step, completes with an END badge, and sheds all shiny classes; a mixed group still titles from the last agent step.

Discriminating: all 9 fail on pre-fix sources (the frontend assertion reproduces the exact reported symptom: title stays 'Processing...'), all 9 pass with the fix.

Full suite: pre-existing collection breakage on current main is unrelated to this change (8 files — email_parser_test, rate_limiter_test (real network call), test_run_ui_config, test_state_sync_handler, test_state_sync_welcome_screen, test_ws_csrf, test_ws_manager, test_ws_security — fail identically on pristine main). The remainder of the suite passes; see PR checks/comment.

Evidence of the bug (live, before fix)

Finished chat log: the final response item carries kvps.finished: true; the memorize utility items appended after it have id: null and no terminal marker, and their updates arrive after monologue_end reset progress — matching the orphaned group the UI renders.

The memory plugin's monologue_end extensions (_50_memorize_fragments,
_51_memorize_solutions) log a utility item when the agent loop ends and
fill it in from a background task. By then the turn's process group was
already completed by the final response, so the Web UI opened a new
process group for these items. That group contains no agent steps, so
its title stayed on the "Processing..." placeholder, and no later event
ever marked it complete, so its steps kept the in-flight shiny animation
and the phase never received an END badge: a finished turn looked like
it was still running ("Processing..." + flashing utility items with a
"waiting for input" status bar).

Backend: the extensions now mark the utility item with a finished kvp on
every terminal update path (early returns, post-loop success marker, and
the exception path), and keep these post-turn bookkeeping items off the
status bar with update_progress="none" (creation, terminal updates, and
the error-path warning, which could previously pin the progress line to
the warning heading with progress_active=true until the next turn).

Frontend: drawMessageUtil closes the last process group when a utility
step arrives with kvps.finished (mirroring drawMessageInfo), and
updateProcessGroupHeader plus the full-log classifier fall back to the
last step's title for groups without agent steps instead of leaving the
"Processing..." placeholder.

Regression coverage: tests/test_post_turn_utility_group.py runs the real
memorize() coroutines against stubbed agents (empty result, successful
insert, and provider-error paths all emit exactly one terminal finished
marker; warnings stay off the status bar) and extracts the real group
header functions from messages.js into a Node fake-DOM harness (utility
-only group titles from its last step, completes with END badge, shiny
removed; mixed groups still prefer the last agent step). All 9 tests
fail on the pre-fix sources and pass with the fix.
@Zenetusken

Copy link
Copy Markdown
Author

Full-suite regression evidence (v2.8 image harness, pytest tests/):

  • Pre-fix main: 73 failed, 1099 passed, 12 errors
  • This branch: 64 failed, 1108 passed, 12 errors
  • Sorted failure lists are identical except for the 9 new tests (which fail on pre-fix sources by design). No pre-existing failure is fixed or introduced by this change.

Pre-existing on pristine main (unrelated drift vs the v2.8 runtime image): 8 collection-error files (email_parser_test, rate_limiter_test — performs a real OpenRouter call at import, test_run_ui_config, test_state_sync_handler, test_state_sync_welcome_screen, test_ws_csrf, test_ws_manager, test_ws_security) plus 64 runtime failures (test_time_travel and others), all byte-identical before and after this patch.

Zenetusken added a commit to Zenetusken/agent-zero that referenced this pull request Aug 2, 2026
Zenetusken added a commit to Zenetusken/agent-zero that referenced this pull request Aug 2, 2026
The regular chat save hook lives at message_loop_end (_90_save_chat),
which runs before monologue_end extensions. Anything logged from
monologue_end — the "Waiting for input" progress reset and the memory
plugin's memorize utility items (created at loop end, filled in by a
background task) — therefore missed the turn's final save. Those entries
reached chat.json only when the next turn happened to trigger a save,
and were lost entirely if the server restarted in between (measured
live: a finished chat's memorize items were absent from chat.json
minutes after completion; the previous turn's only appeared after the
user's next message).

- new extensions/python/monologue_end/_95_save_chat.py persists the chat
  when the monologue ends, capturing memorize item creation and the
  "Waiting for input" progress (skips ephemeral BACKGROUND contexts,
  mirroring the message_loop_end hook);
- the memorize background tasks persist their terminal state (final
  headings + finished marker) in a finally block, covering every exit
  path including provider errors.

Stacked on agent0ai#1805 (shares the _50/_51 except-block region).

Regression coverage: tests/test_post_turn_chat_persistence.py asserts
both memorize tasks save the chat on the empty-result and error paths,
and that the new monologue_end hook saves regular contexts while
skipping BACKGROUND ones. All 6 fail on pre-fix sources; pass with the
fix. Arc-8 test doubles updated to no-op the new save call (persistence
is covered by its own tests).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant