[Feature]: 工作区管理员(workspace administrator)—— 一个绑工作区、跨 turn 存活的常驻实体(讨论稿)

Đang mở
#3,724 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
8/100
Loại issue
Tính năng
Độ rõ ràng
Cần làm rõ
Mức độ hoạt động
Sôi nổi
Công nghệ
rust
Lĩnh vực
ai-infra-agents, backend

Hướng nghiên cứu

This is a design discussion draft with no implementation, so there is no patch to start from. Read the session and turn lifecycle first: src/crates/execution/agent-runtime/src/scheduler.rs (TurnOutcome, queue_action) and src/crates/services/services-core/src/session/types.rs. Done would mean maintainers agree on the direction and shape, which requires a design decision, not a code change.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Problem / opportunity

今天的单位是 session,而 session 是 turn 边界的:一个 turn 结束,队列前进到下一个;没有任何东西在 turn 之后存活。于是「同时照看几件工作」的上限是人的注意力,不是模型能力——人必须自己记住哪些会话在等、哪些漂移了、哪件事该接着做。

这与 #970「跟踪 core runtime 架构拆解闭环」的质量保护条款直接相关:涉及 session 生命周期 / scheduler 时序 / 权限与工具曝光 / remote workspace 的变更属于「必须先补可观测等价测试」且**「必须单独评审,不能伪装成结构重构」的范围。本提案不躲在结构重构后面——它明确新增一个 owner**。

Proposed behavior

新增一个工作区级常驻实体——工作区管理员(接口人):绑在工作区记录上、跨 turn 与重启存活、只在会话自己到达的对话边界上说话,并且只能通过一个刻意最窄的工具面向会话提交 turn。

  • 它是一套封装(存储 + 纯策略 + 最小工具面 + 唤醒与投递机制),不是一个 agent mode。身份 = 存储的连续性,不是上下文的连续性;会话对它只是工作集。
  • 工具面刻意最窄:read / search、session create / freeze / prepare / call、工作区记忆查询、human interaction。没有工作区管理、文件写、shell、delete;只能建 session,永远不建 workspace。
  • 记忆是拉不是推;每会话理解是可重建的投影,绝不是权威副本。
  • 管理员工作区内,现有 session 级 ThreadGoal 语义不再驱动(记录保留、绝不删除);cron 迁移到工作区级、只由管理员控制,语义是「仿佛 router / 用户主动发了一条消息」。
Product surface

Agent runtime / core —— 且是 Cross-cutting:同时涉及 session 生命周期、scheduler 时序、权限与工具曝光、remote workspace。

Details, examples, or constraints

这是一份讨论稿。它没有实现任何东西,也没有为它跑过任何 build 或 test。 作者希望 maintainer 判断的是两件事:这个方向是否值得做,以及要做成什么形状。

作者的自我怀疑写在最前面:这份设计是从一次「要不要移植一个外部参考实现」的研究里长出来的,而那次研究自己的评审结论是朴素版本不可建(理由见下文 6.1)。把它写清楚,是希望 maintainer 先否掉方向,而不是先否掉细节。

下文第 5 节「证明负担」是最重要的一节(12 行仓库既成事实,逐行带锚点);第 7 节列出 4 项待 maintainer 拍的口径,外加一个更根本的问题:这个方向本身以这个形状值不值得做。

命名约束:本提案的实体不得叫 "Super Agent"——#27 已确立本仓库 "superagent" 指委派协调者,且 superAgent 键已承载「超级智能体」模型角色分配(src/web-ui/src/locales/zh-CN/settings.json:481-483)。工作名是工作区管理员(workspace administrator)。

以下是完整 SPEC。


这是一份讨论稿。 它没有实现任何东西,也没有为它跑过任何 build 或 test。作者希望 maintainer 判断的是两件事:这个方向是否值得做,以及要做成什么形状。文中「已决定(作者)」= 作者在自己的设计过程里已经收敛、并在本文里当作前提的口径;「待 maintainer 判断」= 作者认为只有 maintainer / 产品能拍的问题;建议 = 作者的提议,不是结论;(inferred) = 由「缺失检查」或两个锚点合并推出的结论,不是直接读到的代码事实。

先说作者的自我怀疑。 这份设计是从一次「要不要移植一个外部参考实现」的研究里长出来的,而那次研究自己的评审结论是:朴素版本不可建(原因见 6.1)。把它写清楚,是希望 maintainer 先否掉方向,而不是先否掉细节。第 5 节「证明负担」是本文最重要的一节。


摘要

  • 问题:今天的单位是 session,而 session 是 turn 边界的;没有任何东西在 turn 之后存活。于是「同时照看几件工作」的上限是人的注意力,不是模型能力。
  • 提案(一句话):在 OpenBitFun 里新增一个工作区级常驻实体——工作区管理员(接口人)。它绑在工作区记录上、跨 turn 与重启存活、只在会话自己到达的对话边界上说话,并且只能通过一个刻意最窄的工具面向会话提交 turn。
  • 新增的是什么:一个对话边界对象、一套持久投递对象、一份工作区级记忆、一个工作区分片的唤醒源,以及一个新的 owner(不是一个新模式)。
  • 已决定(作者):管理员是一套封装而不是一个 agent mode;三条不变量(4.2);工具面刻意最窄、只建 session 永不建 workspace(4.3);记忆是拉不是推(4.6)。
  • 待 maintainer 判断:4 项具体口径(第 7 节),外加一个更根本的问题——这个方向本身值不值得做。
  • 状态:未实现;未跑 build / 未跑 test。作者的自我评审结论仍是「朴素版本不可建」;本文的价值在于把「为什么难」写清楚。

与既有工作的关系(为什么这不是凭空提案)

  • #970「跟踪 core runtime 架构拆解闭环」(https://github.com/GCWing/OpenBitFun/issues/970)的质量保护条款要求:涉及「权限、工具曝光、session 生命周期、scheduler 时序、remote workspace」的变更「必须先补可观测等价测试」;并且「如果发现需要改变功能逻辑、权限边界、事件语义、工具 schema 或交付形态,必须单独评审,不能伪装成结构重构」。本提案正是 #970 要求的那个「单独评审」:它不躲在一次结构重构后面,而是明确新增一个 owner。
  • #27「Upgrade from Delegation Mode to Agent Team Collaboration」(GCWing 提出,已关闭;https://github.com/GCWing/OpenBitFun/issues/27)指出仓库「already support superagent + subagent delegation」,并提出团队协作 runtime、agent 间通信(direct message / broadcast)、轻量角色语义(coordinator / worker / reviewer)、共享任务状态(pending / in_progress / completed)、团队生命周期(start / stop / cleanup)。
    • 重叠:两者都要处理「多个工作单元之间的关系」。
    • 差别:#27 谈的是一个任务内部的协作;本提案谈的是一个跨 turn 存活、绑在 workspace 记录上的实体。它不引入团队角色、不引入 agent 间通信协议。
    • 作者自己核验到的现状(诚实记录):src/** 扫描显示运行时的共享任务状态与 agent-team owner 目前不存在(AgentTeam / agent_team / shared_task_state 零命中)。仓库里只有一条 UI 痕迹:AGENT_TEAM_TAG_COLORS(src/web-ui/src/app/scenes/agents/agentAppearance.ts:51)。UI 里的 taskBoard 指的是用户自己的待办(导航项 nav-todos-btn,见 tests/e2e/specs/l0-navigation.spec.ts:366-380),不是 agent 任务板。
  • 命名约束(重要)。 #27 已经确立:本仓库里 "superagent" 指的是委派协调者。独立地,src/web-ui/src/locales/*/settings.json 用 superAgent 这个键承载「超级智能体」——主 agent 的模型角色分配(src/web-ui/src/locales/zh-CN/settings.json:481-483)。因此本提案的实体不得叫 "Super Agent" / "super agent"。 它的工作名是工作区管理员(workspace administrator,接口人)。这不是一次改名,也不是要抢那个标签;请勿把它读成命名冲突。

1. 问题

今天的单位是一次会话(session),而会话是 turn 边界的:一个 turn 结束,队列前进到下一个 turn;没有任何东西在 turn 之后再存活下来。

  • turn 的终态是 per-turn 的:TurnOutcome / TurnOutcomeStatus 只有 Completed / Cancelled / Interrupted / Failed,queue_action() 把它们映射成「派发下一个 turn」(src/crates/execution/agent-runtime/src/scheduler.rs:777, 802, 872)。
  • 会话级的静息事实描述的是最后一个 turn:SessionLastTurn(src/crates/services/services-core/src/session/types.rs:1130)、last_finished_at(:150)、unread_completion(:310)、needs_user_attention(:323)。

后果不是「缺少一个功能」,而是上限的形状:要同时照看几件工作,人必须自己当那个跨会话的调度者——记住哪些会话在等、哪些漂移了、哪件事该接着做。这个上限是人的注意力,不是模型能力。

说明:下文的「参考设计」只用来提供动机(「一个常驻的评估/路由角色是这个问题的定义」),从不用来证明这件事有收益。参考设计自己的数字在本文里不作为收益证据(见第 8 节)。


2. 今天已经有什么

能力 owner 路径(file:line) 关键事实
Cron 调度器真的会起 turn src/crates/assembly/core/src/service/cron/service.rs:1300-1302 策略显式构造为 DialogSubmissionPolicy::new(DialogTriggerSource::ScheduledJob, DialogQueuePriority::Low);每作业在途守卫 job.state.active_turn_id.is_none()(:614);turn id 由触发时刻派生 cronjob_{job_id}_{pending_trigger_at_ms}(:622)并兼作已投递键(src/crates/execution/agent-runtime/src/scheduled_job.rs:153-168)。引擎在无 GUI 宿主上已启动:src/apps/server/src/bootstrap.rs:94-106
subagent = 持久、隔离、可续、可取消的子会话 src/crates/contracts/core-types/src/session.rs:5-10 SessionKind { Standard, Subagent, EphemeralChild },只有后两者对用户列表隐藏;create_hidden_agent_session* 设 SessionKind::Subagent(src/crates/assembly/core/src/agentic/coordination/coordinator.rs:3872);父子边图持久在用户级 coordination DB,且解析一律带 parent_session_id 谓词(src/crates/assembly/core/src/agentic/coordination/coordination_store.rs:195-215)——即 agent 只触达自己 spawn 的子会话
Ultimate 监督模式 src/crates/assembly/core/src/agentic/agents/definitions/modes/ultimate.rs:18-37 18 个默认工具(含 AgentSpawn / AgentSendInput / AgentControl / AgentList / AgentWait);delegation_policy_for_agent_turn("Ultimate") → DelegationPolicy::swarm_root()(coordinator.rs:1082);树预算 SWARM_MAX_NODES = 128(含根)、SWARM_MAX_DEPTH = 4(coordination_store.rs:14-15)
每会话 goal 状态 + 自动续跑 src/crates/contracts/runtime-ports/src/agent_api.rs:1101-1102, 1109 goal 存在会话自己的 custom metadata(THREAD_GOAL_METADATA_KEY = "thread_goal");自动续跑由 runtime 驱动,在没有新用户输入的情况下评估每个非内部 turn(src/crates/assembly/core/src/agentic/coordination/scheduler.rs:2896-2942),提交的提示是合成的("Continue working toward the active thread goal.",DialogTriggerSource::AgentSession,:2682-2696);上限 MAX_THREAD_GOAL_AUTO_CONTINUATIONS = 100;单作业宿主另有驱动 ThreadGoalRunTracker(src/crates/execution/agent-runtime/src/thread_goal.rs:796-799)
背景结果投递 + 未读/通知 src/crates/contracts/core-types/src/background_delivery.rs:22, 49 BackgroundReportDisposition { Pending, Quiet, Report, Attention, Unknown(String) };路由 resolve_background_delivery_action:Processing → 注入运行中的 turn,否则提交 follow-up(src/crates/execution/agent-runtime/src/scheduler.rs:700-714);会话侧静息信号 unread_completion(src/crates/services/services-core/src/session/types.rs:310)与 needs_user_attention(:323)
跨会话工具 src/crates/assembly/core/src/agentic/tools/implementations/session_control_tool.rs:410-431 SessionControl(create / cancel / delete / rename / list)、SessionMessage(session_message_tool.rs:708-735)、SessionHistory(session_history_tool.rs:142-144,is_readonly() = true)都在 claw 模式的默认工具面里(src/crates/assembly/core/src/agentic/agents/definitions/modes/claw.rs:46-49)。其中 SessionMessage 是唯一 agent 可达的「写进非子会话」路径
用户全局长期记忆 src/crates/services/services-core/src/memory_store.rs:12 一个用户级 SQLite,schema id openbitfun.memory.stage1.v1,表 stage1_outputs 以 thread_id(= session id)为主键;不同工作区的行合并进同一份 MEMORY.md(src/crates/assembly/core/src/agentic/memories/workspace.rs:346-406);注入点在 src/crates/assembly/core/src/agentic/agents/prompt_builder/prompt_builder_impl.rs:429-466,预算 MEMORY_SUMMARY_TOKEN_LIMIT = 2_500(src/crates/assembly/core/src/agentic/memories/read_path.rs:5-6)
DeepReview 的计划闸门 src/crates/execution/agent-runtime/src/deep_review/manifest.rs:586, 669 DeepReviewRunManifestGate::ensure_active 会拒绝「计划之外」的工作(未声明的子 agent、包外工作)。这是仓库里唯一一个「先声明一份有界的计划,再执行」的现成 owner,也是本提案最接近的形态先例

3. 今天缺什么

# 缺什么 确立这个缺失的证据
1 没有任何地方存在「一段对话已结束」的概念 TurnOutcome / TurnOutcomeStatus 只有 turn 终态(scheduler.rs:777, 802),queue_action() 只映射到「下一个 turn」(:872);SessionLastTurn(types.rs:1130)与 last_finished_at(:150)描述的都是最后一个 turn;SessionStatus::Completed 只为导入的外部会话设置(:329);最接近的 hook SessionEnd 的文档原文是「run when a session is deleted」(src/crates/assembly/core/src/native_hooks.rs:619)——是删除钩子,不是结束信号
2 没有外部事件入口 src/**/*.rs 里 webhook 零命中(作者自跑 grep);AgentSubmissionSource 的八个变体里没有任何外部事件来源(src/crates/contracts/runtime-ports/src/lib.rs:222-234)
3 没有工作区级记忆 存储是用户级的一个 SQLite(memory_store.rs:12);生成阶段枚举每个本地工作区并有意排除远端(src/crates/assembly/core/src/agentic/memories/session_roots.rs:15-20);不同工作区合并(workspace.rs:346-406);注入只在 remote_execution.is_none() 时发生,即远端执行下注入被跳过(prompt_builder_impl.rs:429-466);注入的根硬编码为全局根、没有 workspace 参数(read_path.rs:8-15)
4 没有跨会话仲裁者 工作区级锁保护的是部署所有权,不是会话工作(src/crates/services/services-core/src/runtime_ownership.rs:1-5, 83-86);SessionWriteLock 是 per-session(src/crates/services/services-core/src/session/write_lock.rs:8-12);每个对话队列、host 队列、会话变更锁都以 session id 为键。没有任何结构在两个同工作区会话之间做互斥、冲突检测或排队
5 没有会话冻结/挂起;列表投影里也没有状态 递归搜索 hibernat|suspend_session|freeze_session|SessionFrozen 找不到会话生命期构造(所有 freeze / Frozen 命中都无关);AgentSessionSummary 没有 status 字段(src/crates/contracts/runtime-ports/src/agent_api.rs:117-133),所以列表消费方看不到会话是否归档;而归档不是运行闸门——SessionStatus::Archived 只被两处消费:记忆 phase-1 门控(src/crates/assembly/core/src/agentic/memories/service.rs:687-689)与会话引用拒绝(src/crates/assembly/core/src/agentic/session/session_manager.rs:1400-1405)(inferred)
6 没有 per-turn 概况生产者 全仓只有两个 task-model 绑定:TaskModelsConfig { session_title, git_commit }(src/crates/contracts/config-contracts/src/types.rs:1066-1077);会话标题只对首轮生成一次(src/crates/assembly/core/src/agentic/coordination/coordinator.rs:7241-7279)。没有 fast 或其它低延迟绑定可用于每-turn 概况
7 agent 路径的投递没有收据/幂等/重试,排队也只在内存里 SessionMessage 提交时 turn_id: None(session_message_tool.rs:714),于是调度器自己生成 uuid,去重错误永远不会触发;工具只报 "success": true,发送方无法知道它造成了哪个 turn(:737-757);排队结构是内存里的 DashMap<String, VecDeque<...>>(src/crates/execution/agent-runtime/src/scheduler.rs:269-272),会话存储布局里没有队列文件(src/crates/services/services-core/src/session/layout.rs:26-135)(inferred:重启会丢掉一个已被接受的 agent turn)。对照:人类路径有幂等指纹、收据预算 RECEIPT_LIMIT = 16_384(src/crates/assembly/core/src/agentic/coordination/host_message_queue.rs:10-12)与队列 epoch
8 runtime 事件 JSONL 在 turn 终态时被丢弃 模块文档原文:日志只服务「in flight」的 turn,「When a Turn reaches a terminal state the Session record owns it and this log is dropped」(src/crates/assembly/core/src/service/session_projection_store.rs:7-11);内存 journal 有界且易失(MAX_PROJECTED_EVENTS_PER_SESSION = 2048、MAX_REPLAYABLE_TAIL_EVENTS = 1024,src/crates/execution/agent-runtime/src/session_event_journal.rs:14, 23)。因此迟到读者唯一能读到的持久东西是持久会话记录
9 工作区移除不级联;WorkspaceStatus::Error/Archived 从未被赋值 WorkspaceManager::remove_workspace 只删 map 条目并修剪 current/opened/recent 列表(src/crates/assembly/core/src/service/workspace/manager.rs:1388-1407),该路径里没有会话级联;枚举存在(src/crates/services/services-core/src/workspace_records.rs:22-30),但只有 Active / Inactive 被写入,显式策略是「保留并警告」:cleanup_invalid_workspaces 打日志 "Workspace is unavailable; preserving its record" 并返回 Ok(0)(manager.rs:1409-1422)
10 没有共享任务状态 / agent-team 运行时 owner src/** 里 AgentTeam / agent_team / shared_task_state 零命中;唯一痕迹是 UI 常量 AGENT_TEAM_TAG_COLORS(src/web-ui/src/app/scenes/agents/agentAppearance.ts:51);UI 的 taskBoard 指用户待办(tests/e2e/specs/l0-navigation.spec.ts:366-380,导航项 nav-todos-btn),不是 agent 任务板

4. 提案

4.1 它是什么:一套封装,不是一个 agent mode

已决定(作者)。 在本仓库里,一个 "agent" 的实质是一个模式(mode):agent_type 经 normalize_agent_type 归一(coordinator.rs:1498-1504),未知值的报错原文是 "Unknown session mode: {agent_type}";一个模式的内容就是一份 default_tools 清单(Ultimate 的 18 项,ultimate.rs:18-37)。模式本身也不持有状态——DelegationPolicy 的 nesting_depth 只是 per-turn 的字段(src/crates/contracts/runtime-ports/src/lib.rs:284-291)(inferred:模式不是状态载体)。

而管理员的状态必须跨会话、跨重启。一个只描述「单个会话怎么跑」的模式表达不了它。 因此管理员只能被表达为一套封装:一组存储 + 一组纯策略 + 一个最小工具面 + 一套唤醒与投递机制,跑在已有 runtime 的会话里。

组成 内容 今天有没有
身份与绑定 工作区记录 + 唯一性键(规范文件夹)+ 生命期绑定 只有 control_conversation.rs 那个应用自有隐藏工作区的先例;工作区级的没有
goal 状态 管理员自己持有 goal 语义 没有工作区级 goal 对象
工作区记忆 拉取式、有检索的存储 没有工作区级记忆 owner
每会话理解 投影:可重建,绝不是权威副本 形状有先例(index.json 是派生且可重建、每会话 metadata.json 才是权威,src/crates/services/services-core/src/session/metadata_store.rs:274-306);工作区级 owner 没有
对话边界对象 管理员唯一合法的「先开口」时机 全部不存在(见 §3 第 1 行)
唤醒与投递 去重键、顺序、重试/退避、drop 原因 agent 路径一项都没有;人类路径有全套(见 §3 第 7 行)
工具面 见 4.3 其中 freeze / prepare / 记忆查询 / 新工具都不存在

由此得到本节最重要的一条结构规则:身份 = 存储的连续性,不是上下文的连续性。 一个会话对管理员来说只是工作集(working set):它可以是任意的、可以被换掉、可以被重启丢掉,管理员不因此变成另一个实体。因此 goal 状态、工作区记忆、每会话理解三者都必须是存储里的东西。

4.2 三条不变量

A · 可逆性界定授权。 管理员自身只执行可逆操作:工具面里没有任何不可逆语义(删除、破坏性清理、重置)。不可逆语义只属于人。

修正(已决定,作者):可逆性不可通过委托传递。
原因不是「管理员会更谨慎」,而是可达性:管理员不可约的最小能力是向目标会话提交 turn,而目标会话持有完整工具面(写文件、执行 shell、删除)。因此管理员是一条写链的可达性根——「我的每个工具都是可逆的」在管理员身上是不充分的,它把不可逆语义借给了它可达的会话。
可证伪形式:断言「管理员可达的会话集合中,不存在任何未被显式护栏约束的不可逆路径」。今天这断言是假的:SessionMessage 可以指向任意已注册工作区的任意 session(session_message_tool.rs:596-606, 645-702)。直接后果:下述护栏不是可选项,而 4.1 的封装不是「可选加固」。

B · 只在会话自己到达的边界上说话。 管理员只在两类时机发出东西:某个会话的对话到达了它自己到达的边界(4.4),或召回命中(有会话来问、并且确实查到东西)。它不会在一个尚未到达边界的会话上先开口;除此之外保持沉默。

  • 沉默由工具面强制,不由提示纪律强制:管理员没有「主动输出 / 广播 / 通知」类工具,因此它在结构上无法发表内容。「不要乱说话」这类提示词约束不被当作实现手段。
  • 它也无法自己制造边界:边界只有两个发起者——人结束对话,或 agent 自己结束。
  • 代价明写在账上:早停今天没有在途信号,所以纠正只在对话结束时发生,而模型经常恰好在自己漂移时最自信。这是已接受的取舍,不是未决风险。

C · 一个事实一个 owner。 goal 状态、工作区记忆、投递对象各自恰好有一个 owner,不能各有两份。

  • 绑定规则来自仓库自己(docs/architecture/product-architecture.md:19):

    稳定归属:每项行为只有一个状态 owner 和主入口;适配、传输与重构不得建立平行业务路径。

  • 同一文件 :245 的模块级表述:「原子逻辑模块应满足三个条件:一个明确 owner、一个可独立测试的输入输出契约、一个变化原因」。
  • 为什么这条在这里是硬约束而不是口号:三条设计各自会制造第二个 owner,除非显式处理——goal 迁移会与已有的会话级自动续跑冲突(scheduler.rs:2896-2942);工作区记忆会与现有的用户全局记忆冲突(memory_store.rs:12);投递会与已有人类路径的收据/幂等机制冲突(host_message_queue.rs:10-12)。「每会话理解」是第四处:它必须是投影而不是权威副本。
4.3 工具面(窄,且有意如此)

已决定(作者)。

类别 工具 备注
读 read / search 只读,可枚举工作区内的会话
跨会话 session create / freeze / prepare / call create 只能建 session,永远不建 workspace;freeze / prepare 今天不存在(会话级 freeze / suspend / hibernate 全仓无构造)
记忆 工作区记忆查询服务 今天不存在(§3 第 3 行)
人 human interaction 阻塞式提问;需要「没有应答面」时的降级路径

明确没有:工作区管理、文件写、shell、delete。它只能建 session,永远不建 workspace。

4.4 对话边界对象(关键的新对象)

建议(也是第 7 节第 1 项)。 每个会话一份对话边界列表,每项:

字段 说明
边界落在哪个 turn 指向该会话里的具体 turn(今天可寻址的标识就是 turn id / turn index)
发起者 人 或 agent
可选原因指纹 agent 早停时可以抛出困惑、也可以不抛;抛出时它的指纹是一把更精确的去重键
时间 记录时点

它落在会话记录内部。 这不是随手选的落点:runtime 事件 JSONL 在 turn 终态被丢弃(session_projection_store.rs:7-11),内存 journal 有界且易失(session_event_journal.rs:14, 23),所以持久会话记录是唯一在 turn 终态之后仍然存活、并且会被迟到读取的持久存储。

这个对象服务四个需求——这是它值得成为第一类对象的理由:

  1. 切分:一个会话里的多段对话需要能被分开。今天不能:CronJobTarget::Session 指向一个已存在会话,启动路径把定时载荷作为新的用户消息注入它(src/crates/assembly/core/src/service/cron/types.rs:115;service/cron/service.rs:794-812),于是一个会话会累积多个彼此无关的任务。会话与对话今天只是惯性上的 1:1。
  2. 管理员的触发器:它是管理员唯一合法的「先开口」时机(不变量 B)。
  3. 会合点:管理员随后给出的建议要落在一个可寻址的落点上——「这段对话的下一个边界」,而不是猜测或抢话。
  4. 去重键:反乒乓规则需要「同一段对话」这个身份。

反乒乓规则(已决定,作者)。 同一段对话里第二次早停、且自上次建议以来没有新 turn,不再回答。这条规则不需要 payload 就能工作;agent 选择抛出的困惑是一把额外的键。必要性的背景:agent 到 agent 的回信路由完全没有深度、环、去重护栏——resolve_agent_session_reply_action 只受「取消回信抑制」与 Interrupted 跳过两个守卫,A→B→A 只被每会话队列深度限制,这条路径上没有任何冷却原语(src/crates/execution/agent-runtime/src/scheduler.rs:961-997)。所以去重键必须由这条规则自备。

4.5 触发与唤醒
唤醒源 语义 今天的状态
用户输入 直接对话 已有
cron(只由管理员控制) 语义是「仿佛 router / 用户主动发了一条消息」 引擎已有(cron/service.rs:1300-1302);缺「默认目标」的写入者、控制面与优先级档位。今天的策略是 ScheduledJob / Low,与「像用户消息」直接矛盾
对话结束 一段对话到达了它自己的边界 边界对象不存在(4.4)
未来外部入口(如 IM 群) 未受信事件作为工作来源 排在最后。仓库已有通道:Telegram / Feishu / Weixin bot(src/crates/services/services-integrations/src/remote_connect/bot/telegram.rs、.../feishu.rs、.../weixin.rs);但「未受信事件作为工作来源」与它的准入层不存在(src/**/*.rs 里 webhook 零命中)
4.6 记忆:拉,不是推

已决定(作者)。

  • 为什么只能是拉。 把工作区记忆推进每个会话,就是写进另一个会话的状态,不变量 A 的授权口子会被重新打开——必须走权限意图、grant、来源标记与审计。而「拉」是读。因此工作区记忆的默认形态是拉;推只以提示语义存在,不是记忆写。
  • 三个必须补的补集:
    1. 常驻 inline 指针:纯拉依赖「模型自己意识到该问」。今天的对应物是 build_memory_read_path_reminder(read_path.rs:8),但它注入的根硬编码为全局根、没有 workspace 参数(read_path.rs:8-15)。管理员版本需要同形状的指针,指向工作区级存储。
    2. 非阻塞降级:不要把记忆查询接进权限管线。 权限等待没有超时,也没有「没有应答面」分支——await_permission_execution_plan 只对取消 token 等待(src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs:1376-1408)。查询必须走可降级路径:没答案 / 不可达 → 类型化降级(「工作区记忆不可用」),而不是挂起。
    3. 管理员自己的记忆必须在有检索的存储里,不能靠积累在它的上下文窗口里。
  • 在管理员工作区里,现有的用户全局记忆「隐藏但保留」,绝不删除,并有回到它的路径。 依据是仓库自己的升级义务(AGENTS.md:245):

    Never delete or reset user data to recover from something you cannot parse. Keep the record, degrade the feature, and surface a clear state.

4.7 粗粒度交接

已决定(作者)。

  • 管理员不做细粒度委派。复杂工作交给一个 Ultimate 单元,由它自己的机制(AgentSpawn / AgentSendInput / AgentControl / AgentList / AgentWait,树预算 128 节点 / 4 层)做细分。细分这件事已经有 owner(ultimate.rs:18-37;coordination_store.rs:14-15),管理员不该重复实现它。
  • 机制限制(它决定「交给工作区」这句话到底读作什么):
    • 一个非用户发起的 Ultimate 会话今天只能经 cron 的 Workspace 目标创建(service/cron/service.rs:814-848);
    • 落点硬编码 SessionKind::Standard(src/crates/assembly/core/src/agentic/session/session_manager.rs:2699),因此对用户可见,在列表里叫 "Scheduled: <name>"(:819);
    • 并且必须有工作区记录;
    • SessionControl 的 create 只接受 Standard / Cowork / DeepResearch(src/crates/execution/agent-runtime/src/session_control.rs:29-34),所以管理员不能经它建一个 Ultimate。
  • 因此:「把粗粒度工作交给『工作区』」不是一个可实现的目标——会话才是可寻址目标。
4.8 生命周期与供给

已决定(作者)。

  • 生命期绑工作区记录,不是绑某个会话。这样它能扛过搬迁:工作区 id 在创建时一次性派生、之后冻结且不透明、不从 rootPath 重算(src/crates/services/services-core/src/workspace_persistence.rs:59-61)。
  • 新工作区自动;已有工作区显式 opt-in。 默认授权在 opt-in 时建立,而不是在第一次动作时。
  • 两件必须新建的东西(它们今天不存在):
    1. 工作区移除时的级联语义——今天 remove_workspace 只删记录,会话文件留在原处、只是相对注册表被孤儿化(manager.rs:1388-1407)。按仓库规则,级联不能是删除,只能是一个显式的「工作区不可达 / 管理员已孤儿化」状态加记录保留。
    2. 「工作区不可达」状态——WorkspaceStatus::Error / Archived 今天从未被赋值(workspace_records.rs:22-30;manager.rs:1409-1422)。不复用它们就要新建,因为那两个值今天不是可判定的运行事实。

5. 证明负担 / 已知硬约束

这一节是本文可信度所在,不要软化。 审稿人应当用下表逐条压这个设计;每一行都是仓库里的既成事实,不是风险描述。

# 硬约束 证据 设计必须怎么应对
1 workspace_id 由路径派生,出生后冻结 本地 id = sha256(lower(host) + "\n" + canonical_root)[..16](src/crates/services/services-core/src/workspace_identity.rs:298-304);且「A persisted ID is opaque and stable. Recomputing it from rootPath makes relocation (or an offline SSH profile) corrupt the whole catalog.」(workspace_persistence.rs:59-61) 生命期绑记录,这样搬迁后仍是同一实体
2 注册表没有路径唯一性约束 校验只检查 map key 等于 record.id 且非空(workspace_persistence.rs:52-67);导入按 id 插入、不查路径(src/crates/assembly/core/src/service/workspace/service.rs:1907-1924) 唯一性必须由管理员自己的存储强制,不能指望注册表
3 runtime 所有权键忽略 workspace_id 哈希的是 product_identity + canonical_path(src/crates/services/services-core/src/runtime_ownership.rs:31-54);锁文件是 <key>.lock 唯一性必须键在规范文件夹上,而生命期绑在记录上——这是两个键,不是同一个
4 同一文件夹上两条记录是可达的持久状态 三条路径可造出重复记录:带 id 的搬迁记录(workspace_persistence.rs:59-61)、同一台机器的两个 sshHost 别名、导入外来 catalog(service/workspace/service.rs:1907-1924);「正常本地打开总是收敛到一条记录」覆盖不到它们 必须显式决定消解规则(第 7 节第 3 项)。不能假设「一个文件夹只有一条记录」
5 远端工作区不取本地所有权锁 ensure_workspace_scope 对已验证的远端作用域直接返回 Ok(())(src/crates/assembly/core/src/runtime_ownership.rs:145-170) 远端工作区的管理员必须跑在远端宿主上;否则会同时踩到:没有本地锁的绑定、没有记忆生成(session_roots.rs:15-20)、没有记忆注入(prompt_builder_impl.rs:429-466)
6 跨会话守卫可被绕过 SessionMessage 唯一的 workspace 比较是拿调用方传入的 workspace 参数比(session_message_tool.rs:596-606);没有任何检查断言调用方自身的工作区等于目标工作区 (inferred);同一工具还会自己枚举目标工作区的会话列表,并可以在任何可命名的工作区里建会话(session_message_tool.rs:645-702) 「调用方说目标在同工作区」不能作为护栏;目标集必须在解析处强制
7 跨会话写不声明权限意图,也不留审计形状 SessionMessage 不覆写权限意图,落到通用 custom_tool + 工具名(src/crates/assembly/core/src/agentic/tools/framework.rs:126-133);策略层唯一的 workspace 级层是保留但未接线的 Project(src/crates/contracts/product-domains/src/tool_permissions.rs:344-349);没有任何东西记录「会话 A 写进了会话 B」(inferred) 专用意图(目标写进 resource)+ 在 opt-in 时建立的可撤销 grant + 持久审计行。三者都是必建项,不是加固项
8 插话只在 model-round 边界排空 引擎 round 循环顶部 take_pending → 持久化 → 发事件(src/crates/assembly/core/src/agentic/execution/execution_engine.rs:4631-4735) 一次注入的等待 = 本次模型请求的剩余部分 + 它的工具批,不是「一次工具调用」
9 只有 AgentWait 可 yield round_injection_yieldable() 默认 false(framework.rs:143-147),全树唯一覆写是 AgentWaitTool(agent_wait_tool.rs:147-149) 其余工具必须等它跑完,才轮到下一个 round 边界
10 最坏情况延迟无界 Task(AgentExecutionTool)自报 manages_own_execution_timeout() 为真(src/crates/assembly/core/src/agentic/tools/implementations/task/mod.rs:159-161,测试断言 tool_pipeline.rs:6118-6122),于是被跳过管线超时(tool_pipeline.rs:2609-2616);而管线超时默认本身就无上限:fn default_tool_execution_timeout() -> Option<u64> { None },「None means wait indefinitely」(src/crates/contracts/config-contracts/src/types.rs:1168-1170);父会话有活跃 goal 时子 agent 期限被移除(src/crates/execution/agent-runtime/src/thread_goal.rs:32-42) 「呼唤」必须给定确定上界,或者让管理员可达的会话不进入无界情形。二者都是行为边界变更,需要等价性验证
11 agent 到 agent 的回信路由没有深度 / 环 / 去重护栏 resolve_agent_session_reply_action(src/crates/execution/agent-runtime/src/scheduler.rs:961-997);唯一的守卫是取消回信抑制与 Interrupted 跳过;没有冷却原语 反乒乓去重键必须自备,且必须能在重启后仍然成立
12 仓库禁止把删除 / 重置当恢复手段 AGENTS.md:245:「Never delete or reset user data to recover from something you cannot parse. Keep the record, degrade the feature, and surface a clear state.」 所有「销毁」只能是标记 + 保留记录;级联不能是删除

新增 owner 的额外门槛。 若这些对象要进入稳定面,还要过 docs/architecture/product-architecture.md:451-470 的公开接口进入条件:有当前消费方、说明版本影响与删除条件。另外 AGENTS.md:290 规定:「Moving runtime ownership requires a reviewed port/provider design, old-path compatibility, behavior equivalence tests, and explicit confirmation when a behavior boundary could change.」


6. 非目标

  • 不移植参考实现(见 6.1)。
  • 不做第二个 loop owner。 生产循环今天仍由 assembly/core 的 ConversationCoordinator → Scheduler → ExecutionEngine → RoundExecutor → ToolPipeline 持有(docs/architecture/product-architecture.md:247),本提案不新增循环,只新增 owner。
  • 不做细粒度委派。 细分已有 owner(Ultimate + swarm 树预算)。
  • 永不删除用户数据。 包括旧 goal 记录、旧 cron 条目、旧记忆。
  • 不重命名、也不重用现有的 superAgent 模型角色标签(src/web-ui/src/locales/zh-CN/settings.json:481-483)。
6.1 为什么不移植参考实现
  • 它不是一篇论文——作者没有找到 arXiv 或 DOI 记录;它是一篇设计文章加一个 MIT 研究原型。
  • 那个原型是为另一个 harness 打的 bundle(deepseek-ai/dsh-superagent-prototype),不是可搬进本仓库的模块:其中约三分之二是 harness 胶水、面板与 per-deployment 的 source adapter。
  • 它以操作者的 OS 用户身份、无审批地跑 worker,其自身文档承认那些护栏是「defaults, not a security boundary」;而 OpenBitFun 的对应路径是 fail-closed 的,它的尖角是 await_permission_execution_plan 没有「没有应答面」分支(tool_pipeline.rs:1376-1408)。采纳它改变的是安全属性,不是一个功能。
  • 它唯一的量化结果是一次只测调度的合成实验:编码工作被替换成 stub,n = 5,没有 shipped artifact。

因此本文只借用问题定义与机制形状,不引用它的任何数字作为收益证据。


7. 待 maintainer 判断

# 待定项 缺了会怎样
1 「一段对话的边界」的表示法(最小形状见 4.4:每会话一份列表,每项 { 边界落在哪个 turn, 发起者(人|agent), 可选原因指纹, 时间 }) 未定则第 2、3 项都悬空:没有边界对象,就没有管理员的合法触发点与会合点,也无从谈早停的去重与投递的排序
2 turn 优先级档位(建议:cron→管理员 Normal、管理员→worker Low,并保留「turn id 由触发时刻派生」这一确定性属性) 不定则 scheduled_job_policy() 的 ScheduledJob / Low(service/cron/service.rs:1300-1302)与「cron 语义像用户消息」继续互相矛盾;管理员投递与 cron 工作会在同优先级下互相 FIFO,或反过来压过用户自己的 turn(High = 2,agent_api.rs:833-840)
3 同一文件夹上的两条工作区记录(建议:唯一性键 = 规范文件夹,生命期绑定 = 记录) 不定则「一个工作区一个管理员」在同一文件夹上无法排他;两条记录先后 opt-in 时如何消解没有定义。注意唯一性无法从运行时锁获得
4 旧 cron 是否提供一键迁移动作(建议:提供,按作业或按工作区) 不提供则旧作业只是被标 INVALID 后静默停跑——停掉一个用户依赖的作业即便有标注也是回归;并且「目标会话已经不存在」的旧作业只有这个动作能处理

在以上四项之下,还有一个更根本的问题:

这个方向本身值不值得做?以这个形状?

也就是说:一个绑工作区、跨 turn 存活、只在会话自己到达的边界上说话、只能建 session 不能建 workspace、可被人撤销授权的实体,是不是 OpenBitFun 想要的形状?如果方向对但形状不对,请指出哪一条不变量(4.2)、哪一条工具面约束(4.3)或哪一条硬约束(第 5 节)应该先改。


8. 证据与可复核性

  • 每条 OpenBitFun 断言都锚到仓库 file:line,或(对外部原型)一个 URL。文中标 (inferred) 的结论由「缺失检查」或两个锚点合并得出,不是直接读到的代码事实,依赖前应先实测。
  • 本文没有跑任何 build 或 test,并且不声称任何行为已被验证。任何进入实现的阶段都应先补可观测等价测试(这正是 #970 的要求)。
  • 参考设计自己引用的数字在本文中不作为收益证据。 它唯一的量化结果来自一次把编码工作替换成 stub 的调度实验(n = 5、无方差、无 shipped artifact),只能当作「把评估摊薄到多次到达上是一个值得研究的方向」这类方向性信号。本文引用它的数字时,只引用机制形状,不引用收益。
  • 三个会改变本文结论的核验项(任一结果都可能推翻设计的一部分):
    1. 「远端工作区上的管理员」这一部署形态是否存在(今天只有「远端不取本地锁 / 记忆生成排除远端 / 远端注入被跳过」三条事实);
    2. 「会话级 goal 不再驱动」之后,effective_subagent_timeout_seconds 是否确实回到有界(即无界插话路径是否真的关掉了);
    3. 边界记录、审计行与投递对象的保留策略——仓库禁止把删除当恢复手段,而这三样都会持久增长,所以保留策略必须有人拍板。
Ngôn ngữ chính
Rust
Star
2.3k
Fork
236
Merge trung bình
13 giờ 23 phút
Pull request đã merge (30 ngày)
333

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của GCWing/OpenBitFun

Tất cả issue của GCWing/OpenBitFun

Issue tương tự

Thêm issue về Rust

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.