Skip to content

feat(ui): auto-trigger agent when background tasks complete while idle#1571

Merged
RealKai42 merged 4 commits intomainfrom
kaiyi/accra
Mar 25, 2026
Merged

feat(ui): auto-trigger agent when background tasks complete while idle#1571
RealKai42 merged 4 commits intomainfrom
kaiyi/accra

Conversation

@RealKai42
Copy link
Copy Markdown
Collaborator

@RealKai42 RealKai42 commented Mar 25, 2026

Summary

  • Add _BackgroundCompletionWatcher in the shell main loop to detect background task completions while the agent is idle, and automatically trigger a new soul run so the LLM can react to the results.
  • BackgroundTaskManager now exposes a completion_event that is set whenever a new terminal notification is published.
  • The watcher uses a double-check mechanism (_has_pending_llm_notifications) to avoid redundant triggers when notifications were already consumed during a previous soul run.
  • User input always takes priority over background completion signals.
  • Also includes fix for removed acp.TerminalHandle replaced with Client terminal methods.

Test plan

  • Start a background bash task (e.g. sleep 5 && echo done), verify the agent auto-triggers when the task completes while idle
  • Verify no redundant trigger when background task completes during an active soul run
  • Verify user input takes priority when both arrive simultaneously
  • Verify _exit_after_run is respected after background auto-trigger
  • Run existing background and notification tests: pytest tests/background/ tests/core/test_notifications.py

Open with Devin

Copilot AI review requested due to automatic review settings March 25, 2026 03:26
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da76468535

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +94 to +95
if self.enabled and self._has_pending_llm_notifications():
return None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prioritize queued prompt events before background auto-run

wait_for_next returns None as soon as any LLM notification is pending, before it even checks idle_events, so a user input already queued at loop entry is ignored until after an extra auto-triggered run_soul_command finishes. This breaks the stated “user input always takes priority” behavior and can cause an unintended extra model call (cost/latency) whenever a background completion and user submission happen close together (e.g., task finishes during/just after a run and the user types immediately).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an idle-loop “background completion” watcher to the interactive shell so the agent can automatically re-run when background tasks finish and produce new LLM-facing notifications.

Changes:

  • Introduces _BackgroundCompletionWatcher in the shell loop to wait on either user input or background completion signals and auto-trigger a soul run when appropriate.
  • Exposes BackgroundTaskManager.completion_event, setting it when a new terminal task notification is published.
  • Adds logic to avoid redundant triggers when completions are already consumed during a prior soul run.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
src/kimi_cli/ui/shell/init.py Adds idle-loop watcher to auto-trigger the agent on background completion, prioritizing user input.
src/kimi_cli/background/manager.py Adds completion_event and sets it when publishing new terminal notifications.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +104 to +113
done, _ = await asyncio.wait(
[idle_task, bg_wait_task],
return_when=asyncio.FIRST_COMPLETED,
)
for t in (idle_task, bg_wait_task):
if t not in done:
t.cancel()
with contextlib.suppress(asyncio.CancelledError):
await t

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “user input always takes priority” guarantee isn’t fully upheld if the background event completes just before user input is enqueued: when bg_wait_task wins, idle_task is cancelled and the queued input will only be processed after the auto-triggered soul run completes. To make input priority deterministic, consider checking idle_events.empty() (or doing a non-blocking get_nowait() fallback) after the background event fires before returning None/triggering a run.

Copilot uses AI. Check for mistakes.

@property
def completion_event(self) -> asyncio.Event:
"""Event set when a background task reaches terminal status."""
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The completion_event docstring says it is set “when a background task reaches terminal status”, but the event is actually set only when a new terminal notification is published (and won’t be set if the task is terminal but reconcile()/publish_terminal_notifications() hasn’t run yet, or if publishing is deduped). Please adjust the docstring to match the actual semantics so consumers don’t rely on the stronger guarantee.

Suggested change
"""Event set when a background task reaches terminal status."""
"""Event set when a new terminal-status notification is published for a background task.
Note: this is tied to notification publishing, so it may not be set immediately when a
task becomes terminal, and may not fire again for already-terminal tasks if publishing
is deduplicated.
"""

Copilot uses AI. Check for mistakes.
Comment on lines 498 to +501
notification = self._notifications.publish(event)
if notification.event.id == event.id:
published.append(notification.event.id)
self._completion_event.set()
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New behavior: _completion_event is set when a terminal notification is newly published. There are already tests for publish_terminal_notifications() and reconcile() in tests/background/test_manager.py, but none assert the event semantics.

Add a test that verifies the event transitions (set on first publish; not set again when the publish is deduped; can be cleared and re-set for a subsequent distinct terminal notification) to prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +379 to +392
if result is None:
# Background task completed with pending notifications
logger.info("Background task completed while idle, triggering agent")
resume_prompt.set()
await self.run_soul_command(
"<system-reminder>"
"Background tasks completed while you were idle."
"</system-reminder>"
)
console.print()
if self._exit_after_run:
console.print("Bye!")
break
continue
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a new control-flow path where the shell auto-triggers run_soul_command() while idle (including respecting _exit_after_run). There are existing unit tests around Shell.run() behaviors (e.g. tests/ui_and_conv/test_shell_run_placeholders.py), but this new background-completion path isn’t covered.

Consider adding a test that simulates completion_event + a pending LLM notification and asserts exactly one auto-trigger occurs (and that user input is not starved when both signals arrive).

Copilot uses AI. Check for mistakes.
Comment on lines +383 to +387
await self.run_soul_command(
"<system-reminder>"
"Background tasks completed while you were idle."
"</system-reminder>"
)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions a fix for “removed acp.TerminalHandle replaced with Client terminal methods”, but this diff doesn’t touch any ACP-related code (and a repo-wide search doesn’t find TerminalHandle). If that fix is no longer part of this PR, consider updating the description to avoid confusion in review/release notes.

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +96
if self.enabled and self._has_pending_llm_notifications():
return None

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait_for_next() immediately returns None whenever there are pending LLM notifications (even if no new completion_event was observed). If run_soul_command() fails (e.g., LLM not set / provider error), those notifications remain pending and the shell loop will re-enter this branch again immediately, creating a tight retry loop that can spam logs/CPU and make the UI unusable.

Consider gating the auto-trigger on an actual completion signal (e.g., only trigger when completion_event is set), or record/backoff after an auto-trigger attempt (especially on failure) so the loop falls back to waiting for user input instead of immediately re-triggering.

Suggested change
if self.enabled and self._has_pending_llm_notifications():
return None

Copilot uses AI. Check for mistakes.
devin-ai-integration[bot]

This comment was marked as resolved.

- Update completion_event documentation to clarify notification behavior.
- Add has_pending_for_sink method to NotificationManager for checking pending deliveries.
- Refactor _BackgroundCompletionWatcher to improve handling of background task completions and user input prioritization.
- Introduce tests for completion_event and background completion watcher functionality.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30368f28ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +67 to +70
def has_pending_for_sink(self, sink: str) -> bool:
"""Check whether any notification has a pending delivery for *sink*."""
for view in self._store.list_views():
sink_state = view.delivery.sinks.get(sink)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recover stale claims before checking pending notifications

The shell’s new idle auto-trigger path depends on has_pending_for_sink("llm") (_BackgroundCompletionWatcher.wait_for_next), but this helper reads delivery state without calling recover(). If a prior run crashed after claiming an LLM notification, that sink can stay claimed long enough for the watcher to enter a blocking wait on completion_event; when another component later requeues stale claims, no new publish occurs to set that event, so the agent can stay idle indefinitely until the user submits input. Running recover() before this pending check would prevent missing those recovered notifications.

Useful? React with 👍 / 👎.

@RealKai42 RealKai42 merged commit de7e1c2 into main Mar 25, 2026
14 checks passed
@RealKai42 RealKai42 deleted the kaiyi/accra branch March 25, 2026 07:10
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4989d32797

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +67 to +72
def has_pending_for_sink(self, sink: str) -> bool:
"""Check whether any notification has a pending delivery for *sink*."""
for view in self._store.list_views():
sink_state = view.delivery.sinks.get(sink)
if sink_state is not None and sink_state.status == "pending":
return True
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recover stale sink claims before pending check

has_pending_for_sink() reads delivery state directly but never calls recover(), so notifications that are stuck in claimed (for example after an interrupted/crashed run) are treated as non-pending forever. The new idle auto-trigger path in _BackgroundCompletionWatcher relies on this method, so in that stale-claim state the shell can miss legitimate LLM work and remain idle until manual user input. Calling recover() before scanning (as claim_for_sink() already does) avoids this false-negative state.

Useful? React with 👍 / 👎.

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.

2 participants