Skip to content

feat(kap-server): stream agent prompt queues and reorder queued prompts - #4737

Open
wbxl2000 wants to merge 13 commits into
mainfrom
qer/prompt-queue-v2
Open

wbxl2000 wants to merge 13 commits into
mainfrom
qer/prompt-queue-v2

Conversation

@wbxl2000

@wbxl2000 wbxl2000 commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Requirement or Bug

客户端需要以服务端为准显示和操作 agent 的排队输入:知道一次提交是开启了新一轮、插进了当前轮还是在排队,收到每个 agent 排队区的实时内容,并能调整排队顺序。本 PR 是服务端第一阶段;暂停排队、排队中的 /compact、插话优先级留到第二阶段。

同时修复一个已有问题:插话之后,排在队列里的输入会乱序执行。

配套客户端 PR:MoonshotAI/kimi-code-app#1203。

Bug Reproduction Steps

  1. agent 正在运行一轮时,依次提交 b、c、d 三条输入(排队)。
  2. 再提交一条插话(delivery: "steer",或 TUI 里的插话)。
  3. 当前轮结束后,b、c、d 实际按 c、d、b 的顺序开始。

Root Cause

一轮结束时,状态机比 loop 先进入下一轮:它把队首取出、开始新一轮,而 loop 还在处理上一轮的收尾(插话会让收尾变慢,所以插话后必现)。loop 发现自己还有活动轮次,就把这条输入重新 submit 给引擎,而 submit 追加到队尾,顺序因此被打乱。

本 PR 让重新入队的输入回到队首,属于根本修复:顺序保持正确,代价是插话过的轮结束时,队首这条会在 queue 帧里短暂消失又出现。

Code Changes

核心思路:排队 / 插话 / 新开一轮只由 loop 的 submit() 决定一次,调用方读它的返回值,不再自己根据提交前的快照推断;队列变化只有一个信号来源:引擎订阅状态机快照,队列 id 或正在启动的条目变了就发出。

flowchart LR
    route["POST /prompts<br/>delivery"] -->|steerIfActive| submit["loop.submit()<br/>→ acceptedAs"]
    submit --> machine[(状态机 queue)]
    move["POST .../queue/{id}:move"] -->|moveQueued| machine
    machine -->|快照变化| engine["engine: queueChanged"]
    engine --> loop["loop.onDidChangeQueue"]
    loop --> feed["SessionAgentFeed<br/>微任务合并 + 去重"]
    feed -->|queue 帧| ws((WebSocket 订阅者))
Loading

agent-core-v2:

 IAgentLoopService
-  submit(input, options): { id }
+  submit(input, options): { id, acceptedAs: 'turn' | 'steer' | 'queue' }
+  moveQueued(promptId, beforePromptId?)        // 不在队列或正在启动时抛 PROMPT_NOT_FOUND
+  onDidChangeQueue: Event<void>
 LoopSnapshot
+  startingPromptId?                             // 状态机处在 idle.gating 时的队首
 MachineEngine
+  move(id, beforeId?)                           // 状态机新增 input.move
 bindMachineTurn(loop 仍有活动轮次时)
-  engine.submit(entry)                          // 追加到队尾
+  engine.submit(entry); engine.move(id, head)   // 放回队首

promptChannel.submit / submitSteer、skillService.promptWithSkills 和提交路由都改为读 acceptedAs。

kap-server:

  • POST /sessions/{id}/prompts:新增 delivery: "queue" | "steer"(默认 queue),响应新增 accepted_as。两个分支(普通提交、带 skills 的提交)都支持。
  • 新增 POST /sessions/{id}/agents/{agent_id}/queue/{prompt_id}:move,可带 before_prompt_id,省略时移到末尾;不在队列里返回 40402。
  • 新增 queue 帧 { type, session_id, agent_id, event_created_at, paused, inputs },挂在已有的 SessionAgentFeed 上:订阅时随 agent / todo 等帧一起发送,之后队列变化时推送,内容不变时不发。条目形状按 KAP 草案的 QueuedInput;正在启动的条目和系统内部的(untracked)条目不列出。

与 KAP 草案的差异(第一阶段有意简化):

  • delivery: "steer" 遇到没有可插话的轮时直接退化为普通排队(accepted_as: "queue"),不实现“插话排在普通排队之前”。
  • paused 固定为 false;提交仍走 POST /prompts,响应暂不带 turn_id。

建议阅读顺序:loopService.ts 的 submit / moveQueued / bindMachineTurn → engine.ts 的快照订阅 → sessionAgentFeed.ts → routes/prompts.ts。

Behavior Changes and Affected Users

行为 之前 之后 谁依赖旧行为 退路
插话后排队输入的执行顺序 loop 重新入队时追加到队尾,顺序可能被打乱 回到队首,按排队顺序执行 无(旧行为是 bug) 无需
提交响应 { prompt_id, user_message_id, status, content, created_at, metadata? } 额外带 accepted_as desktop / web、SDK 与外部脚本:只新增字段 无需
提交请求的 delivery 不存在(未知字段) steer 时在有运行中轮次时插话 不传时行为不变 不传即旧行为
WebSocket 订阅帧 agent / usage / goal / todo 额外收到 queue 帧 按 type 分发的客户端会忽略未知帧;严格白名单的客户端会看到新类型 无
promptChannel.submitSteer 的 steered 提交前快照判断“有运行中轮次”且 turn id 相同 状态机实际是否接收了插话 TUI / klient 插话:正常路径结果相同,只有状态机没接收插话时由误报 true 改为 false 无需
API surface 快照 — 新增 POST /api/v1/sessions/{session_id}/agents/{agent_id}/queue/{tail} 读取 OpenAPI 的客户端 无需

受影响模块与测试:

  • agent-core-v2 loop.test.ts:新增 prompt queue 用例。
    • 定向用例覆盖受理方式(acceptedAs)、调序与拒绝,以及正在启动的条目不可调序。
    • 随机操作序列测试(5 个种子)检查四条不变量:队列无重复;每次变化都有通知且没有多余通知;各轮按预期顺序开始;最终全部结束、队列清空。
    • 已用故意改坏的代码确认这些测试能抓到“漏发通知”“可移动正在启动的条目”“重新入队到队尾”三种错误。
  • kap-server sessionEventBroadcaster.test.ts:queue 帧在订阅时发送、内容不变不重发,并过滤正在启动和系统内部的条目。
  • kap-server prompts.test.ts:accepted_as: turn,以及 :move 的 40402 / 40001 / 40401。
  • kap-server conversationContract.e2e.test.ts S11:用 mock 模型完整跑一遍排队、调序、插话,核对 queue 帧内容和各轮实际执行顺序(这个用例最先暴露了上面的乱序问题)。
  • 文档:docs/{en,zh}/reference/server-api.md。

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (external PRs: issue must have a maintainer's /approve).
  • I have added tests that prove my feature works.
  • The behavior-change table above is complete, and every removed behavior or flipped default is named in the changeset and either has an escape hatch or was explicitly approved by a maintainer in this PR.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

- loop submit reports acceptedAs (turn / steer / queue); prompt route, prompt channel and skill submit use it instead of pre-submit snapshots
- engine emits queueChanged when queue ids or the starting entry change; loop exposes onDidChangeQueue, moveQueued and startingPromptId
- requeue a head the machine started early at the front of the queue, not the end
- kap-server: delivery and accepted_as on submit, per-agent queue frame, queue/{prompt_id}:move route
- docs: server API reference (en / zh)
@changeset-bot

changeset-bot Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 01235b2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T18:32:58.084265Z 01235b2 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@01235b2
npx https://pkg.pr.new/@moonshot-ai/kimi-code@01235b2

commit: 01235b2

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

Copy link
Copy Markdown

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: 7b48ae4acb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread docs/en/reference/server-api.md Resolved
# Conflicts:
#	docs/en/reference/server-api.md
#	docs/zh/reference/server-api.md
…ts as queued

- declare the move route params and optional body so OpenAPI exposes them
- a prompt submitted while an earlier one waits for the engine to attach is accepted as queued
- document the steer fallback as it behaves: queued only when no user turn is running
@wbxl2000

wbxl2000 commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

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

Copy link
Copy Markdown

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: 8a32de1b94

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

# Conflicts:
#	packages/agent-core-v2/src/features/skill/skillService.ts
…arked prompts from queue frames

- a prompt submitted with delivery steer materializes its daemon file refs before it reaches the running turn, so the prepared upload cleanup cannot drop them
- the loop snapshot reports whether the machine engine is attached; queue frames list no inputs until it is, since parked submissions cannot be moved yet
@wbxl2000

wbxl2000 commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 9710fda2dd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

… settle

The machine started the queued head as soon as its run ended, while the loop
was still deciding whether the turn continues. The loop then pushed the head
back into the queue, so it flickered out and in and its submit hooks ran twice.

The gate now waits for the loop's settle decision. A continuing turn with
pending notifications defers the head and runs the notifications in the same
turn; otherwise the head starts normally. The requeue path is removed, and the
starting marker is owned by the loop while submit hooks run.
…ompts

Queued prompts are pending work: the session keeps its run lock, reports
running, and is skipped by idle reclaim until every agent's queue drains.
:reload now refuses with 40901 while prompts are queued instead of silently
dropping the in-memory queue.
… prompts

A user steer that cannot join a running user turn (idle with queued work, a
system-triggered turn, or a paused queue) now waits in the queue marked as a
steer: after earlier steers and before queued prompts, never ahead of a head
whose submit hooks are already running. When a user turn starts, the remaining
steer entries steer into it at once. :steer on a queued prompt moves it into
the slot when no user turn is running. Steer entries cannot be moved and
cannot be a move target (40001). Internal callers keep steerIfActive
unchanged; the submit routes use the new user-only steer option, and queue
frames report delivery: steer for slot entries.
:compact now answers { accepted_as, prompt_id }. An idle agent with an empty
queue compacts at once (compact); otherwise a compact entry with the
instruction joins the end of the agent's queue (queue), and 40910 is returned
while a compaction is running or already queued. When the entry reaches the
head, the prompt gate runs the compaction instead of a turn and then drops the
entry; a failure leaves an error notice and the queue moves on. Queued
compactions can be moved and withdrawn but not steered (40001); once started,
withdrawing returns 40402 and the existing compaction cancel applies.
…t id

Prompts are now submitted with PUT /sessions/{session_id}/prompts/{prompt_id}.
The body is unchanged except that prompt_id moves into the path, where it must
be 1-128 letters, digits, _ or - (40001 otherwise); idempotency is unchanged.
POST /sessions/{session_id}/prompts is removed. The response adds turn_id, the
conversation turn the prompt started, when accepted_as is turn. The server
accepts path parameters up to 256 characters so the longest prompt ids also
work with the :abort, :steer and :move actions. Server API docs cover the
submit route, the steer slot, queued compaction and the queue frame.
…he queue fold

The session activity fold now reads each agent's queue, so the subagent loop
fake exposes an empty queue and a queue change event, and the state manifest
lists the new queued flag.

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

Copy link
Copy Markdown

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: 01235b2ef2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +221 to +222
method: 'PUT',
path: '/sessions/{session_id}/prompts/{prompt_id}',

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 Preserve the existing POST prompt endpoint

Existing desktop, web, and external API clients submit prompts with POST /sessions/{session_id}/prompts and an optional body prompt_id, but this change replaces that registration with a PUT-only path and the repo-wide route search finds no compatibility alias. After upgrading the server, every previous client still using the documented POST request receives a 404 and cannot submit prompts; retain the POST route while adding the idempotent PUT form, or explicitly version this breaking contract change.

AGENTS.md reference: AGENTS.md:L121-L123

Useful? React with 👍 / 👎.

Comment on lines +431 to +432
if (this.engine !== undefined) this.machineEngine().submit(entry);
else this.pendingSubmissions.push(entry);

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 Retain queued compactions until the engine attaches

When an ordinary prompt is parked before the machine engine attaches and a concurrent :compact request is accepted behind it, this branch parks the compaction in pendingSubmissions, but drainPendingToMachine() later forwards only entries with a promptWaiter; compactions never create one. The API therefore returns accepted_as: "queue" and a prompt id, then silently discards the compaction during attach, so it never runs or produces an error. Preserve kind: "compact" entries when draining, or give them equivalent pending bookkeeping.

AGENTS.md reference: AGENTS.md:L129-L131

Useful? React with 👍 / 👎.

Comment on lines +402 to +404
} else if (slotted) {
const index = this.pendingSubmissions.findIndex((item) => item.meta?.delivery !== 'steer');
this.pendingSubmissions.splice(index === -1 ? this.pendingSubmissions.length : index, 0, entry);

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 Keep the parked starting prompt ahead of pre-attach steers

When a normal prompt is parked before the machine engine attaches, it is reported as acceptedAs: "turn"; if a delivery: "steer" prompt arrives in the same startup window, this insertion treats the first prompt as an ordinary queue entry and places the steer before it. Once the engine attaches, the later steer starts the first turn and the original turn prompt runs afterward, contradicting both responses and reversing submission order. Preserve the first parked turn candidate ahead of the steer slot, matching the startingPromptId handling used after attachment.

Useful? React with 👍 / 👎.

Comment on lines +366 to +370
const steersActive =
(options?.steerIfActive === true || (options?.steer === true && !this.machinePaused())) &&
this.active !== undefined &&
this.active.prompt.tracked &&
this.engine !== undefined;

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 Require a live machine turn before accepting an immediate steer

During turn teardown, the state machine can already be idle or gating the next queued prompt while this.active still refers to the previous turn—this is the ordering gap that continuesActiveTurn() handles elsewhere. A delivery: "steer" submission in that window satisfies this condition, so promptSteered associates its handle, completion, and events with the already-finished turn even though the machine carries the notification into the next turn. This can report the prompt completed with the previous turn before it is actually consumed; require the engine snapshot to still be running, otherwise place the prompt in the steer slot.

AGENTS.md reference: AGENTS.md:L121-L123

Useful? React with 👍 / 👎.

Comment on lines +481 to +489
for (const id of promptIds) {
const queue = engine.snapshot().queue;
const starting = this.snapshot().startingPromptId;
const index = queue.findIndex((item) => item.meta?.promptId === id);
const before =
id === starting
? queue[index + 1]?.meta?.promptId
: steerSlotBefore(queue.filter((item) => item.meta?.promptId !== id), starting);
engine.move(id, before, 'steer');

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 Leave an already-slotted steer in place

When no user turn is active and a client calls :steer again for a prompt already in the steer slot, this loop removes that entry and reinserts it immediately before the first ordinary queued prompt. For a queue such as [s1, s2, q1], steering s1 again silently changes the priority to [s2, s1, q1], even though the public contract says an existing steer keeps its place. Detect meta.delivery === "steer" and leave that entry untouched in the no-active-turn path.

AGENTS.md reference: AGENTS.md:L121-L123

Useful? React with 👍 / 👎.

@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch

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 Use a minor bump for the new queue API

This changeset introduces capabilities users could not use before—streaming queues, reordering queued inputs, and selecting steer delivery—but marks the CLI release as patch. The repository changeset rules classify real new capabilities as minor, so leaving this as a patch publishes the feature under the wrong semantic version; change this entry to a minor bump while keeping the separate ordering bug fix as patch.

AGENTS.md reference: AGENTS.md:L89-L90

Useful? React with 👍 / 👎.

This branch has not been deployed

No deployments
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