Repository navigation
kb init(jiuwenswarm): deepen #277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| --- | ||
| title: "jiuwenswarm/common 审查规则:配置读写事务、跨仓契约与持久化键稳定性" | ||
| created: 2026-09-30 | ||
| updated: 2026-09-30 | ||
| type: rule | ||
| tags: [jiuwenswarm] | ||
| sources: [] | ||
| --- | ||
|
|
||
| # jiuwenswarm/common 审查规则:配置读写事务、跨仓契约与持久化键稳定性 | ||
|
|
||
| ## JIUWENSW-I1 — `jiuwenswarm/common/config.py::update_config` 的 mutator 不能再次进入写锁,返回值决定是否写盘 | ||
|
|
||
| - `_CONFIG_WRITE_LOCK`(`threading.Lock`)和 portalocker 文件锁都不可重入。如果 mutator 里调用了任何经过 `update_config` 或 `config_write_lock` 的函数(如 `update_ttse_enabled_in_config`、`save_models_candidate`、`update_permissions_*`),同一线程会一直阻塞,到 `lock_timeout` 后抛出 `TimeoutError`,写入失败。 | ||
| - mutator 的返回值才是要 dump 的对象。返回 `None` 表示不写盘(`delete_*` 没命中时就靠它跳过)。新写的 mutator 如果漏了 `return data`,修改会被悄悄丢掉。 | ||
| - mutator 内抛出异常会中止写入。校验(`raise_if_invalid`、重复 id 检查)必须放在 mutator 里面或调用之前,不能放到 `update_config` 返回之后。 | ||
| - 写盘后要读出来展示的数据,应在 `update_config` 返回后另发一次调用,不要在 mutator 里读。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I4 — 新增的 config.yaml 写入口必须走 `update_config` 加 `dump_yaml_round_trip`,不要复制裸读改写的写法 | ||
|
|
||
| - AgentServer 和 Gateway 两个进程会同时写同一个 config.yaml。`update_channel_in_config`、`update_browser_in_config`、`set_auto_memory_enabled` 这类裸 `load_yaml_round_trip` 到 `dump_yaml_round_trip` 的写法不持锁,会覆盖另一个进程的修改。新代码不要照抄这种写法;PR 改到这些旧函数时,优先迁移到 `update_config`。 | ||
| - 不要使用 `set_config`(直接 `open(..., "w")` 后 `yaml.safe_dump`),也不要自己开文件写。这种写法不是原子的,读者可能读到写了一半的文件,还会丢掉注释和引号风格。 | ||
| - `dump_yaml_round_trip` 的临时文件必须和目标文件放在同一目录(`os.replace` 不能跨文件系统),异常路径要删掉 tmp 文件。不要去掉 `_atomic_replace` 对 Windows `PermissionError` 的重试。 | ||
| - `_CONFIG_YAML_PATH`、`_load_yaml_round_trip`、`_dump_yaml_round_trip` 是下游模块在导入的兼容别名,不能删除或改名。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I13 — 读取开关时注意字符串布尔值;evolution 开关只有一个来源 | ||
|
|
||
| - 通过 `${VAR:-false}` 注入的值是字符串,而 `bool("false")` 为 True。可能来自环境变量替换的新开关应使用 `coerce_config_bool`。`is_subagent_runtime_enabled` 里的 `bool(...)` 就有这个问题,不要照抄。 | ||
| - `_get_evolution_config` 有意只读 `react.evolution`,不读顶层的 `evolution`,也不读环境变量覆盖,以免残留的部署变量改变运行中 agent 的能力。`get_skill_evolution_enabled` 和 `get_evolution_auto_save_enabled` 用 `is True` 严格判断。PR 不能把旧路径或 env 回退加回来。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I17 — `jiuwenswarm/common/client/` 下的抽象接口是跨仓库契约,改签名必须和 Gateway 副本同步 | ||
|
|
||
| - `AgentServerClient` 和 `ThirdAgent` 在 Gateway 仓库里各有一份副本,两边靠 E2A 线协议对齐。修改方法名、keyword-only 参数或返回类型时,必须同步 Gateway 副本和所有实现(WebSocket、AgentOS Router 等)。 | ||
| - 新增 `@abstractmethod` 会让所有没实现它的子类无法实例化。可选能力应提供默认实现(参考 `ThirdAgent.normalize_agent_type`)。 | ||
| - `send_request` 的 `timeout=None` 表示使用客户端默认值。实现方必须采用调用方传入的更大值(例如 cron 的 `timeout_seconds`),不能再被内层默认值提前截断。 | ||
| - `UnsupportedThirdAgent` 的返回形状(`ok` 为 False,带 `error`,`code` 为 UNSUPPORTED)是调用方依赖的约定。`normalize_agent_type` 只对内置的 jiuwenswarm 不区分大小写,registry 里的名称保留原大小写。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| --- | ||
| title: "video_duplex 全双工扩展审查规则:任务检查点、授权投递与 Provider 协议" | ||
| created: 2026-09-30 | ||
| updated: 2026-09-30 | ||
| type: rule | ||
| tags: [jiuwenswarm] | ||
| sources: [] | ||
| --- | ||
|
|
||
| # video_duplex 全双工扩展审查规则:任务检查点、授权投递与 Provider 协议 | ||
|
|
||
| ## JIUWENSW-I21 — 检查点 push/ACK 只走一次:`_pending` 要在推送前注册、在 finally 里清理,任何一侧都不许重试 | ||
|
|
||
| - `RemoteTaskEndpoint.call` 必须先把 future 放进 `_pending[command_id]`,再 `await send_runtime_push(...)`。ACK 可能在推送还没返回时就到了。`finally` 里必须 `pop` 并取消还没完成的 future。 | ||
| - 每次 `call` 都会生成新的 `command_id`。在调用方或 `call` 里加重试,会绕开 `store.replay` 的去重,`claim` 这类动作就会被执行两次。超时应当直接失败。`handle_checkpoint_push` 在 ACK 发送失败时只记日志,不重做,这一行为要保留。 | ||
| - `resolve_checkpoint_ack` 在 `set_result` 之前要核对 `(user_id, session_id, execution_request_id)` 与 `identity` 是否一致。ACK 里的 `execution_request_id` 来自 `chunk.request_id`,改字段名时两端要一起改。 | ||
| - `call` 靠 `result.get("error")` 判断失败,所以 `execute` 的成功返回值里不能出现 `error` 键。 | ||
| - `_endpoints` 是 `WeakValueDictionary`。endpoint 的生命周期由 `AgentTaskExecutor.endpoint` 这个强引用维持。重构后如果不再持有这个引用,推送会报 `Task Gateway endpoint is unavailable`。 | ||
| - `handle_checkpoint_push` 遇到非 `EVENT` 帧必须返回 False,调用方靠这个返回值把帧交给普通消息投递。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I23 — `AgentTaskExecutor.run`:执行未确认 settle 前,既不能返回,也不能抛普通异常 | ||
|
|
||
| - 成功路径:`_run` 返回后,如果 `interaction.state` 还是 `pending`,必须抛 `ExecutionUncertain`。如果 `execution_bound` 为真,必须先 `wait_settled` 再返回。 | ||
| - 异常路径:已 bind 但 `execution_settled` 还没置位时,必须包装成 `ExecutionUncertain("... no work was replayed")` 抛出,不能让原始异常直接冒出去。这样才能和可以安全重跑的普通失败区分开,避免有副作用的 Agent 工作被重放。 | ||
| - `InteractionPending` 分支要先 `wait_settled`,再重新抛出。 | ||
| - 产物查询要遍历 `prior_request_ids` 加上当前 `request_id`。某一轮查询失败时只设置 `artifact_lookup_failed`,不能让整个结果失败。 | ||
| - 新增的等待必须设上限并转成 `ExecutionUncertain`,参照 `wait_settled` 的 20s。`cancel` 里轮询 `execution_settled` 的循环现在没有上限,改到这里时不要再加类似的无界等待。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I24 — 授权链上 `ValueError` 表示永久失权,瞬时故障必须抛 `RuntimeError` | ||
|
|
||
| - 每个 `handle_*` 和 `start` 都必须先通过 `self.scope`(内部调用 `authorized_scope`)拿到 `(owner, scope)`,然后才能访问 `self.service`。`task-duplex:` 作用域还要通过 AgentServer 的 `SESSION_GET_METADATA` 核对 `user_id == owner`。`handle_answer` 自己拼接 `"task-duplex:" + session_id`,也必须经过 `scope`。 | ||
| - 在 `task_identity` 里,空 owner 表示现有的匿名作用域。不要用服务器 OS 用户或其他值去填它。 | ||
| - `changed.deliver` 每次事件都会重新授权,捕获到 `ValueError` 就删除订阅,并清理对应的 `_approval_cards`。所以网络或 AgentServer 的瞬时失败必须保持为 `RuntimeError`,像 `task_agent_query` 那样;改成 `ValueError` 会把在线订阅者悄悄踢掉。反过来,身份被否决时必须抛 `ValueError`。 | ||
| - `subscribers` 的键是 `(owner, session, id(ws))`,`_approval_cards` 的键是在它后面加 task id。两者的清理条件要保持一致。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I25 — `notification_timeout` 按 `deliver` 的最坏耗时推算,`deliver` 里增减 await 时要同步改公式 | ||
|
|
||
| - `TaskService` 的 `notification_timeout=AGENT_QUERY_TIMEOUT + 2 * EVENT_SEND_TIMEOUT + 1` 对应的是:一次 `authorized_scope` 查询(`AGENT_QUERY_TIMEOUT`),加两次 `send_event`(各 `EVENT_SEND_TIMEOUT`)。 | ||
| - `deliver` 最后还调用了 `_sync_approval_card`,里面又有一次 `wait_for(..., EVENT_SEND_TIMEOUT)`,公式没把它算进去。在 `deliver` 或 `_sync_approval_card` 里新增 await、或调整这些超时时,要一起修正公式,并确认总耗时没有超出预算。 | ||
| - 所有 `channel.send_event` 都必须包在 `asyncio.wait_for(..., EVENT_SEND_TIMEOUT)` 里。不同订阅者之间要用 `gather(..., return_exceptions=True)` 并行投递(注释写明一个慢连接不能占掉其他订阅者的预算),不要改成顺序 await。 | ||
| - 只要有任意一个订阅者投递失败,就必须抛 `RuntimeError("Task notification delivery was incomplete")`,不能吞掉。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I26 — 任务管理工具:名字集合、schema、手写类型校验、`operate` 分支四处同步改;冲突按异常类型区分 | ||
|
|
||
| - `_TASK_TOOLS` 里的名字必须和 `task_management_tools()` 里的名字一一对应。只在集合里有、specs 里没有的名字,会让 `parse_qwen_omni_tool_call` 的 `next(...)` 抛 `StopIteration`;只在 specs 里有的名字会被当成不支持的工具拒绝。 | ||
| - 参数白名单来自 schema 的 `properties`/`required`,但类型校验是手写的: | ||
| - `answers` 只查是否为列表、长度是否在 1–32。 | ||
| - `revision`、`offset`、`queue_version` 必须是整数。 | ||
| - 其余属性一律当作非空字符串,长度不超过 256(`instruction` 不超过 4000)。 | ||
| - `answers` 的元素这里没有校验(schema 里声明的是 string/maxLength 4000)。 | ||
| - `VideoSearchManager.operate` 最后的 `else` 按 `jiuwen_task_modify` 处理。新增任务工具必须加显式分支,否则会被当成 modify 执行。 | ||
| - 冲突用 `QueueVersionConflict`/`TaskRevisionConflict` 捕获(`errors.py` 声明文案不属于控制协议),不要改成匹配错误字符串。这两个类继承自 `ValueError`,如果前面放了更宽的 `except ValueError`,会把冲突回执吞掉。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I27 — 委派工具的调度字段(`independent`/`depends_on`/`resources`)和兼容字段在多处硬编码 | ||
|
|
||
| - `qwen_omni_tools()` 的 schema。 | ||
| - `parse_qwen_omni_tool_call` 的排除集合 `set(arguments) - {...}` 和逐项校验。 | ||
| - `handle_qwen_tool` 里的 `scheduling` 复制。 | ||
| - `AgentTaskExecutor.run` 的上下文选择(`independent` 只带 `depends_on` 的结果,其余情况带最近 6 条)。 | ||
| - `public` 的投影。 | ||
| 漏改时,新字段要么被当成第二个 task 字段、报 `exactly one task field`,要么被悄悄丢掉。 | ||
| - `jiuwen_delegate` 的 schema 只公开 `task`,但解析时仍接受 `_DELEGATE_ARGUMENT_NAMES` 里的旧字段名。旧版 `jiuwen_research` 要求 arguments 里只有 `query` 一个键。`QwenOmniToolCall.query` 是兼容别名。这些都不要删。 | ||
| - `handle_qwen_tool` 用字面量 `{"jiuwen_delegate", "jiuwen_research"}` 判断是否委派。改 `QWEN_OMNI_DELEGATE_TOOL_NAME`/`QWEN_OMNI_RESEARCH_TOOL_NAME` 时这里要一起改。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I31 — JoyAI 适配:动作标记与提示词、音频采样率与编码都要成对修改 | ||
|
|
||
| - `parse_action` 的判定顺序固定: | ||
| - 先找 delegation 标记,标记之前是 response,之后是 delegation。 | ||
| - 然后找 silence,再找 response。 | ||
| - 空文本算 silence,没有标记的文本算 response。 | ||
| `_USER_KNOWLEDGE_GUARD` 让模型输出 `</response> 简短说明 </delegation> ...`。改提示词或 `JOYAI_TASK_INSTRUCTIONS` 里的标记格式时,`_RESPONSE_MARKER`/`_SILENCE_MARKER`/`_DELEGATION_MARKER` 和判定顺序要一起改。 | ||
| - instruction 为空时,`ground_user_instruction` 必须返回空串,这样纯帧轮次不会带上约束。此时 `request_frame` 用 128 max_tokens,有指令时用 512。工具结果只能放在“已确认的九问工具结果”这个事实段里,不能拼进【用户原话】,以防注入。 | ||
| - ASR 的 setup 里 `sample_rate: 16_000` 必须和 `_pcm16_from_wav` 的 `target_rate` 一致,`struct.pack(">iii", -1, 0, 0)` 的帧头也要保留。TTS config 里的 `sample_rate: 24_000` 必须和 `synthesize_channel` 里 `_wav_from_pcm16(pcm, 24_000)` 一致。只改一边会得到变速或失真的音频,而且不会报错。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| --- | ||
| title: "Process CLI 频道(jiuwenswarm/channels/process_cli)审查规则" | ||
| created: 2026-09-30 | ||
| updated: 2026-09-30 | ||
| type: rule | ||
| tags: [jiuwenswarm] | ||
| sources: [] | ||
| --- | ||
|
|
||
| # Process CLI 频道(jiuwenswarm/channels/process_cli)审查规则 | ||
|
|
||
| ## JIUWENSW-I32 — 会话 prepare 必须在 finally 中配对 _abort_prepared_session,且 abort 不得覆盖主错误 | ||
|
|
||
| - 调用 `prepare_session_create`、`prepare_session_switch` 或 `prepare_session_fork` 时,必须在 `try` 之前写 `prepared = None`,在 `finally` 中写 `await _abort_prepared_session(client, prepared, sys.exception())`。新增会话操作也要照这个写法。 | ||
| - 不要把 prepare 挪到 `try` 外,也不要删掉 `finally`。否则,取消或异常落在 prepare 与 commit 之间时,还处于 PREPARED 的租约会遗留给 `client.close()`。 | ||
| - `jiuwenswarm/channels/process_cli/app.py::_abort_prepared_session` 只在 `prepared.state is SessionProvisionState.PREPARED` 时调用 abort。abort 自身抛出的 `Exception` 只记日志;只有没有主错误时,才重新抛出 `CancelledError`。改动后,abort 失败不能盖掉主操作的错误,已 commit 的会话也不能再被 abort。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I36 — Team 流一轮结束后不会 EOF:_consume 靠终止事件返回,close 要先停掉 Team 流任务 | ||
|
|
||
| - Team 模式的 Runtime 流在一轮结束后会继续存活。`_consume` 靠 `_is_terminal_team_event` 返回,判定条件是 `chat.processing_status`,且 `is_processing is False`、`is_complete is True`。如果改了这个判定或事件字段,或删掉 694 行的检查,Team 命令会一直挂到超时,返回 124。 | ||
| - `InProcessRuntimeClient.close` 必须先调用 `cancel_all_team_stream_tasks`,再调用 `self._runtime.close()`。两步各自包在 try 里,都跑完后再抛出第一个错误。不要改成先 close,也不要遇到错误就提前返回。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I37 — LiveSessionInputController 的 pause/observe/render/resume 顺序,以及递归 _consume 的所有权 | ||
|
|
||
| - 有 `live_input` 时,根流事件和 `client.answer_interaction` 返回的事件走同一个顺序:遇到交互事件或 `is_complete` 事件,先 `await live_input.pause()`,然后 `observe`,最后经 `render_root_event` 渲染。不要直接调用 `renderer.render`。 | ||
| - `handle_interaction` 要先 `pause()` 再显示提示。有 `live_input` 时,答案必须用 `live_input.read_interaction_line()` 读取,不能用 `_interaction_answer`:后者直接调用 `sys.stdin.readline()`,会和 live reader 抢 stdin。继续流或发送答案之前,要先调用 `resume_after_event()`。 | ||
| - 中断恢复时递归调用 `_consume`,必须传 `owns_live_input=False`。只有根 `_consume` 在 `finally` 里对控制器调用 `close()`,否则恢复流一结束,控制器就会被提前关闭。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I39 — _write_worker_result 是 worker 与父 REPL 之间的接口,字段和写入时机都不能随意改 | ||
|
|
||
| - worker 和父 REPL 之间没有传输层,只通过 `_session_result_file`(内容只有 session_id)和 `_worker_result_file`(JSON,字段为 `operation`、`session_id`、`mode`、`work_mode`、`project_dir`)交换已提交的状态。改字段名、删字段、换编码,都会破坏父进程这一侧的解析。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In the pinned Useful? React with 👍 / 👎. |
||
| - chat 在 `create_or_resume_session` 之后、构建请求和开始流之前,就写结果文件,这样后续失败或超时时,父进程仍然能拿到会话 ID。不要把这一步挪到流结束之后。 | ||
| - delete 写入和返回的是当前会话 `args.session`,不是被删掉的 `target`。改成 `target` 会让父 REPL 切到已删除的会话上。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
|
|
||
| ## JIUWENSW-I41 — Duplex 控制输入:严格绑定当前运行,一次性令牌,出错时不回显输入 | ||
|
|
||
| - 每条控制消息的 `request_id` 必须等于 `writer.request_id`;如果带了 `session_id`,也必须等于 `writer.session_id`。回答只能按 `_register_interaction` 发出的 uuid 令牌路由,而不是 Runtime 的 request_id。令牌 `pop` 一次后就失效,重放会被拒绝。 | ||
| - 任何控制错误都经 `_terminate`,并使用固定文案,不回显请求内容。`_terminate` 以第一次失败为准,`stop_input` 之后调用会被忽略。不要在 message 里拼接用户输入。 | ||
| - 输入 EOF 时如果还有待答交互,必须以 `INPUT_CLOSED` 按取消处理(退出码 130)。`harness.activate_interaction` 以及没有定位符的交互会被拒绝,报 `INTERACTION_UNSUPPORTED`;待答交互数量上限是 `_MAX_PENDING`。 | ||
|
|
||
| <!-- kb:rule status=active since=init-f0a69728c96b --> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For changes such as
jiuwenswarm/common/config.py,_routes.yamlstill routes Direct to this_index.md; the returned quick map is markedok/read_required: falseand contains only the generic入口row plus this link, not any of JIUWENSW-I1/I4/I13/I17. The same problem affects the new extension and process-CLI rules, so the Direct contract stops navigation without presenting any of the 16 rules even though this commit enables the lifecycle. Point the owner routes at the new rule pages or embed their rule rows in the routed quick maps.AGENTS.md reference: knowledge/AGENTS.md:L16-L16
Useful? React with 👍 / 👎.