Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
aa8cb76
fix(soul): keep agent loop alive while background tasks are running
RealKai42 Apr 8, 2026
47cb48e
docs: add changelog entry for background task wait fix
RealKai42 Apr 8, 2026
0f06bef
Merge branch 'main' into kaiyi/taipei-v1
RealKai42 Apr 8, 2026
21097db
fix(soul): skip timed wait when completion event is already set
RealKai42 Apr 8, 2026
a6a4e5d
Merge branch 'main' into kaiyi/taipei-v1
RealKai42 Apr 9, 2026
8e09783
Merge branch 'main' into kaiyi/taipei-v1
RealKai42 Apr 9, 2026
6072f8d
fix(print): poll for background tasks after run_soul in one-shot mode
RealKai42 Apr 9, 2026
a3885c2
Merge remote-tracking branch 'origin/kaiyi/taipei-v1' into kaiyi/taip…
RealKai42 Apr 9, 2026
60870da
docs: simplify changelog entry for print background wait
RealKai42 Apr 9, 2026
91220f8
fix(print): skip background wait when keep_alive_on_exit is set
RealKai42 Apr 9, 2026
1077e9c
merge from main
RealKai42 Apr 20, 2026
3874c64
feat: enhance background task management during CLI shutdown
RealKai42 Apr 20, 2026
e0b5814
feat(logging): ensure user-facing notices are written to original std…
RealKai42 Apr 20, 2026
1a44ccc
feat: add skip_user_prompt_hook parameter to run methods for syntheti…
RealKai42 Apr 20, 2026
a45e8e8
Merge remote-tracking branch 'origin/main' into kaiyi/taipei-v1
RealKai42 Apr 20, 2026
742088e
refactor(print,app): tighten background task exit wording and labels
RealKai42 Apr 20, 2026
ce1020b
chore: fix lint after merge (ruff format + pyright list type)
RealKai42 Apr 20, 2026
a2da914
fix(background,print): resolve 4 PR review findings on exit path
RealKai42 Apr 20, 2026
3586234
fix(background,print): follow-up on R5 review
RealKai42 Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Only write entries that are worth mentioning to users.

## Unreleased

- Print: Wait for background tasks before exiting — in one-shot `--print` mode, the process now waits for running background agents to finish and lets the model process their results, instead of exiting and killing them
- Core: Fix agent loop silently stopping when model response contains only thinking content — detect think-only responses (reasoning content with no text or tool calls) as an incomplete response error and retry automatically
- Core: Fix crash on streaming mid-flight network disconnection — when the OpenAI SDK raises a base `APIError` (instead of `APIConnectionError`) during long-running streams, the error is now correctly classified as retryable, enabling automatic retry and connection recovery instead of an unrecoverable crash
- Shell: Exclude empty current session from `/sessions` picker — completely empty sessions (no conversation history and no custom title) are no longer shown in the session list; sessions with a custom title are still displayed
Expand Down
1 change: 1 addition & 0 deletions docs/en/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This page documents the changes in each Kimi Code CLI release.

## Unreleased

- Print: Wait for background tasks before exiting — in one-shot `--print` mode, the process now waits for running background agents to finish and lets the model process their results, instead of exiting and killing them
- Core: Fix agent loop silently stopping when model response contains only thinking content — detect think-only responses (reasoning content with no text or tool calls) as an incomplete response error and retry automatically
- Core: Fix crash on streaming mid-flight network disconnection — when the OpenAI SDK raises a base `APIError` (instead of `APIConnectionError`) during long-running streams, the error is now correctly classified as retryable, enabling automatic retry and connection recovery instead of an unrecoverable crash
- Shell: Exclude empty current session from `/sessions` picker — completely empty sessions (no conversation history and no custom title) are no longer shown in the session list; sessions with a custom title are still displayed
Expand Down
1 change: 1 addition & 0 deletions docs/zh/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## 未发布

- Print:退出前等待后台任务完成——在单次 `--print` 模式下,进程现在会等待仍在运行的后台 Agent 完成并让模型处理它们的结果,而不是直接退出并杀死它们
- Core:修复模型响应仅包含思考内容时 agent loop 静默停止的问题——将仅含思考内容(无文本或工具调用)的响应检测为不完整响应错误并自动重试
- Core:修复长时间 streaming 过程中网络断连导致崩溃的问题——当 OpenAI SDK 在流式传输中途抛出基类 `APIError`(而非 `APIConnectionError`)时,现在能正确识别为可重试错误,自动触发重试和连接恢复,而不再直接崩溃退出
- Shell:从 `/sessions` 选择器中排除空的当前会话——完全为空的会话(既无对话记录也无自定义标题)不再显示在会话列表中;有自定义标题的会话仍然正常显示
Expand Down
8 changes: 8 additions & 0 deletions src/kimi_cli/background/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ def _active_task_count(self) -> int:
1 for view in self._store.list_views() if not is_terminal_status(view.runtime.status)
)

def has_active_tasks(self) -> bool:
"""Return True if any background tasks are in a non-terminal status.

This includes ``running``, ``awaiting_approval``, and any other
non-terminal state — not just actively executing tasks.
"""
return self._active_task_count() > 0

def _worker_command(self, task_dir: Path) -> list[str]:
if getattr(sys, "frozen", False):
return [
Expand Down
1 change: 1 addition & 0 deletions src/kimi_cli/soul/kimisoul.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@ async def _agent_loop(self) -> TurnOutcome:
has_steers = await self._consume_pending_steers()
if has_steers:
continue # steers injected, force another LLM step

final_message = (
step_outcome.assistant_message
if step_outcome.stop_reason == "no_tool_calls"
Expand Down
58 changes: 58 additions & 0 deletions src/kimi_cli/ui/print/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,64 @@ def _handler():
runtime.session.wire_file if runtime else None,
runtime,
)

# In one-shot text mode the process exits after this
# function returns, which would kill still-running
# background agents. Poll until they finish, calling
# reconcile() each iteration (the notification pump
# inside run_soul is no longer running, so we must
# drive reconcile ourselves to recover lost workers
# and publish terminal notifications). Only re-enter
# the soul when there are pending LLM notifications.
#
# stream-json mode is multi-turn: background tasks
# from one command must not block the next command.
if runtime and runtime.role == "root" and self.input_format == "text":
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 Skip print background wait when keep_alive_on_exit is enabled

The new post-run_soul wait loop runs for every root/text print turn, but it ignores runtime.config.background.keep_alive_on_exit. That flag is already honored by KimiCLI.shutdown_background_tasks (src/kimi_cli/app.py), which means exiting would not kill active tasks in this mode; with the current condition, one-shot --print can still block until all background tasks finish (or forever for long-lived jobs), which defeats the configured keep-alive behavior.

Useful? React with 👍 / 👎.

manager = runtime.background_tasks
notifications = runtime.notifications
while not cancel_event.is_set():
# Drive reconcile() ourselves: the notification
# pump inside run_soul is no longer running, so
# we must recover lost workers and publish
# terminal notifications here.
manager.reconcile()
if notifications.has_pending_for_sink("llm"):
# Re-enter soul so the LLM can process the
# completion notification. Do this even if
# other tasks are still active — progress on
# completed tasks should not wait on siblings.
bg_prompt = (
"<system-reminder>"
"Background tasks have completed."
" Process their results."
"</system-reminder>"
)
await run_soul(
self.soul,
bg_prompt,
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 Avoid routing synthetic background prompts through run_soul

This branch re-enters run_soul with a synthetic user prompt whenever has_pending_for_sink("llm") is true, which means the internal completion-followup turn goes through normal KimiSoul.run user-prompt hooks. In environments with UserPromptSubmit hooks that block/transform prompts, the notification can remain pending and this loop immediately continues into another run_soul call, causing a tight hang in one-shot --print while completions never drain. Internal background-notification processing should bypass user-prompt hooks (or use a dedicated internal path) so pending notifications are always acked.

Useful? React with 👍 / 👎.

partial(visualize, self.output_format, self.final_only),
cancel_event,
runtime.session.wire_file,
runtime,
)
continue
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
if not manager.has_active_tasks():
# Re-check once after noticing no active
# tasks: a worker may have finished between
# the reconcile above and this snapshot,
# leaving a terminal state on disk that we
# haven't published yet. Without this
# second reconcile+pending check, that
# final completion notification would be
# lost when the process exits.
manager.reconcile()
if notifications.has_pending_for_sink("llm"):
continue
break
# Still waiting for tasks to finish.
await asyncio.sleep(1.0)
if cancel_event.is_set():
raise RunCancelled
else:
logger.info("Empty command, skipping")

Expand Down
Loading
Loading