[Feature]: 工作区管理员(workspace administrator)—— 一个绑工作区、跨 turn 存活的常驻实体(讨论稿)
Mantenedores costumam responder em até 1 dia
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 5/5
- Tempo estimado
- Mais de uma semana
- Facilidade para iniciantes
- 8/100
- Tipo de issue
- Funcionalidade
- Clareza
- Precisa de esclarecimento
- Status de atividade
- Ativa
- Stack de tecnologia
- rust
- Domínio
- ai-infra-agents, backend
Direção de pesquisa
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.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Problem / opportunity
今天的单位是 session,而 session 是 turn 边界的:一个 turn 结束,队列前进到下一个;没有任何东西在 turn 之后存活。于是「同时照看几件工作」的上限是人的注意力,不是模型能力——人必须自己记住哪些会话在等、哪些漂移了、哪件事该接着做。
这与 #970「跟踪 core runtime 架构拆解闭环」的质量保护条款直接相关:涉及 session 生命周期 / scheduler 时序 / 权限与工具曝光 / remote workspace 的变更属于「必须先补可观测等价测试」且**「必须单独评审,不能伪装成结构重构」的范围。本提案不躲在结构重构后面——它明确新增一个 owner**。
Proposed behavior
新增一个工作区级常驻实体——工作区管理员(接口人):绑在工作区记录上、跨 turn 与重启存活、只在会话自己到达的对话边界上说话,并且只能通过一个刻意最窄的工具面向会话提交 turn。
- 它是一套封装(存储 + 纯策略 + 最小工具面 + 唤醒与投递机制),不是一个 agent mode。身份 = 存储的连续性,不是上下文的连续性;会话对它只是工作集。
- 工具面刻意最窄:
read/search、sessioncreate/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 supportsuperagent + subagentdelegation」,并提出团队协作 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 终态之后仍然存活、并且会被迟到读取的持久存储。
这个对象服务四个需求——这是它值得成为第一类对象的理由:
- 切分:一个会话里的多段对话需要能被分开。今天不能:
CronJobTarget::Session指向一个已存在会话,启动路径把定时载荷作为新的用户消息注入它(src/crates/assembly/core/src/service/cron/types.rs:115;service/cron/service.rs:794-812),于是一个会话会累积多个彼此无关的任务。会话与对话今天只是惯性上的 1:1。 - 管理员的触发器:它是管理员唯一合法的「先开口」时机(不变量 B)。
- 会合点:管理员随后给出的建议要落在一个可寻址的落点上——「这段对话的下一个边界」,而不是猜测或抢话。
- 去重键:反乒乓规则需要「同一段对话」这个身份。
反乒乓规则(已决定,作者)。 同一段对话里第二次早停、且自上次建议以来没有新 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、来源标记与审计。而「拉」是读。因此工作区记忆的默认形态是拉;推只以提示语义存在,不是记忆写。
- 三个必须补的补集:
- 常驻 inline 指针:纯拉依赖「模型自己意识到该问」。今天的对应物是
build_memory_read_path_reminder(read_path.rs:8),但它注入的根硬编码为全局根、没有 workspace 参数(read_path.rs:8-15)。管理员版本需要同形状的指针,指向工作区级存储。 - 非阻塞降级:不要把记忆查询接进权限管线。 权限等待没有超时,也没有「没有应答面」分支——
await_permission_execution_plan只对取消 token 等待(src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs:1376-1408)。查询必须走可降级路径:没答案 / 不可达 → 类型化降级(「工作区记忆不可用」),而不是挂起。 - 管理员自己的记忆必须在有检索的存储里,不能靠积累在它的上下文窗口里。
- 常驻 inline 指针:纯拉依赖「模型自己意识到该问」。今天的对应物是
- 在管理员工作区里,现有的用户全局记忆「隐藏但保留」,绝不删除,并有回到它的路径。 依据是仓库自己的升级义务(
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 时建立,而不是在第一次动作时。
- 两件必须新建的东西(它们今天不存在):
- 工作区移除时的级联语义——今天
remove_workspace只删记录,会话文件留在原处、只是相对注册表被孤儿化(manager.rs:1388-1407)。按仓库规则,级联不能是删除,只能是一个显式的「工作区不可达 / 管理员已孤儿化」状态加记录保留。 - 「工作区不可达」状态——
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),只能当作「把评估摊薄到多次到达上是一个值得研究的方向」这类方向性信号。本文引用它的数字时,只引用机制形状,不引用收益。
- 三个会改变本文结论的核验项(任一结果都可能推翻设计的一部分):
- 「远端工作区上的管理员」这一部署形态是否存在(今天只有「远端不取本地锁 / 记忆生成排除远端 / 远端注入被跳过」三条事实);
- 「会话级 goal 不再驱动」之后,
effective_subagent_timeout_seconds是否确实回到有界(即无界插话路径是否真的关掉了); - 边界记录、审计行与投递对象的保留策略——仓库禁止把删除当恢复手段,而这三样都会持久增长,所以保留策略必须有人拍板。
- Linguagem predominante
- Rust
- Estrelas
- 2.3k
- Forks
- 236
- Merge médio
- 13h 23min
- PRs com merge (30d)
- 335
Preparar o ambiente
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de GCWing/OpenBitFun
-
[Feature]: 模型服务表单缺少「图像生成」分类入口 —— 后端已支持 default_models.image_generationTalvez já em andamento @skyfireitdiy assumiu há 19 dias. Aberta
Dificuldade 2/5 Meio dia Facilidade para iniciantes 74/100
GCWing/OpenBitFun#3279 · 1 reação ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 48/100
GCWing/OpenBitFun#3733 ·
Mantenedores costumam responder em até 1 dia
-
[Bug] 切换会话/工作区时 manage_dialog_queue 返回 "Session is not loaded on the execution host"Talvez já em andamento @Raing5Days assumiu hoje. Aberta
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 25/100
GCWing/OpenBitFun#3729 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 5/5 Mais de uma semana Facilidade para iniciantes 35/100
GCWing/OpenBitFun#3728 ·
Mantenedores costumam responder em até 1 dia
-
[Feature]: 让magpie 可以识别Aberta
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 35/100
GCWing/OpenBitFun#3727 ·
Mantenedores costumam responder em até 1 dia
Todas as issues de GCWing/OpenBitFun
Issues semelhantes
-
state:needs triage
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 75/100
zed-industries/zed#66543 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
ruffle-rs/rust-flash-lso#142 ·
-
area: docs type: docs
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 62/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
light-curve/light-curve-python#870 ·
Mantenedores costumam responder em até 1 dia
-
bug file-compat priority: high
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 76/100
storytold/photocraft#11330 · 1 comentário ·
Mantenedores costumam responder em até 1 dia