跳到主要内容

一次请求的一生:8 阶段运行时编排

30 秒导读: QwenPaw 的中央循环是 Runtime.run()。它把一次 AgentRequest 翻译成一串 SSE(Server-Sent Events)envelope 对象吐给前端。整条流水线被切成 8 个固定相位点(Phase),相位之间夹着 3 个固定步(slash 命令分发、组装 agent、执行 agent)。所有会变的业务逻辑——会话加载、模式行为、审计——都不写死在 Runtime 里,而是注册成 hookmode,由相位点调起。

本章讲清楚:一次请求进来,Runtime 怎么一层层往下走、在哪些点让插件介入、agent 怎么被临时组装出来、事件怎么变成 SSE 流、出错和收尾怎么兜底。

想深入 agent 内部怎么 ReAct、prompt 怎么逐块拼、provider 怎么选 → 看 第 02 章。想深入 工具执行的权限包装(GuardedFunctionTool / PolicyGuardedTool) → 看 第 05 章。本章只在编排层面提它们一句。


1. 这是什么(零基础也能懂)

一句话定义

Runtime每个 workspace 一个的请求编排器:每来一个 AgentRequest 就调一次 run(),run() 是个异步生成器,一边处理一边 yield 出 SSE envelope,前端拿到就实时渲染。

依据:runtime/runtime.py:32 class Runtime + :49 async def run

解决什么问题

假设你在网页里对 QwenPaw 说了句话。从「你按回车」到「屏幕上一个字一个字冒出来、工具调用的卡片一张张弹出来」,中间要发生一堆事:

  • 要不要先跑个 /help 这样的斜杠命令?(那就不必惊动大模型)
  • 这次请求属于哪个 agent、哪个会话?历史记忆要不要先捞出来?
  • 现在是不是「编码模式」?要不要给 agent 额外挂上改代码的工具?
  • 大模型吐字、调工具、返图片——这些内部事件怎么变成前端认得的流式协议?
  • 万一中途报错、或者用户点了「停止」,已经建好的 agent、打开的数据库句柄谁来关?

Runtime 就是把这一长串编排成固定顺序、并且留好插入点的那层代码。

一句话直觉

Runtime.run() 想成一条带固定工位的流水线:

  • 8 个「相位点」= 传送带上的 8 个安检口,每个口都可以站若干检查员(hook),挨个检查这单货、盖章放行,或者当场拦下。
  • 3 个「固定步」= 流水线上写死的加工机器(分发命令、装配 agent、开机跑 agent),它们不是安检口,不能被替换,是主干。

检查员(hook)可以随便增删,机器(固定步)是骨架。这就是「可插拔的 hook 架构 + 固定的中央循环」。

用起来什么样

调用方非常薄——路由拿到请求,new 一个 Runtime,async for 把它 yield 的东西转发出去:

# 真实调用点:app/_app.py:124-129(略简化)
rt = Runtime(workspace=workspace, app_services=self._app_services)
async for item in rt.run(request): # item 是一个 SSE envelope 对象
yield item # 直接转发给 HTTP 流

workspace.py:265 里还有一处一模一样的调用。所有复杂度都被关在 run() 里。


2. 顶层全景(它大概怎么转)

一张图:8 相位 + 3 固定步

先说怎么读这张图:从上往下是时间顺序;左边 [相位 N] 是可插拔的 hook 安检口,右边 {固定 N} 是写死的主干机器;skip_agent 是一个贯穿全程的开关,一旦被拨到 True,后面三个固定步就整段跳过,但相位照跑

一次 AgentRequest

_normalize + _build_context ← 造出 HookContext(全程随身携带)

┌─────────────────▼──────────────────────────────────────┐
│ [相位1] PRE_DISPATCH 请求规范化后、分发前 │
│ └ SHORT_CIRCUIT → 吐 payload 并直接 return │
├────────────────────────────────────────────────────────┤
│ {固定1} slash 命令分发 /help、/model… 命中就不惊动模型 │
│ └ 命中 → 吐命令结果,拨 skip_agent=True │
│ └ 未命中 ↓ │
│ [相位2] POST_DISPATCH 没命中命令时的兜底 │
├────────────────────────────────────────────────────────┤
│ [相位3] PRE_AGENT_BUILD 会话加载 / 模式预置(若未跳过) │
├────────────────────────────────────────────────────────┤
│ {固定2} AgentBuilder.build 为这一请求现装一个 agent │
│ [相位4] POST_AGENT_BUILD agent 建好,注入模式上下文 │
│ [相位5] PRE_EXECUTE bootstrap / prompt 刷新 / 环境栈 │
├────────────────────────────────────────────────────────┤
│ {固定3} 执行 agent emit_response_created → Executor 驱动│
│ └ reply_stream 事件 → Envelope 翻成 SSE │
├────────────────────────────────────────────────────────┤
│ [相位6] POST_RESPONSE 会话保存 / cron 回写(总会跑) │
│ envelope.finalize() 收尾 response │
└────────────────────────────────────────────────────────┘

异常?→ [相位7] ON_ERROR + error/cancel envelope

总是 → agent.close() → [相位8] FINALLY(幂等清理)

依据:相位枚举 runtime/phases.py:28-38;主流程 runtime/runtime.py:61-169

部件一句话职责

部件干什么在哪(path:symbol)
Runtime8 相位主编排,唯一的中央循环runtime/runtime.py:32 Runtime
Phase8 个相位点的枚举runtime/phases.py:28 Phase
HookContext全程随身的「请求上下文」,hook 之间靠它传状态runtime/hooks.py:73 HookContext
HookRegistry按相位存 hook、拓扑排序、执行一个相位runtime/hooks.py:227 HookRegistry
SlashCommandRegistry/xxx 文本解析并派发到命令处理器runtime/slash_command_registry.py:45
AgentBuilder为每个请求现场装配一个 QwenPawAgentruntime/builder.py:22 AgentBuilder
AgentExecutor驱动 agent.reply_stream,套心跳runtime/executor.py:23 AgentExecutor
EnvelopeSSE 状态机,把 agent 事件翻成前端协议runtime/envelope.py:27 Envelope
PromptManager按优先级拼系统 prompt 的各个片段runtime/prompt_manager.py:58
AgentMode一个「模式」= 命令+工具+hook+prompt 的打包modes/base.py:30 AgentMode

主线走一遍(高层)

  1. 规范化:_normalizesession_id/user_id,_build_context 造出贯穿全程的 HookContext(runtime.py:54-55)。
  2. PRE_DISPATCH 相位:分发前最后的介入点。
  3. 固定步:slash 分发:取最后一条用户消息的文本,丢给命令注册表;命中 /model 之类就直接产出结果,不惊动大模型。
  4. 没命中才走 POST_DISPATCHPRE_AGENT_BUILD
  5. 固定步:build:AgentBuilder.build(ctx) 把工具、prompt、模型、中间件全部装进一个新 agent。
  6. POST_AGENT_BUILD / PRE_EXECUTE 相位:agent 已就位,做最后的上下文注入。
  7. 固定步:execute:AgentExecutor 驱动 agent 吐事件,Envelope 翻成 SSE。
  8. POST_RESPONSE:保存会话等收尾 hook(不管前面跳没跳 agent 都会跑)。
  9. finally:先 agent.close(),再跑 FINALLY 相位做幂等清理。

3. 核心原理(逐个机制,由浅入深)

3.1 三种 hook 返回语义:CONTINUE / SHORT_CIRCUIT / SKIP_AGENT

它要解决的小问题: hook 检查完这单货,得能告诉主流程「往下走 / 我给答案了别麻烦 agent 了 / agent 别建了但别的还照跑」。这就是三种返回。

思路: 一个枚举 HookAction 表态,HookResult 顺带捎上要吐的消息。

返回含义主流程反应
CONTINUE默认,放行继续下一个 hook / 相位
SHORT_CIRCUIT「我已产出结果」payload(一个 Msg)翻成完整 SSE 序列吐出;当前相位立刻停
SKIP_AGENT「别建也别跑 agent」跳过 build+execute 两个固定步;所有相位仍按序执行

依据:runtime/hooks.py:46-65 HookAction / HookResult

关键细节(容易读错的地方): SHORT_CIRCUIT不同相位效果不一样——只有 PRE_DISPATCH 命中时是真正 return 整个 run()(runtime.py:64-67);而 POST_DISPATCH / PRE_AGENT_BUILD / PRE_EXECUTE 命中时,是吐出 payload 后把 skip_agent 拨 True(runtime.py:82-85 / 92-95 / 111-114),然后继续往下走到 POST_RESPONSE + finalize。也就是说,后三处的「短路」不真结束,只是跳过 agent。hooks.py 顶部注释把这点简化成了「emit and ends」,以代码为准。

SKIP_AGENT粘性的:一个相位里只要有一个 hook 报了 SKIP_AGENT,该相位其余 hook 照跑,但相位最终结果携带 SKIP_AGENT(hooks.py:276-283 HookRegistry.run)。

3.2 HookContext:一张贯穿始终的「请求便签」

它要解决的小问题: 8 个相位、几十个 hook,彼此不认识,怎么传状态?比如 PRE_AGENT_BUILD 捞出来的会话状态,得让 build 步用上。

思路: 一个 dataclass,请求一进来就造好,从头带到尾,每个 hook 都拿到同一个实例。稳定的字段用具名属性(方便 IDE 跳转),临时的塞两个「逃生舱」dict。

HookContext(runtime/hooks.py:73)
├── 身份:request / session_id / agent_id / root_session_id / workspace_dir
├── 容器(只读):workspace / app_services
├── 逐相位填充:input_msgs / agent_config / session_state / agent / error
└── 逃生舱:
├── mode_state —— 按模式名分区的私有状态(见 3.5)
└── extras —— 短命的 hook 对传流量(如给 FINALLY 留的清理句柄)

依据:runtime/hooks.py:73-108_build_contextruntime.py:185-208 组装它——注意 agent_id 会优先用 workspace 解析出的 id 而非裸 "default"

3.3 HookRegistry:按相位存、拓扑排序、执行

它要解决的小问题: 同一相位里若干 hook,谁先谁后?优先级还不够——有时 A 必须在 B 之后(比如「加载会话状态」必须先于「用会话状态还原 mission」)。

思路: 每个 hook 声明 priority(小的先)外加可选的 before / after 名字约束;注册表对一个相位的 hook 做拓扑排序,排序结果缓存,注册新 hook 时失效。

排序规则(_topo_sort,hooks.py:139):

  • before=(X,) → 加一条「本 hook → X」的边;after=(X,) → 加「X → 本 hook」。
  • 就绪集里用 (priority, 注册序号)确定性排序,保证多次运行结果一致。
  • 引用了不存在的 hook 名 → 打 warning 但当无操作(不报错)。
  • 检测到 → 抛 HookCycleError,启动即失败,不拖到线上才乱。

执行一个相位(HookRegistry.run,hooks.py:264):挨个 await hook.run(ctx);遇 SHORT_CIRCUIT 立即返回;SKIP_AGENT 记下但继续;hook 抛异常不吞,直接冒泡到 run()ON_ERROR 链(测试依赖这个契约)。

一个简化演示,帮建立直觉:

# 示意,非源码:一个相位内的执行骨架
final = HookAction.CONTINUE
for hook in topo_sorted(hooks_of_this_phase): # 拓扑序,确定性
result = await hook.run(ctx)
if result.action == SHORT_CIRCUIT:
return result # 当前相位立刻停,带出 payload
if result.action == SKIP_AGENT:
final = SKIP_AGENT # 粘性:记住但不停
return HookResult(action=final) # 重点看:SKIP_AGENT 会带到相位末尾

多个注册表还能 merge() 成一个(hooks.py:285),用于跨 workspace 合并。

3.4 两类 hook 基类:LifecycleHook vs ModeGatedHook

它要解决的小问题: 有的 hook 每个请求都该跑(会话加载),有的只在某模式激活时才跑(编码模式注入)。别让作者每次手写「先判断模式激活没」——那是老代码反复漏掉的 bug。

思路:类层面分家,作者凭意图选基类:

基类什么时候跑在哪
LifecycleHook到了它的相位就跑,不管模式hooks/base.py:22
ModeGatedHook只有 owner_mode.is_active(ctx) 为真才跑modes/base.py:81

ModeGatedHook 的巧处:它重写了 run() 先跑模式门禁,门没开就返回空 HookResult;子类只实现 _run(),永远不用自己写门禁(modes/base.py:92-98)。

实例:SessionLoadHook(每请求加载会话,hooks/session/session_hook.py:21,PRE_AGENT_BUILD)是 LifecycleHook;ProjectDirInjectionHook(编码模式才注入项目目录,modes/coding/hooks.py:11)是 ModeGatedHook

3.5 AgentMode:把「一个模式」打包成四件套

它要解决的小问题: 「编码模式」不是单一开关——它要额外的命令、额外的工具、额外的 hook、额外的 prompt 段落。散着注册容易漏、容易忘了谁属于谁。

思路: 一个 AgentMode 把这四样捆在一起,setup(workspace)唯一的注册入口——把四样分别推进 workspace 的四个注册表。于是「哪个模式拥有什么」只要看 mode.commands()/.tools()/.hooks()/.prompt_contributors() 就一目了然。

AgentMode.setup(workspace) (modes/base.py:40)
├── commands() → slash_command_registry.register(...)
├── tools() → tool_registry.register(...)
├── hooks() → hook_registry.register(...)
└── prompt_contributors() → prompt_manager.register(...)

每个模式必须重写 is_active(ctx),默认返回 False——没配置的模式绝不悄悄漏进请求(modes/base.py:68-78)。两个内置模式(概念层面,实现细节在别处):

模式何时激活带来什么在哪
coding(编码)agent_config.coding_mode.enabled 为真项目目录注入 hook + 编码人格 prompt + 改代码工具modes/coding/__init__.py:24 CodingMode
mission(任务)会话里 mission_active 为真任务状态的加载/保存 hook + 任务 promptmodes/mission/__init__.py MissionMode

哪些模式此刻激活,由 active_mode_names(ctx) 汇总(app/workspace/workspace_plugins.py:55),build 步会读它来决定挂哪些工具。

注:mission 模式的安全/技能实现、coding 模式的沙箱与改代码机制不在本章展开,分别见 第 05 章第 03 章

3.6 两个固定步之首:slash 命令分发

它要解决的小问题: 用户打 /model qwen-max 只想切模型,没必要惊动大模型跑一整轮。

思路: 一个每 workspace 一个SlashCommandRegistry,把历史上四套并行的命令机制(daemon / control / conversation / skill)统一到一个派发点

  • 解析:文本 lstrip 后必须以 / 开头,取第一个词做命令名(大小写不敏感),其余原样作参数(resolve,slash_command_registry.py:84)。
  • 派发:命中就 await spec.handler(ctx, args);没命中且注册了唯一的 fallback,就交给 fallback——通常用于 /<技能名> 解析(dispatch,:108)。
  • 命中后主流程把结果 Msg 交给 Envelope.from_msg 翻成完整 SSE,并拨 skip_agent=True(runtime.py:74-78)。

命令用声明式的 CommandSpec 描述,category 字段记录出身(daemon/control/conversation/skill/user…),内置适配器把老四套包成 CommandSpec 注册进来(runtime/builtin_commands.py)。注意它故意不叫 CommandRegistry——那个名字是 IM 渠道的优先级路由,和 slash 分发无关(见 第 04 章)。

3.7 两个固定步之二:AgentBuilder 现场装配 agent

它要解决的小问题: agent 不是全局单例——每个请求的工具集、prompt、模型、上下文策略可能都不同。得为每次请求现装一个

思路: AgentBuilder.build(ctx)(builder.py:91)是一台装配机,把散落各处的零件汇到一起,从外部注入 QwenPawAgent 构造函数——agent 自己不 new 任何依赖。

装配顺序(高层):

build(ctx) (runtime/builder.py:91)
1. 载 agent_config,按 ACP 请求可能临时打开编码模式
2. 校验有可用模型(没有就抛 RuntimeError)
3. 解析生效技能 resolve_effective_skills → 见第03章
4. 算激活模式 active_mode_names(ctx)
5. 起 governor(治理/审计层) → 见第05章
6. 收集工具:编码模式工具 + driver/MCP 工具 → 见第04/05章
7. 建模型+formatter,可选 scroll 上下文策略 → 见第02/06章
8. build_toolkit(...) 工具来自 local_workspace.list_tools()
9. build_prompt(ctx) 走 PromptManager(见 3.9)
10. 组装中间件(工具协调 / 结果裁剪 / 观测)
11. new QwenPawAgent(model, system_prompt, toolkit, middlewares, ...)
12. 若 session_state 已被 SessionLoadHook 填好 → agent.load_state_dict

工具的来源是每 workspace 的 QwenPawLocalWorkspace.list_tools()(build_toolkit,builder.py:36-68),extra_tools / memory_tools 追加在后面。工具在这里被 PolicyGuardedToolGuardedFunctionTool 包一层做权限守卫——这层只提一句,细节转 第 05 章

agent 内部的 ReAct 循环、模型 provider 选择、formatter 细节 → 第 02 章

3.8 两个固定步之三 + SSE 状态机:Envelope

它要解决的小问题: agent 内部吐的是 agentscope 的 EventType 事件(文本块开始/增量/结束、思考块、工具调用、工具结果、数据块……);前端 Builder.tsx 要的是另一套 envelope 协议。得有个翻译机,还得记住「现在开到第几个块了」。

思路: Envelope每次 run() 一个实例的状态机(envelope.py:27)。核心是 translate_event(event)(:137)——一个大 if/elif,把每类事件翻成 0..N 个 schema 对象并 yield;内部用几个 dict(_text_blocks / _reasoning_blocks / _tool_calls / _data_blocks)累积在途状态,用 _seq_counter 给每个吐出的对象盖递增序号(_tag_seq,:83)。

固定步「执行」怎么驱动它(runtime.py:118-124):

# 真实流程,runtime.py:120-124(略简化)
async for ev in envelope.emit_response_created(): # 先发 Created→InProgress 两拍
yield ev
executor = AgentExecutor(ctx.agent, envelope)
async for ev in executor.run(ctx.input_msgs): # 执行器驱动 reply_stream
yield ev

Envelope 的关键出口:

方法干什么在哪
emit_response_created开场:response 从 Created→InProgressenvelope.py:91
translate_event把一个 agent 事件翻成 SSE 对象envelope.py:137
heartbeat空闲时重发一次 response 当保活envelope.py:689
from_msg把 slash 命令的成品 Msg 翻成完整序列envelope.py:696
finalize正常收尾 response(幂等,_finalized 守卫)envelope.py:752
error_envelope / cancel_envelope出错/取消时把 response 收成 Failedenvelope.py:736 / 744

一处巧思:数据块(base64 图/音/视频)在 DATA_BLOCK_DELTA只累积不吐,因为半截 base64 前端解不了,直到 DATA_BLOCK_END 才一次性发完整 payload(envelope.py:530-538)。

3.9 系统 prompt 的组装位置:PromptManager

它要解决的小问题: 系统 prompt 不是一坨字符串,而是身份、AGENTS.mdSOUL.md、多模态提示、编码人格、环境上下文……一段段拼出来的,还得能按请求开关某段。

思路: 每段是一个 PromptContributor,声明 name + priority(小的靠前);PromptManager 按优先级跑一遍,把非空片段用 PROMPT_SEPARATOR("\n\n",是个钉死的常量,改它算破坏契约)join 起来(prompt_manager.py:23,89)。一个 contributor 抛异常只记日志并跳过,不拖垮整份 prompt。

build 步通过 AgentBuilder.build_prompt(builder.py:279)调它:优先用 workspace 自己的 PromptManager,没有就 build_default_prompt_manager() 兜底——后者预装了 9 个内置 contributor(prompt_contributors.py:297 _ALL_CONTRIBUTORS;文件顶部注释写「7 个」已过时,以这份清单为准)。

prompt 各段具体内容、怎么按模式变化 → 第 02 章。本章只定位它在编排中的位置:它是 build 固定步里的一个子步


4. 深入实现:异常、心跳、收尾

4.1 心跳:别让长等待掐断连接

工具审批可能让 agent 卡住几十秒。要是这期间一个字节都不发,HTTP 流可能被中间设备判死。

AgentExecutor.run(executor.py:36)把 agent 的事件流用 _iter_with_heartbeat 包一层:每 25 秒(HEARTBEAT_INTERVAL_SECONDS,heartbeat.py:7)没等到新事件,就吐一个 _HEARTBEAT_TICK 哨兵,执行器据此调 envelope.heartbeat() 发一拍保活(executor.py:51-53)。

巧处在 heartbeat.py:11asyncio.shield:wait_for 超时会取消它等的东西,但底层那个 __anext__() 任务要跨多个心跳存活,所以用 shield 保护,超时只放弃这次等待、下轮继续 await 同一个 pending 任务——否则每次心跳都会丢掉一整段在途状态。

4.2 异常与取消:两条分支

run()try 兜两类异常(runtime.py:133-154):

情况走哪动作
CancelledError / KeyboardInterrupt(用户点停)runtime.py:133ctx.error → 跑 ON_ERROR 相位 → cancel_envelope()raise
其它任何 BaseExceptionruntime.py:139记 error 日志 → 跑 ON_ERROR → 从 ctx.extras["_error_text"] 取错误文案 → error_envelope(...)raise

两条都先让 ON_ERROR hook 介入(可做错误规范化、写审计),再把 response 收成失败态吐给前端,最后重新抛出。

4.3 finally:关 agent 的顺序有讲究

finally 块(runtime.py:155-169)总会跑,且顺序是刻意的:

  1. agent.close()(若 agent 建过且有 close)。为什么先关 agent?因为 QwenPawAgent.close(agents/react_agent.py:234)要停 governor、刷审计日志、释放 scroll 历史库的文件句柄、清过期工具结果——这些必须在下游 FINALLY hook 观察上下文之前完成。close 失败只 warning,不阻断。
  2. FINALLY 相位做幂等清理(关 MCP、重置 ContextVar 等)。

这就是「先固定步收尾、再插件收尾」的顺序契约。


5. 巧妙之处(可借鉴的技术)

  • 固定步只碰 agent 两处。 整条流水线里,真正接触 agent 的只有 AgentBuilder.buildAgentExecutor.run;其余全是 hook。这让「agent 相关的改动」和「编排相关的改动」天然隔离(runtime/runtime.py 通篇结构)。
  • 相位点固定、逻辑可插拔。 8 个相位写死在枚举里,永不增删;要加能力就注册 hook/mode,不改 Runtime。中央循环因此极稳(phases.py:28)。
  • 类层面区分「总跑」与「模式门禁」。 LifecycleHook / ModeGatedHook 让作者凭基类就选对语义,门禁不再手写、不再漏写(modes/base.py:81)。
  • 拓扑排序 + 启动即验环。 hook 顺序用 before/after 声明,确定性排序,发现环立刻 HookCycleError 而非线上乱序(hooks.py:139-224)。
  • SKIP_AGENT 是粘性的、且相位照跑。 命令命中或某 hook 决定跳过 agent 时,会话保存等 POST_RESPONSE 收尾依然执行——跳过的是「算」,不是「收尾」(hooks.py:276-283 + runtime.py:126-127)。
  • 半截 base64 不吐。 数据块攒齐再发,避免前端拿到解不了的碎片(envelope.py:530-538)。

6. 边界与局限

  • 相位数固定为 8。 想在别处插逻辑,只能落到现有某个相位;没有「自定义相位」这回事(phases.py)。
  • SHORT_CIRCUIT 语义不统一。 只有 PRE_DISPATCH 是真 return,其余三处退化成 skip_agent=True 后继续(见 3.1)。读代码时别被 hooks.py 顶部那句「emit and ends」的简化注释带偏。
  • Runtime 是每 workspace 一实例、每请求一 run() 跨请求的共享状态不在这里,而在 workspace 与 AppServiceManager(见 第 06 章)。
  • hook 抛异常不被相位吞。 直接冒泡进 ON_ERROR——这是契约(测试依赖),写 hook 时要自己保证健壮(hooks.py:264 文档串)。
  • build 步依赖「有可用模型」。 没配激活模型直接 RuntimeError,请求当场失败(builder.py:126-129)。

7. 代码地图(导航索引)

主题文件符号
8 相位枚举src/qwenpaw/runtime/phases.pyPhase
主编排循环src/qwenpaw/runtime/runtime.pyRuntime.run / Runtime._build_context
hook 返回语义src/qwenpaw/runtime/hooks.pyHookAction / HookResult
请求上下文src/qwenpaw/runtime/hooks.pyHookContext
hook 拓扑排序/执行src/qwenpaw/runtime/hooks.pyHookRegistry / _topo_sort
两类 hook 基类src/qwenpaw/hooks/base.py · src/qwenpaw/modes/base.pyLifecycleHook · ModeGatedHook
会话加载/保存 hooksrc/qwenpaw/hooks/session/session_hook.pySessionLoadHook / SessionSaveHook
模式打包与注册src/qwenpaw/modes/base.pyAgentMode.setup
编码/任务模式src/qwenpaw/modes/coding/__init__.py · modes/mission/__init__.pyCodingMode · MissionMode
slash 命令注册表src/qwenpaw/runtime/slash_command_registry.pySlashCommandRegistry.dispatch / .resolve
内置命令适配器src/qwenpaw/runtime/builtin_commands.pyCommandSpec 适配器
每请求装配 agentsrc/qwenpaw/runtime/builder.pyAgentBuilder.build / .build_toolkit
执行驱动src/qwenpaw/runtime/executor.pyAgentExecutor.run
心跳保活src/qwenpaw/runtime/heartbeat.py_iter_with_heartbeat / HEARTBEAT_INTERVAL_SECONDS
SSE 状态机src/qwenpaw/runtime/envelope.pyEnvelope.translate_event / .finalize / .error_envelope
prompt 装配src/qwenpaw/runtime/prompt_manager.pyPromptManager / PROMPT_SEPARATOR
prompt 各段src/qwenpaw/runtime/prompt_contributors.pybuild_default_prompt_manager / _ALL_CONTRIBUTORS
每 workspace 插件面src/qwenpaw/app/workspace/workspace_plugins.pyWorkspacePlugins.active_mode_names
调用入口src/qwenpaw/app/_app.py · app/workspace/workspace.pyRuntime(...).run(request)
agent 收尾src/qwenpaw/agents/react_agent.pyQwenPawAgent.close