diff --git a/GITEA-DESIGN.md b/GITEA-DESIGN.md index b2d9434..3afd68f 100644 --- a/GITEA-DESIGN.md +++ b/GITEA-DESIGN.md @@ -98,8 +98,8 @@ worker 环境里永远没有 token 变量。 cursor: family('gitea-cursor:'), stream: family('gitea-stream:') }`(tests/store.spec.ts 只在那里要求 key 字面量)。 6. 构建接线:tsdown entry `'gitea-watch': 'src/sources/gitea.ts'`;package.json exports `"./gitea-watch"`; cordis.patch.yml bundle 插入 `dsh-superagent-gitea` 名 `dsh-superagent/gitea-watch` **disabled: true** - (router profile 启用它;web profile 必须不启)。007-architecture.md 模块+测试表(tests/docs.spec.ts - 强制完整性)+ 001-domain "What is reusable" 行。测试:`tests/gitea.spec.ts`(fetch 用注入的 + (router profile 启用它;web profile 必须不启)。007-架构.md 模块+测试表(tests/docs.spec.ts + 强制完整性)+ 001-领域模型 "哪些是可复用的" 行。测试:`tests/gitea.spec.ts`(fetch 用注入的 fetchImpl / vi.stubGlobal mock),模式同 tests/sources.spec.ts。 ## MVP 裁剪线(M2 在后):Gitea PR 观察 + threadClosed/bindThread + 自动合并;行内评审评论; diff --git a/INSTALL.md b/INSTALL.md index 7c300fa..38526e5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,6 +1,6 @@ # 安装(本 fork,中文速查) -把本仓库作为 DSH 插件栈装上手的完整步骤。英文版见 [docs/006-deployment.md](docs/006-deployment.md)(Install / Machine configuration / Start and stop)。 +把本仓库作为 DSH 插件栈装上手的完整步骤。参考 [docs/006-部署.md](docs/006-部署.md)(安装 / 机器配置 / 启动与停止)。 ## 前置条件 diff --git a/README.md b/README.md index 1155454..8759467 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ dsh-superagent 是为一个 GitHub 仓库服务的主动式编码 agent,打包成 [DeepSeek Harness](https://www.deepseek.com/en/harness/) 的 bundle。它监听 GitHub,可选地也监听飞书。当允许名单里的人向 agent 要活儿时,它开一个工单,在自己独立的 agent 会话和目录里干活,并在原对话里答复——比如提一个 pull request。Web 面板显示哪些事需要人。 -它也是一种与 agent 协作的方式:**尽量少的占用人的注意力,尽量多的交付价值。** 一个能替整个团队行动的 agent 也能浪费整个团队的注意力,所以它只在被要求、或被团队自己写下的规则允许时才行动,只在有人必须行动、或能用上结果时才说话。[docs/000-philosophy.md](docs/000-philosophy.md) 解释这个原则以及它对操作者的要求。 +它也是一种与 agent 协作的方式:**尽量少的占用人的注意力,尽量多的交付价值。** 一个能替整个团队行动的 agent 也能浪费整个团队的注意力,所以它只在被要求、或被团队自己写下的规则允许时才行动,只在有人必须行动、或能用上结果时才说话。[docs/000-理念.md](docs/000-理念.md) 解释这个原则以及它对操作者的要求。 ## 安装 @@ -16,21 +16,21 @@ dsh-superagent 是为一个 GitHub 仓库服务的主动式编码 agent,打包 它会一次只问一个短问题,展示一次完整计划,然后安装并配置好一切。[SETUP.md](SETUP.md) 就是它遵循的协议。 -本 fork 另有一篇动手向的中文速查,覆盖插件安装、Gitea Source 与模型 provider:[INSTALL.md](INSTALL.md)。英文参考仍是 [docs/006-deployment.md](docs/006-deployment.md)。 +本 fork 另有一篇动手向的中文速查,覆盖插件安装、Gitea Source 与模型 provider:[INSTALL.md](INSTALL.md)。完整参考是 [docs/006-部署.md](docs/006-部署.md)。 ## 文档 按编号顺序读;每篇只建立在前几篇之上。 -- [000 理念](docs/000-philosophy.md) -- [001 领域模型](docs/001-domain.md) -- [002 授权](docs/002-authorization.md) -- [003 执行](docs/003-execution.md) -- [004 提示词](docs/004-prompts.md) -- [005 面板](docs/005-panel.md) -- [006 部署](docs/006-deployment.md):手工安装、配置、升级与排障 -- [007 架构](docs/007-architecture.md) -- [008 手册](docs/008-cookbook.md):加一个 Source +- [000 理念](docs/000-理念.md) +- [001 领域模型](docs/001-领域模型.md) +- [002 授权](docs/002-授权.md) +- [003 执行](docs/003-执行.md) +- [004 提示词](docs/004-提示词.md) +- [005 面板](docs/005-面板.md) +- [006 部署](docs/006-部署.md):手工安装、配置、升级与排障 +- [007 架构](docs/007-架构.md) +- [008 手册](docs/008-手册.md):加一个 Source ## 开发 @@ -46,7 +46,7 @@ dsh-superagent 是为一个 GitHub 仓库服务的主动式编码 agent,打包 ## 安装本 fork 插件栈(速览) -人话版安装步骤;英文版见 [docs/006-deployment.md](docs/006-deployment.md) 的 Install / Machine configuration。前提是已能使用 `dsh` CLI(没有就先 `npm install -g @deepseek-ai/dsh`,版本需满足 `package.json` 的 `engines.dsh`)。 +人话版安装步骤;参考 [docs/006-部署.md](docs/006-部署.md) 的安装 / 机器配置 节。前提是已能使用 `dsh` CLI(没有就先 `npm install -g @deepseek-ai/dsh`,版本需满足 `package.json` 的 `engines.dsh`)。 ```sh git clone <本仓库> dsh-superagent && cd dsh-superagent diff --git a/SETUP.md b/SETUP.md index d1050c2..76c288c 100644 --- a/SETUP.md +++ b/SETUP.md @@ -4,9 +4,9 @@ ## 部署流程怎么走 -部署遵循 [docs/000-philosophy.md](docs/000-philosophy.md):尽量少的占用操作者的注意力。 +部署遵循 [docs/000-理念.md](docs/000-理念.md):尽量少的占用操作者的注意力。 -- **问第一个问题之前**,只读这个文件和 `docs/000-philosophy.md`,并把下面的探测跑一遍。在所有答案收齐之前,不要读其它任何东西。 +- **问第一个问题之前**,只读这个文件和 `docs/000-理念.md`,并把下面的探测跑一遍。在所有答案收齐之前,不要读其它任何东西。 - **一条消息只问一个问题**,只问一件事。等回答到了再问下一个。探测已经能回答的问题就跳过。 - **每条消息不超过 300 词**,除非操作者要求更多。每个问题给出一个操作者可以一句话同意的推荐答案,并一句话说明它为什么重要。 - **问的时候不要动手**。收齐所有答案后,发一条包含完整计划的消息,等操作者确认了才执行。 @@ -26,11 +26,11 @@ grep -l -E 'id: dsh-superagent\\b' "${DSH_HOME:-$HOME/.dsh}"/profiles/*/cordis.p - **前置条件**:Node.js 22.19+ 或 24+、pnpm、git 2.32+、tmux、Python 3.8+、GitHub CLI 2.48+,以及 DSH 版本在 `package.json` 的 `engines.dsh` 范围内。缺哪个就把它变成计划里的一项,而不是一个问题。DSH 还需要一个模型 provider;如果操作者从没配过模型的 DSH,配置它属于操作者的步骤。 - **操作者登录名**:`gh api user` 的结果是操作者自己的 GitHub 登录名,作为第 5 问的推荐值。 -- **已有 profile**:某个 profile 里已有 `dsh-superagent` 条目,说明是早前的部署:问是继续它、保留它的配置,并去读 `docs/006-deployment.md` 的升级一节。 +- **已有 profile**:某个 profile 里已有 `dsh-superagent` 条目,说明是早前的部署:问是继续它、保留它的配置,并去读 `docs/006-部署.md` 的升级一节。 ## 2. 提问 -按这个顺序问,一条消息一个,跟着 [docs/001-domain.md](docs/001-domain.md) 的工作流走:先问 Trigger 从哪来,再问谁可以在那里提工作,最后问 Executor 拿到工单后要做什么。推荐值放在方括号里。 +按这个顺序问,一条消息一个,跟着 [docs/001-领域模型.md](docs/001-领域模型.md) 的工作流走:先问 Trigger 从哪来,再问谁可以在那里提工作,最后问 Executor 拿到工单后要做什么。推荐值放在方括号里。 **来源:请求从哪里来。** @@ -64,18 +64,18 @@ grep -l -E 'id: dsh-superagent\\b' "${DSH_HOME:-$HOME/.dsh}"/profiles/*/cordis.p ## 4. 执行 -1. **操作者步骤先行。** 读 `docs/006-deployment.md` 的 Accounts 一节,自己写好 CLI wrapper,然后发一条消息列出只有操作者能做的事:建 agent 账号;开了自动合并的话,用有权开它的账号登录一个 CLI;以 agent 账号接受仓库邀请;每个 wrapper 跑 `auth login`;用飞书的话,创建并发布 bot 应用,带着它的密钥跑 `config init`。等操作者说做完了,再用 ` api user --jq .login` 逐个核对登录。 -2. **安装并启动。** 按 `docs/006-deployment.md` 的 Install、Machine configuration、Start and stop 三节做。跳过本仓库的 `git clone`:你就在它的 checkout 里。第 7 问要新 clone 的,去 clone 第 2 问的那个仓库。已有 profile 时,跳过 `dsh superagent --from-default-profile` 命令——它会替换 profile 并删掉它的 `cordis.patch.yml`;改为编辑那个文件,不要替换。 -3. **保存设置。** 按 `docs/006-deployment.md` 的 Management API 保存,顺序:`github` Source、(如用)`feishu` Source、`policy` 块。然后按 Delivery requirements 存提示词(第 8、9 问的答案)。返回 `restarting: true` 的保存会重启 dsh-superagent:等 `scripts/dsh-superagent status` 不再以 1 退出再发下一个请求,并从 `GET settings` 重新读 revision。 -4. **失败。** 被拒的保存会在响应体里点名 setup 问题。去 `docs/006-deployment.md` 的 Troubleshooting 表里找它,修好再继续。只有你自己修不了的才问操作者。 +1. **操作者步骤先行。** 读 `docs/006-部署.md` 的 Accounts 一节,自己写好 CLI wrapper,然后发一条消息列出只有操作者能做的事:建 agent 账号;开了自动合并的话,用有权开它的账号登录一个 CLI;以 agent 账号接受仓库邀请;每个 wrapper 跑 `auth login`;用飞书的话,创建并发布 bot 应用,带着它的密钥跑 `config init`。等操作者说做完了,再用 ` api user --jq .login` 逐个核对登录。 +2. **安装并启动。** 按 `docs/006-部署.md` 的 Install、Machine configuration、Start and stop 三节做。跳过本仓库的 `git clone`:你就在它的 checkout 里。第 7 问要新 clone 的,去 clone 第 2 问的那个仓库。已有 profile 时,跳过 `dsh superagent --from-default-profile` 命令——它会替换 profile 并删掉它的 `cordis.patch.yml`;改为编辑那个文件,不要替换。 +3. **保存设置。** 按 `docs/006-部署.md` 的 Management API 保存,顺序:`github` Source、(如用)`feishu` Source、`policy` 块。然后按 Delivery requirements 存提示词(第 8、9 问的答案)。返回 `restarting: true` 的保存会重启 dsh-superagent:等 `scripts/dsh-superagent status` 不再以 1 退出再发下一个请求,并从 `GET settings` 重新读 revision。 +4. **失败。** 被拒的保存会在响应体里点名 setup 问题。去 `docs/006-部署.md` 的 Troubleshooting 表里找它,修好再继续。只有你自己修不了的才问操作者。 ## 5. 验证与交接 -`scripts/dsh-superagent status` 以 0 退出且没有 setup 问题。如果你运行在操作者自己的 DSH profile 里,用你的日程工具建 `docs/006-deployment.md` Hourly check 一节里的那个每小时检查;否则把那条 prompt 交给操作者,让他在自己的地方排期。然后发一条消息,不超过 300 词: +`scripts/dsh-superagent status` 以 0 退出且没有 setup 问题。如果你运行在操作者自己的 DSH profile 里,用你的日程工具建 `docs/006-部署.md` Hourly check 一节里的那个每小时检查;否则把那条 prompt 交给操作者,让他在自己的地方排期。然后发一条消息,不超过 300 词: - 面板 URL,以及侧边栏里 **Superagent** 条目是看哪里。 - 怎么试:用允许名单里的账号,在仓库的某个 issue 下评论 `@ <请求>`。大约一分钟内,overview 上会出现工单。 -- 给操作者的三条规则,来自 `docs/000-philosophy.md` 的 Responsibility 一节:从小开始、往少噪音调、为 agent 说的话负责。 +- 给操作者的三条规则,来自 `docs/000-理念.md` 的 Responsibility 一节:从小开始、往少噪音调、为 agent 说的话负责。 - 想读更多:`docs/` 里编号的文档,按顺序读。 ## Gitea Source 配置要点(本 fork) diff --git a/cordis.patch.yml b/cordis.patch.yml index 709b2f0..d171050 100644 --- a/cordis.patch.yml +++ b/cordis.patch.yml @@ -1,5 +1,5 @@ # dsh-superagent bundle layer: inserts the plugins without configuration, and the agent presets its Sessions run under. The -# profile supplies the `dsh-superagent` entry's stateDir (see docs/006-deployment.md and examples/profile.cordis.patch.yml); the plugin +# profile supplies the `dsh-superagent` entry's stateDir (see docs/006-部署.md and examples/profile.cordis.patch.yml); the plugin # refuses to start without stateDir. - insert: - id: dsh-superagent diff --git a/docs/000-philosophy.md b/docs/000-philosophy.md deleted file mode 100644 index 8422db9..0000000 --- a/docs/000-philosophy.md +++ /dev/null @@ -1,33 +0,0 @@ -# 000 Philosophy - -dsh-superagent is a framework, and also a way of working with an agent. Its core principle: - -**Take as little human attention as possible, and give as much value as possible.** - -Every part of the project is judged by the attention it costs. A message, a notification, a question, a setting, a panel element and a paragraph of documentation each cost someone's attention, and each has to give more value than that, or it is removed. - -## What follows for the agent - -- **It acts only when asked, or when a written rule allows it.** A request counts only when it addresses the agent directly and comes from a person the team allowed: one on an allowed list, or anyone in a conversation the team opened to requests. Work nobody asked for needs a rule the team wrote, and starts in a mode that only records what the agent would have done. -- **It speaks only when a person must act or can use a result.** It confirms a request once and names what it will do. It stays quiet while it works. It writes again when a result is ready, when it needs a decision, or when it cannot finish. Messages are short and go to the conversation where the request was made. -- **It asks one concrete question at a time,** in a structured form, so the question reaches the person instead of staying inside a long report. -- **It stops by impact, not by effort.** A local change goes as far as a reviewable result. A change that needs a design decision stops with a clear question. -- **It reports honestly.** A draft is marked unfinished, a failure is reported as a failure, and a limit is never bypassed to avoid asking. - -## What follows for the design - -- **Few concepts.** The design has only its domain concepts, such as a Source, and their concrete implementations, such as GitHub and Feishu. A business need becomes an attribute of one of them, or a prompt the agent follows. The design does not add a concept for it. -- **Few settings.** A default replaces a setting when one value suits almost every team. Behaviour that a team describes in words is prompt text, not a setting. -- **A panel that shows only what needs a person.** It lists first what waits for a person. A healthy state takes no space. -- **Documents read in order.** Each numbered document builds only on the ones before it, so a reader never needs a concept that has not been introduced yet. - -## Responsibility - -The agent comments, opens pull requests and messages people under accounts your team sees every day. Every message it sends costs your colleagues' attention, and you are responsible for it. The more the agent can do, the more care its operator owes the team: - -- **Start narrow.** Begin with a short allowed list and with proactive work only recorded. Widen both once what the agent would have done looks right. -- **Tune for less noise.** When the agent is noisy, make its prompts shorter and stricter, or pause it. Do not add more messages to explain the noise. -- **Do not use it to produce volume.** Mass pull requests, broadcast messages and busywork cost the team more than they give. -- **Own what it says.** Read what the agent posts in your name, and correct it in the prompts when it is wrong. - -The goal is the right help at the right time, and nothing at any other time. diff --git a/docs/000-理念.md b/docs/000-理念.md new file mode 100644 index 0000000..846a18f --- /dev/null +++ b/docs/000-理念.md @@ -0,0 +1,33 @@ +# 000 理念 + +dsh-superagent 是一个框架,也是一种与 agent 协作的方式。它的核心原则: + +**尽量少地占用人的注意力,尽量多地交付价值。** + +项目的每一处都按它花费的注意力来评判。一条消息、一次通知、一个问题、一个设置、一个面板元素、一段文档,各自都要消耗某个人的注意力,各自都必须给出比这更多的价值,否则就删掉。 + +## agent 要遵守的 + +- **它只在被要求、或被写下的规则允许时才行动。** 只有直接面向 agent、且来自团队允许的人的请求才算数:允许名单上的人,或团队开放给请求的会话里的任何人。没人要求的工作需要团队写下的规则,并且以只记录 agent 本会做什么的模式开始。 +- **它只在有人必须行动、或用得上结果时才说话。** 它确认一次请求,并说明它将做什么。干活期间保持安静。有结果、需要决定、或无法完成时再写。消息简短,发到提出请求的那个对话。 +- **它一次只问一个具体问题,** 用结构化表单,让问题到达人那里,而不是埋在一长段报告里。 +- **它按影响停下,不按费力程度。** 本地改动推进到可评审的结果为止。需要设计决定的改动停在一个清楚的问题上。 +- **它如实汇报。** 草案标未完成,失败按失败报告,绝不为了少问一句而绕过限制。 + +## 设计要遵守的 + +- **概念少。** 设计里只有它的领域概念(例如 Source)和这些概念的具体实现(例如 GitHub 和 Feishu)。一个业务需求要么变成其中某个概念的属性,要么变成 agent 遵循的一段提示词。设计不为此新增概念。 +- **设置少。** 当一个取值几乎适合所有团队时,用默认值代替设置。团队用语言描述的行为是提示词文本,不是设置。 +- **面板只显示需要人的东西。** 它先列出等待人的事项。健康的状态不占空间。 +- **文档按顺序读。** 每篇编号文档只建立在前几篇之上,读者永远不会遇到一个还没引入的概念。 + +## 责任 + +agent 以你们团队天天见到的账号发评论、开 pull request、给人发消息。它发出的每条消息都消耗你同事的注意力,你要为此负责。agent 能做得越多,操作者对团队就越要多留心: + +- **从窄处开始。** 先用一份短的允许名单,主动工作只做记录。等 agent 本会做的事看起来对了,再放宽这两样。 +- **往噪声更少的方向调。** agent 太吵时,把它的提示词改短、改严,或者暂停它。不要为了解释噪声而多发消息。 +- **不要拿它堆产量。** 大批量 pull request、群发消息、刷出来的活儿,团队亏的比赚的多。 +- **替它说的话负责。** 读 agent 以你的名义发的内容,发现不对就在提示词里纠正。 + +目标是恰好的帮助出现在恰好的时间,其余时间什么都不出现。 diff --git a/docs/001-domain.md b/docs/001-domain.md deleted file mode 100644 index fd53001..0000000 --- a/docs/001-domain.md +++ /dev/null @@ -1,112 +0,0 @@ -# 001 Domain model - -This document names the concepts of dsh-superagent and the rules each one keeps. The code, the tools the agents call, the panel and the other documents use these terms and no others. Following [000 Philosophy](000-philosophy.md), the model holds only concepts the work needs, and a business need becomes an attribute of one of them. - -## Flow - -Work moves from Trigger to Eval to execution. A Source produces Triggers. A Trigger fans out as context to the Tickets subscribed to its Threads. A person's message that reached a Ticket this way stays with that Ticket; every other Trigger enters Eval, which decides whether it asks for new work. When Eval accepts, it creates a Ticket with a Mandate. An Assignment hands the Ticket to an Executor, and results go back to the Source through a Channel. - -```mermaid -flowchart LR - S[Source] -->|Trigger| T[Thread] - T -->|batch| E[Eval] - T -->|Subscription fan-out| K[Ticket] - E -->|accept with Mandate / stop| K - E -->|ignore / unavailable| X[recorded] - P[Policy change] -->|notice| K - K -->|Assignment| W[Executor] - K -->|Channel| S -``` - -## Concepts - -Source, Thread, Eval, Mandate and Subscription form the core domain; the rest supports them. - -| Concept | Definition | Rules it keeps | Why it exists | -| --- | --- | --- | --- | -| Source | A stream of events. A Source kind is a reusable implementation; a Source instance is one configuration of a kind: whether it runs and what it listens to | Holds its read position and its own bot identity. Defines what counts as an event, including conditions that only hold over time. Drops the bot's own messages and Triggers outside what it listens to before they enter the domain. Translates each Trigger into the Threads it belongs to and the Channel addresses to reply on | A restart must not replay events | -| Trigger | One immutable event with the Source's own metadata, such as its actor | Belongs to one or more Threads. Accepting a Trigger twice creates one Ticket | The unit of audit and of idempotence | -| Thread | Triggers in time order; the Source defines which Triggers form one Thread | At most one Eval runs on it at a time. It may have several Tickets. It holds its Subscriptions, the running Eval and its Decision history | Gives Eval a consistent view and its context | -| Correlation | The relation between Threads that one Trigger belongs to | Adds context; never routes | Lets Eval follow related evidence | -| Channel | The write side of a Thread; the Source defines its addresses | A Thread may have several Channels; a Ticket holds a set of them | Acknowledgements, replies, pull requests and resolved reviews are all writes | -| Policy | The versioned assessment rules and each Source's part: who may originate work through that Source, in that Source's own identities, how the list is synchronized, and which bots may make candidates | The Policy version covers the rules, the accept mode and the Source parts. A change that bears on a live Ticket, such as a change to a rule it was accepted under, is delivered to its Assignment; it does not revoke Mandates. Identities on different Sources are never mapped to each other | When the rules change, a running Eval must be discarded | -| Eval | The judgement on a batch of Triggers, in two layers: gate and assessment | Input: the Trigger batch, a Thread snapshot, every Ticket on the Thread including ended ones, and the current Policy. Output: a Decision. Holds no state | Decides whether new work starts, goes on or stops | -| Mandate | What an accept grants: the Policy version, the scope, the strength, the matched rules, the sender's identity on that Source and how the Source received the Trigger | Immutable; wider scope means a new Ticket. A Ticket's end does not end it: only a person's stop withdraws it, and a Policy that no longer holds it revokes it. Revalidated against the current Policy before an ended Ticket resumes | States why the work may be done, and how far it may go | -| Ticket | A unit of authorized work | Only Eval creates one. Refers to its Mandate; holds its Channels and Inputs. Goes open → ended, and back to open when a person's message on one of its Threads resumes it, unless a person stopped it. Defines its own state instead of following the Executor's | Owns the execution and its reports | -| Assignment | The binding of a Ticket to one Executor instance | A Ticket has several over time. Goes pending → running → ended independently of the Ticket. Only its final report may end the Ticket on the Executor's side | The Executor is outside the domain, but who executes must be inside it | -| Subscription | A many-to-many relation between Tickets and Threads, held by the Thread | A matching Trigger fans out to every live subscribed Ticket, with no check of who sent it; a person's message that reaches a Ticket this way takes no Eval. A Thread an Executor follows for context reaches its Ticket the same way, but lends it no authority and does not resume it. An ended Ticket keeps its Subscriptions, including that of a pull request that closed or merged: a person's message resumes it under the same Mandate, with no Eval or Decision; messages from bots and CI do not reach it, since its Executor reads its Threads again when it resumes. A Ticket a person stopped receives nothing. The main Session attaches a Thread to an existing Ticket only when its message points at that Ticket explicitly. A person can drop every Subscription on a Thread; an Executor can drop only its own Ticket's Subscription to a Thread it finds unrelated, except the Thread its Ticket started on and a Thread a Source made it follow, such as its pull request's | Later discussion reaches the Executor without anyone mentioning the agent again | - -A person is not a domain concept. A Trigger carries its actor's identifier as the Source gives it. An allowed list is a Source's part of the Policy, in that Source's identities. That one person has accounts on several Sources is prompt text that the agents read when they need it. - -The Executor, with its Sessions and directories, is outside the domain and connects through Assignments. - -Aggregates: a Source holds its read position. A Thread holds its Subscriptions, its running Eval, which runs serially and merges Triggers that arrive meanwhile into the next batch, and its Decision history. A Ticket holds its Assignments, Channels and Inputs, and refers to Decisions by id. - -## Rejected concepts - -| Rejected | Reason | -| --- | --- | -| Principal or Membership as an entity, or mapping identities across Sources | A person is Trigger metadata; an allowed list is a Source's part of the Policy; a cross-Source mapping is prompt text | -| Eval scheduling its own delayed Triggers | A condition that needs waiting is an ordinary Source, so Eval never depends back on Sources | -| Waiting or revoked Eval states | Waiting belongs to Sources; an Eval that runs out of time queues its Triggers again, or ends `unavailable` | -| At most one live Ticket per Thread | One Thread can carry several independent pieces of work; the assessment avoids duplicates from the Ticket list | -| An "append to Ticket" Decision | Appending is Subscription fan-out and needs no Eval | -| Admission checks on Subscriptions | A Subscription covers the whole Thread; the Mandate separates authorization from context | -| Correlation that routes | Subscriptions route; Correlation only adds context | -| Ticket state that follows the DSH Session | A Ticket can be handed off, resumed, or run by several Executors, so it defines its own state | -| An Executor declaring that a person asked it to stop | Only Eval decides the strength of an end | -| Cancelling running Tickets on a Policy change | A cancelled change leaves half-finished work; the change is recorded and delivered instead | -| Locking all of an Executor Session's tools when it fails or blocks | It would refuse a person working directly in the Session, and the report after recovery; pausing automatic input is enough | -| Inferring "waiting on a person" from a report's free text | The classification needs structured facts; the Executor asks through a structured field | - -## What is reusable - -The concepts above and their rules are reusable. The Source implementations, the Policy contents, the Executor and the Channel implementations belong to a deployment. Each Source keeps its own words internally, such as message, chat or notification, and translates them into Triggers. - -| Part | In this repository | -| --- | --- | -| Source | Feishu: live events, history recovery, polling of subscribed topics. GitHub: notifications, subscribed Issues and pull requests, CI changes, pull requests merged or closed, and, where switched on, every new Issue, pull request and comment in the repository and the default branch's CI | -| Policy | Assessment rules, and each Source's allowed list, its sync, its bot list and its admission table | -| Executor | A DSH Executor Session and a directory per Ticket, in an execution environment whose variables the Executor and the Sources register | -| Channel | Feishu topic replies, GitHub comments and review thread replies | - -## Glossary - -A reference list of the terms this and the later documents use, with their Chinese names; the later documents explain the terms not introduced above. The panel shows one language at a time; its (?) button opens this glossary with both languages. The messages agents send to people on GitHub and Feishu use plain language, not these terms. - -| Term | 中文 | Meaning | -| --- | --- | --- | -| Agent | 智能体 | The automated team member dsh-superagent runs, acting on Sources through identities such as a GitHub account and a Feishu bot | -| Main Session | 主 Session | The DSH Session that evaluates Triggers, accepts and dispatches Tickets, and replies; `orchestrator` in code and prompts | -| Executor Session | 执行器 Session | The DSH Session an Executor runs for one Ticket; `worker` in code and prompts | -| Source | 来源 | A stream of events: a kind that a plugin provides, plus one instance configuration | -| Trigger | 触发 | One immutable event from a Source | -| Thread | 线程 | Triggers in time order; the unit within which Evals run one at a time | -| Correlation | 关联 | The relation between Threads that share a Trigger; it adds context only | -| Channel | 渠道 | The write side of a Thread | -| Policy | 策略 | The versioned rules for accepting work, and each Source's part of it, such as who may ask for work there | -| Policy version | 策略版本 | The identifier of one combination of rules, Source parts and accept mode; every Mandate records it | -| Eval | 评估 | The process that turns a batch of Triggers into a Decision | -| Gate | 准入 | The deterministic first layer of Eval: who may originate work, and with what strength | -| Assessment | 研判 | The model's judgement during Eval | -| Decision | 决定 | The result of Eval | -| Mandate | 授权 | What an accept grants: strength, scope and the Policy version it relied on | -| Ticket | 工单 | A unit of authorized work | -| Assignment | 派工 | The binding of a Ticket to one Executor instance | -| Subscription | 订阅 | The relation between a Ticket and a Thread | -| Fan-out | 扇出 | Delivering Triggers of a subscribed Thread to a Ticket as context | -| Executor | 执行器 | The port that runs Assignments: one Executor Session and one directory per Ticket | -| Execution environment | 执行环境 | Where Executor Sessions and the prepare command run: variables that the Executor and the Sources register, each with a key, a value and a description | -| Report | 报告 | The state an Assignment reports to its Ticket | -| Attention | 待人工决定 | A question on a Ticket that waits for a person's answer | -| Disposition | 去向 | The record of a Trigger being filtered, evaluated or fanned out | -| Input | 输入 | Context delivered to a Ticket: fanned-out Triggers, Policy changes, resumes and text | -| Scope | 范围 | The work a Mandate allows | -| Strength | 强度 | How strong the basis of a Mandate or an Eval is | - -| Group | Values | -| --- | --- | -| Decision | accept 接单 · stop 停止 · ignore 忽略 · unavailable 未完成 | -| Strength | explicit 明确 · inferred 推断 | -| Assignment | pending 待启动 · running 执行中 · ended 已结束 | -| Report | progress 进展 · waiting 等待 · ready 就绪 · blocked 受阻 · failed 失败 · dropped 无需处理 | diff --git a/docs/001-领域模型.md b/docs/001-领域模型.md new file mode 100644 index 0000000..e2a1734 --- /dev/null +++ b/docs/001-领域模型.md @@ -0,0 +1,112 @@ +# 001 领域模型 + +本文命名 dsh-superagent 的各个概念,以及每个概念守住的规则。代码、agent 调用的工具、面板和其他文档都使用这些术语,不用别的。依照 [000 理念](000-理念.md),模型只保留工作需要的概念,一个业务需求会变成其中某个概念的属性。 + +## 流程 + +工作从 Trigger 到 Eval 再到执行。Source 产出 Triggers。一个 Trigger 作为上下文扇出给订阅了其 Threads 的各个 Tickets。以这种方式到达某个 Ticket 的人的消息就留在那个 Ticket 上;其余每个 Trigger 都进入 Eval,由它决定是否发起新的工作。Eval 接单时,创建一个带 Mandate 的 Ticket。Assignment 把 Ticket 交给一个 Executor,结果通过 Channel 回到 Source。 + +```mermaid +flowchart LR + S[Source] -->|Trigger| T[Thread] + T -->|batch| E[Eval] + T -->|Subscription fan-out| K[Ticket] + E -->|accept with Mandate / stop| K + E -->|ignore / unavailable| X[recorded] + P[Policy change] -->|notice| K + K -->|Assignment| W[Executor] + K -->|Channel| S +``` + +## 概念 + +Source、Thread、Eval、Mandate 与 Subscription 构成核心领域;其余概念支撑它们。 + +| 概念 | 定义 | 它守住的规则 | 为什么存在 | +| --- | --- | --- | --- | +| Source | 一条事件流。Source kind 是一个可复用的实现;Source instance 是某个 kind 的一份配置:是否运行、监听什么 | 保存自己的读取位置与自己的 bot 身份。定义什么算事件,包括只在时间上成立的条件。在进入领域之前,丢弃 bot 自己的消息和监听范围之外的 Triggers。把每个 Trigger 翻译成它所属的 Threads 与用于答复的 Channel 地址 | 重启不得重放事件 | +| Trigger | 一条不可变事件,带有 Source 自己的元数据,例如它的 actor | 属于一个或多个 Threads。同一个 Trigger 被接单两次只创建一个 Ticket | 审计与幂等的单位 | +| Thread | 按时间排列的 Triggers;由 Source 定义哪些 Triggers 构成一个 Thread | 同一时刻至多运行一个 Eval。它可以有多个 Tickets。它保存自己的 Subscriptions、正在运行的 Eval 和 Decision 历史 | 给 Eval 一个一致的视图和它的上下文 | +| Correlation | 同一个 Trigger 所属 Threads 之间的关系 | 只增加上下文;从不路由 | 让 Eval 能追踪相关证据 | +| Channel | Thread 的写入侧;地址由 Source 定义 | 一个 Thread 可以有多个 Channels;一个 Ticket 持有其中一个集合 | 认领回执、答复、pull requests 和 resolved reviews 都是写入 | +| Policy | 带版本的评估规则,加上每个 Source 自己的部分:谁可以通过该 Source 发起工作、用的是该 Source 自己的身份、名单如何同步、以及哪些 bots 可以产生候选 | Policy 版本覆盖规则、accept mode 和各 Source 部分。一个与某个活着的 Ticket 相关的变化——比如它接单所依据的规则变了——会投递给它的 Assignment;但不吊销 Mandates。不同 Source 上的身份彼此绝不映射 | 规则变了,正在运行的 Eval 必须作废 | +| Eval | 对一个 Trigger 批次的判断,分两层:gate 与 assessment | 输入:Trigger 批次、一个 Thread 快照、该 Thread 上的所有 Tickets(含 ended 的)、以及当前的 Policy。输出:一个 Decision。不持有状态 | 决定新的工作是开始、继续还是停止 | +| Mandate | 一次接单所授予的东西:Policy 版本、scope、强度、命中的规则、发送者在该 Source 上的身份、以及 Source 收到该 Trigger 的方式 | 不可变;更宽的 scope 意味着新的 Ticket。Ticket 的结束不等于它的结束:只有人的 stop 才撤回它,一个不再成立的 Policy 会吊销它。ended Ticket 恢复之前会按当前 Policy 重新校验 | 说明工作为什么可以做、能做到什么程度 | +| Ticket | 一份授权工作的单位 | 只能由 Eval 创建。引用它的 Mandate;持有它的 Channels 与 Inputs。走 open → ended,当有人在它某个 Thread 上发的消息让它复活时会回到 open,除非它是被人停掉的。它定义自己的状态,不跟随 Executor 的 | 拥有执行及其报告 | +| Assignment | 一个 Ticket 与某一个 Executor 实例的绑定 | 一个 Ticket 随时间会有多个。独立于 Ticket 走 pending → running → ended。在 Executor 一侧,只有它的最终报告能结束 Ticket | Executor 在领域之外,但"由谁执行"必须在领域之内 | +| Subscription | Ticket 与 Threads 之间的多对多关系,由 Thread 持有 | 命中的 Trigger 会扇出给每个订阅着的活 Ticket,不检查发送者是谁;以这种方式到达某个 Ticket 的人的消息不经过 Eval。Executor 为上下文关注的 Thread 同样会抵达它的 Ticket,但不给它带来任何授权、也不使它复活。ended Ticket 保留它的 Subscriptions,包括一个已关闭或已合并的 pull request 的订阅:人的消息会在同一个 Mandate 下让它复活,不经过 Eval 也不产生 Decision;来自 bots 和 CI 的消息到不了它,因为它的 Executor 恢复时会重新读取它的 Threads。被人停掉的 Ticket 什么都收不到。只有当一条消息明确指向某个既有 Ticket 时,main Session 才把 Thread attach 到它。人可以移除某个 Thread 上的所有 Subscriptions;Executor 只能移除自己的 Ticket 对一个它判定为无关的 Thread 的订阅,除了它的 Ticket 由此开始的那个 Thread、以及被 Source 要求关注的那个 Thread(例如它的 pull request) | 让后续讨论不需要任何人再次点名 agent,就能到达 Executor | + +人不是领域概念。Trigger 带着 Source 给出的 actor 标识符。允许名单是 Source 在 Policy 里的那部分,用的是该 Source 的身份体系。同一个人拥有几个 Source 上的账号——这是 agent 需要时才会读的提示词文本。 + +Executor 连同它的 Sessions 和目录在领域之外,通过 Assignments 连接。 + +聚合:Source 持有它的读取位置。Thread 持有它的 Subscriptions、正在运行的 Eval——Eval 串行运行,期间到达的 Triggers 并入下一批——以及它的 Decision 历史。Ticket 持有它的 Assignments、Channels 与 Inputs,并按 id 引用 Decisions。 + +## 被拒绝的概念 + +| 被拒绝的 | 理由 | +| --- | --- | +| Principal 或 Membership 作为实体、或跨 Sources 映射身份 | 人是 Trigger 的元数据;允许名单是 Source 在 Policy 里的部分;跨 Source 的映射是提示词文本 | +| Eval 自己调度延迟的 Triggers | 一个需要等待的条件就是一个普通的 Source,这样 Eval 永远不反过来依赖 Sources | +| waiting 或 revoked 的 Eval 状态 | 等待属于 Sources;超时的 Eval 把它的 Triggers 重新排队,或以 `unavailable` 结束 | +| 每个 Thread 至多一个活 Ticket | 一个 Thread 可以承载多件相互独立的工作;评估靠 Ticket 列表来避免重复 | +| "append to Ticket" 这个 Decision | 追加是 Subscription 扇出,不需要 Eval | +| 对 Subscriptions 做准入检查 | Subscription 覆盖整个 Thread;Mandate 负责区分授权与上下文 | +| 会路由的 Correlation | 路由归 Subscriptions;Correlation 只增加上下文 | +| Ticket 状态跟随 DSH Session | Ticket 可以被移交、复活、或由多个 Executors 执行,所以它定义自己的状态 | +| 由 Executor 声称有人要求它停止 | 只有 Eval 决定结束的强度 | +| Policy 变更时取消正在运行的 Tickets | 被取消的改动会留下做了一半的工作;正确的做法是记录并投递这个变更 | +| Executor Session 失败或受阻时锁掉它的全部工具 | 那会连累直接在 Session 里工作的人、以及恢复之后的报告;暂停自动输入就够了 | +| 从报告的自由文本推断"在等人" | 分类需要结构化事实;Executor 用结构化字段来问 | + +## 哪些是可复用的 + +上面的概念及其规则是可复用的。Source 实现、Policy 内容、Executor 与 Channel 实现属于具体的部署。每个 Source 在内部保留自己的说法,例如 message、chat 或 notification,并把它们翻译成 Triggers。 + +| 部分 | 在本仓库中 | +| --- | --- | +| Source | Feishu:实时事件、历史恢复、对订阅话题的轮询。GitHub:notifications、被订阅的 Issues 与 pull requests、CI 变化、pull requests 的合并或关闭,以及(开启时)仓库中每一个新的 Issue、pull request、评论和默认分支的 CI | +| Policy | 评估规则,以及每个 Source 的允许名单、它的同步、bot 名单与准入表 | +| Executor | 每个 Ticket 一个 DSH Executor Session 和一个目录,运行在一个由 Executor 与各 Sources 注册变量的执行环境里 | +| Channel | Feishu 话题回复、GitHub 评论与 review thread 回复 | + +## 术语表 + +本及后续文档所用术语的对照表,附中文名;上面没有引入的术语由后续文档解释。面板一次只显示一种语言;它的 (?) 按钮打开这份双语术语表。agent 在 GitHub 与 Feishu 上发给人的消息使用平实的语言,不用这些术语。 + +| 术语 | 中文 | 含义 | +| --- | --- | --- | +| Agent | 智能体 | dsh-superagent 运行的自动化团队成员,通过 GitHub 账号、Feishu bot 这样的身份在 Sources 上行动 | +| Main Session | 主 Session | 评估 Triggers、接单并派发 Tickets、以及答复的那个 DSH Session;代码与提示词里叫 `orchestrator` | +| Executor Session | 执行器 Session | Executor 为一个 Ticket 运行的 DSH Session;代码与提示词里叫 `worker` | +| Source | 来源 | 一条事件流:由插件提供的一个 kind,加上一份实例配置 | +| Trigger | 触发 | 来自 Source 的一条不可变事件 | +| Thread | 线程 | 按时间排列的 Triggers;Eval 在其上逐个运行的单位 | +| Correlation | 关联 | 共享一个 Trigger 的 Threads 之间的关系;只增加上下文 | +| Channel | 渠道 | Thread 的写入侧 | +| Policy | 策略 | 用于接受工作的带版本规则,以及每个 Source 在其中的部分,例如谁可以在那里请求工作 | +| Policy version | 策略版本 | 规则、Source 部分与 accept mode 的一种组合的标识;每个 Mandate 都记录它 | +| Eval | 评估 | 把一个 Trigger 批次变成一个 Decision 的过程 | +| Gate | 准入 | Eval 确定性的第一层:谁可以发起工作、以什么强度 | +| Assessment | 研判 | Eval 期间模型做出的判断 | +| Decision | 决定 | Eval 的结果 | +| Mandate | 授权 | 一次接单所授予的东西:强度、scope 和它所依据的 Policy 版本 | +| Ticket | 工单 | 一份授权工作的单位 | +| Assignment | 派工 | 一个 Ticket 与某一个 Executor 实例的绑定 | +| Subscription | 订阅 | 一个 Ticket 与一个 Thread 之间的关系 | +| Fan-out | 扇出 | 把被订阅 Thread 的 Triggers 作为上下文交付给一个 Ticket | +| Executor | 执行器 | 运行 Assignments 的端口:每个 Ticket 一个 Executor Session 和一个目录 | +| Execution environment | 执行环境 | Executor Sessions 与 prepare command 运行的地方:由 Executor 与各 Sources 注册的变量,每个都有 key、取值和说明 | +| Report | 报告 | 一个 Assignment 向它的 Ticket 报告的状态 | +| Attention | 待人工决定 | Ticket 上等待人答复的一个问题 | +| Disposition | 去向 | 一个 Trigger 被过滤、被评估或被扇出的记录 | +| Input | 输入 | 投递给一个 Ticket 的上下文:扇出的 Triggers、Policy 变更、resume 与文本 | +| Scope | 范围 | 一个 Mandate 所允许的工作 | +| Strength | 强度 | 一个 Mandate 或 Eval 的依据有多强 | + +| 分组 | 取值 | +| --- | --- | +| Decision | accept 接单 · stop 停止 · ignore 忽略 · unavailable 未完成 | +| Strength | explicit 明确 · inferred 推断 | +| Assignment | pending 待启动 · running 执行中 · ended 已结束 | +| Report | progress 进展 · waiting 等待 · ready 就绪 · blocked 受阻 · failed 失败 · dropped 无需处理 | diff --git a/docs/002-authorization.md b/docs/002-authorization.md deleted file mode 100644 index 287e3fe..0000000 --- a/docs/002-authorization.md +++ /dev/null @@ -1,155 +0,0 @@ -# 002 Authorization - -Authorization determines who may start work and what that work may include. This document builds on [001 Domain model](001-domain.md). - -The host enforces allowed lists, rules and Mandates. Messages, comments and quoted documents are inputs to a decision; they cannot change those permissions. - -## Two paths for a Trigger - -### Eval: deciding whether to start work - -The Source records whether a message addresses the agent: `mention` for a mention of the bot or agent account, and `direct` for a direct message. The gate checks the sender and the Source's Policy: - -- An allowed person who addresses the agent makes an explicit request. -- A person's follow-up on a Thread with Tickets follows the rules below. -- Other human messages use the admission table for their receiving scope. -- A bot on the Source's bot list can produce an inferred candidate, never an explicit request. Other bots are not admitted. - -### Subscriptions: continuing existing work - -Every message on a subscribed Thread reaches its subscribed Tickets as context, including messages from bots and CI. This delivery bypasses the gate and grants no new authority. Each Ticket remains limited by its Mandate. - -A person's message on a Ticket's own Thread belongs to that Ticket's Executor to decide. The same applies when the main Session attaches a message to a Ticket. While an Executor owns the message, it takes no Eval and gets no Decision. Each Executor either handles the request within its Ticket's scope or hands it back for routing elsewhere. - -Ownership continues while the Ticket is live, even if a person removes its Subscription. It also covers an ended Ticket that was not stopped by a person and has the message waiting to resume it. - -The main Session receives a follow-up notice. While an Executor owns the message, the main Session may only stop work on the person's request; accept, attach, ignore and requeue are refused. A stop still passes through the gate with the follow-up strength described below. - -### Handing back and releasing follow-ups - -An Executor hands a message back once when it asks for work beyond the Ticket's scope or belongs to another Ticket. The Ticket keeps its Subscription. If an attached Thread is unrelated, the Executor hands back the messages it owns there. - -A message goes to a fresh Eval only after no Ticket still owns it. Its candidate includes the Executors' reasons. The gate judges it as an ordinary message on that Thread, and the main Session can accept it, attach it elsewhere or ignore it. Handing back a message the Ticket did not own, such as a bot message or one a person already sent to Eval, queues nothing. - -When a Ticket ends, it releases follow-ups its Executor did not read or take up. These include messages never delivered to the Executor and messages delivered after its last Report or turn end, provided it neither acknowledged, answered nor handed them back. Each released message goes to Eval when no other Ticket owns it. A stop ends only the stopped Ticket's work. - -A stopped Ticket does not send its stop message to Eval again unless another Ticket handed it back. The main Session that stopped it may accept the rest of the message as new work. - -A person can send an owned follow-up to Eval from the panel. If the main Session then attaches it back to a live Ticket, the Executor receives it again. The panel also offers re-evaluation when no effective judgement covers the message. - -### Context-only subscriptions - -An Executor can follow a related Thread, such as another Issue or pull request, for context. Its messages reach the Ticket but grant no authority or follow-up strength. Human messages there do not resume an ended Ticket unless the main Session attaches a message from that Thread. Notes from the main Session about other work are also context only. - -Human messages that reach no Ticket, including Threads whose Tickets were stopped by a person, take the Eval path. Bot messages take that path independently of their delivery to subscribed Tickets. - -## Strength - -| Strength | Cases | -| --- | --- | -| explicit | A person on the list addresses the agent, or follows up on a Thread that already has a Ticket; a cell of the admission table set to explicit request | -| inferred | A person not on the list follows up on a Thread with a Ticket, unless their cell of the admission table is explicit; a cell of the admission table set to inferred candidate; a message of a bot on the Source's bot list that addresses the agent or arrives where the Source receives every message | -| not admitted | Any other bot's message, everything the admission table ignores, and every inferred case while the accept mode is `off` | - -For an unlisted person, an explicit admission cell takes precedence even on a Thread with Tickets and works in every accept mode. Otherwise their follow-up is inferred and is not admitted in `off` mode. Messages on Threads followed only for context use the admission table instead of follow-up strength. - -## Admission - -Each Source's Policy has an admission table. Its rows are receiving scopes: - -- **Feishu:** each configured chat, other chats the bot receives mentions from, and direct messages. Other chats are received only when **其他群** is set to **仅 @** on the Feishu card. -- **GitHub:** new Issues, new pull requests, comments and the default branch's CI. - -A row has up to three cells. Each cell decides whether a message becomes an explicit request, an inferred candidate or nothing: - -| Cell | Choices | Default | -| --- | --- | --- | -| On the list, not addressing the agent | inferred, ignore | inferred | -| Not on the list, addressing the agent | explicit, inferred, ignore | ignore | -| Not on the list, not addressing the agent | inferred, ignore | ignore | - -Cells for unaddressed messages are available only where the Source receives every message, such as a Feishu chat set to **全部消息** or a GitHub scope with its receive-all switch enabled. A CI result has no sender, so its row offers only the last cell. Bot messages use the bot list instead of these cells. - -Setting the unlisted-addressed cell to explicit lets anyone in that scope request work by mentioning the agent. This can be useful for a dedicated support chat. Inferred candidates still depend on the accept mode. Follow-ups normally bypass this table, except that an explicit cell still grants explicit strength. - -The admission table is part of the Policy version. Weakening a cell does not immediately stop open Tickets accepted under it. It can revoke their Mandates when they are revalidated before resuming. - -### Bots and automated reviewers - -The host recognizes GitHub Apps, logins ending in `[bot]`, and Feishu bots as bots. A Source can list bots such as `dependabot[bot]` whose messages may become inferred candidates. They must either address the agent or post in a scope that receives every message. They cannot make explicit requests or stop Tickets. - -An automated reviewer using an ordinary account counts as a person to the host. Its comments reach subscribed Tickets and generate follow-up notices. Team prompt text must identify such accounts as automated reviewers. - -## Routing candidates - -The main Session routes explicit candidates with `orchestrator_route`. One call can contain several accept, attach, stop or ignore decisions. Each item is processed independently: a refused item or a missing field does not prevent the others from running. - -An inferred candidate normally needs a full assessment. There is one shortcut: if its card clearly touches no enabled rule, such as plain chat or unrelated discussion, the main Session may ignore it directly. The Decision records `ignoredFromCard` to distinguish this from a rule assessment. The main Session reads the enabled rules in its Policy prompt section. - -Accepting an inferred candidate always requires an assessment. Direct accept refuses a Trigger that the gate did not admit as explicit. - -## Accept mode and rules - -The accept mode is part of the Policy: - -| Mode | Effect | -| --- | --- | -| `off` | Nothing inferred becomes a candidate, such as an unaddressed message, a bot's message or the follow-up of a person not on the list | -| `observe` (default) | Inferred candidates are assessed; a positive assessment is recorded as ignore marked "would accept", and nothing is accepted | -| `auto` | A positive assessment of an inferred candidate accepts it | - -Explicit requests work in every mode. Start in `observe` and switch to `auto` after reviewing the recorded Decisions. Each assessment judges every enabled rule exactly once, whether it matches or not. A proactive accept requires at least one matching rule. - -Each rule describes a kind of proactive work. The Ticket's Mandate records the matched rules, and its Executor receives their names and instructions. - -For that Ticket, a rule's process and communication requirements override the defaults. A silence rule can suppress an acknowledgement; a review rule can name the required reviewer and approval before merge. Requirements that apply to every Ticket belong in team prompt sections instead. - -## Policy version - -Rules, accept mode and each Source's Policy form the Policy version. Every Mandate records the version under which it was accepted. - -A version change discards running Evals and queues their Triggers again. It also sends a policy-changed Input to each affected live Ticket. The Input explains whether: - -- a matched rule changed, including its new text, or no longer applies; -- the requester may no longer start work; or -- proactive work is no longer accepted, for example because the mode changed from `auto`. - -Unaffected Tickets receive no notice. The change does not immediately revoke live Tickets' Mandates. - -Source configuration is separate from Policy. It controls what enters the system: which chats are read and whether a scope receives all messages or only mentions. A Mandate records its Source and receiving scope. Resuming needs the Source to still listen to the Thread, but does not require it to still receive every message there. - -Replies wait while their Source is off. They are dropped if the Source no longer listens to their destination. - -A condition on what the Executor may do with its own result, such as when it may merge, does not decide who may authorize work. It is part of the agent's instructions, not of the Policy. - -## Resume and stop - -An ended Ticket keeps its Mandate. A person's message on one of its own Threads can resume the same work without a new Eval or Decision, regardless of who sent it. Bots and CI do not wake ended Tickets. The Executor decides whether the request fits the scope and ends the round if it does not. - -Only three operations change a Mandate: accept creates it, a person's stop withdraws it, and failed revalidation revokes it. A Ticket stopped by a person stays ended and receives nothing more. Work requested there needs a new accept. - -### Revalidation before resume - -The Source must still listen to the original Thread. The remaining checks depend on the Mandate's basis: - -- An explicit follow-up Mandate passes. -- An inferred Mandate needs `auto` mode and its matched rules still enabled. -- A Mandate admitted by a table cell must still receive at least its original strength under today's table and sender membership. Removing a sender from the list can therefore revoke a Mandate accepted through the listed-sender cell. -- A bot's Mandate requires that bot to remain on the Source's bot list. Inferred table and bot Mandates also need `auto` mode and enabled matched rules. -- Other Mandates require the sender to remain on the Source's allowed list. - -If revalidation fails, the Ticket stays ended and takes none of its waiting messages. Each message is treated as having reached no Ticket; if no other Ticket owns it, it goes to Eval. The main Session is not told it reached a worker. This also applies after a processing pause. Attaching to the Ticket is refused, and further work requires a new accept. - -- **A person saying "stop" in a Thread is an ordinary Trigger,** and Eval decides stop. An Executor cannot declare that a person asked it to stop. -- **Stop in the panel is a command.** It acts on the Ticket directly and is recorded as a manual stop. -- **Waiting conditions belong to Sources.** A condition such as "no assignee for N hours" would be a Source that waits and checks again itself; Eval never waits. - -## When Eval does not finish - -The time limit starts when the candidate reaches the main Session. Time spent waiting for delivery does not count, and an undelivered candidate never expires. - -An expired Eval queues its Triggers for another attempt. After two consecutive retries, the third expiry records `unavailable`. An assessment failure also records `unavailable`; a later Trigger can start a new Eval. A Policy change or a decision made against an outdated Policy records `unavailable` and immediately queues the batch under the current version. - -The `evals` health entry reports unresolved unavailable Decisions from the last hour. `scripts/dsh-superagent status` includes that error. - -The panel asks for human attention only when the unavailable Decision is less than 24 hours old, is still the latest Decision on its Thread, has not been dismissed, and the Thread is neither queued nor evaluating. A person can evaluate the Trigger again or dismiss the problem. diff --git a/docs/002-授权.md b/docs/002-授权.md new file mode 100644 index 0000000..8821442 --- /dev/null +++ b/docs/002-授权.md @@ -0,0 +1,155 @@ +# 002 授权 + +授权决定谁可以开始工作、以及那项工作可以包含什么。本文建立在 [001 领域模型](001-领域模型.md) 之上。 + +host 强制执行允许名单、规则与 Mandates。消息、评论与被引用的文档只是决策的输入;它们不能改变那些权限。 + +## 一条 Trigger 的两条路径 + +### Eval:决定是否开始工作 + +Source 记录消息是否点名了 agent:提到 bot 或 agent 账号的记为 `mention`,私聊记为 `direct`。gate 检查发送者与该 Source 的 Policy: + +- 名单内的人点名 agent,构成显式请求。 +- 人在一个已有 Tickets 的 Thread 上的跟进,遵循下面的规则。 +- 其他人类消息按它们的接收范围走准入表。 +- Source bot 名单上的 bot 可以产生 inferred 候选,永远不能构成显式请求。其他 bots 不予准入。 + +### Subscriptions:继续已有的工作 + +被订阅 Thread 上的每一条消息都会作为上下文到达订阅它的 Tickets,包括来自 bots 与 CI 的消息。这条投递路径绕过 gate,不授予任何新的授权。每个 Ticket 仍受它的 Mandate 约束。 + +Ticket 自己的 Thread 上的人的消息,归该 Ticket 的 Executor 决定。main Session 把一条消息 attach 给某个 Ticket 时同理。消息仍由 Executor 认领期间,不进入 Eval、不产生 Decision。每个 Executor 要么在自己 Ticket 的 scope 内处理这个请求,要么把它交还给别处路由。 + +认领在 Ticket 存活期间持续,即使有人移除了它的 Subscription。认领也覆盖这样一个 ended Ticket:它不是被人停掉的,并且有消息正在等待让它恢复。 + +main Session 会收到一条跟进通知。消息仍由 Executor 认领时,main Session 只能应人的要求停止工作;accept、attach、ignore 与 requeue 都会被拒绝。一次停止仍然要过 gate,带着下面所述的跟进强度。 + +### 交还与释放跟进 + +Executor 只在一条消息要求的工作超出 Ticket 的 scope、或属于另一个 Ticket 时,把它交还一次。Ticket 保留它的 Subscription。如果一个被 attach 上来的 Thread 无关,Executor 交还它在其中认领的那些消息。 + +一条消息只有在没有任何 Ticket 仍认领它之后,才进入一次全新的 Eval。它的候选里带着各个 Executors 的理由。gate 按该 Thread 上的一条普通消息来判它,main Session 可以接单、把它 attach 到别处、或忽略。交还一条 Ticket 并未认领的消息——例如一条 bot 消息、或一条人已经送进 Eval 的消息——什么都不会排队。 + +一个 Ticket 结束时,会释放它的 Executor 没有读过或没有认领的跟进。其中包括从未投递给 Executor 的消息,以及在其最后一次 Report 或回合结束之后才送达的消息,只要它既没有认领、答复过、也没有交还。每条被释放的消息在没有其他 Ticket 认领时进入 Eval。一次 stop 只结束被停那个 Ticket 的工作。 + +被停掉的 Ticket 不会把它的 stop 消息再送一次 Eval,除非有另一个 Ticket 交还过它。停掉它的 main Session 可以把这条消息的其余部分接成一项新工作。 + +人可以从面板把一个被认领的跟进送进 Eval。若 main Session 随后又把它 attach 回一个活着的 Ticket,Executor 会再次收到它。当没有任何有效的判断覆盖这条消息时,面板还提供重新评估。 + +### 仅上下文的订阅 + +Executor 可以为了上下文关注一个相关的 Thread,例如另一个 Issue 或 pull request。它的消息能到达 Ticket,但不授予授权或跟进强度。那里的人类消息不会恢复一个 ended Ticket,除非 main Session 把来自那个 Thread 的一条消息 attach 过来。main Session 关于其他工作的备注同样只是上下文。 + +没有到达任何 Ticket 的人类消息——包括 Tickets 都被人停掉的 Threads——走 Eval 路径。bot 消息走哪条路径,与它们向订阅 Tickets 的投递互不相干。 + +## 强度 + +| 强度 | 情形 | +| --- | --- | +| explicit | 名单上的人点名 agent,或在一个已经有 Ticket 的 Thread 上跟进;准入表中设为 explicit request 的单元格 | +| inferred | 不在名单上的人在一个有 Ticket 的 Thread 上跟进,除非他的准入表单元格是 explicit;准入表中设为 inferred candidate 的单元格;Source bot 名单上某个 bot 的消息点名了 agent,或落在 Source 全量接收消息之处 | +| not admitted | 其他任何 bot 的消息、准入表忽略的一切、以及 accept mode 为 `off` 时所有 inferred 的情形 | + +对一个未列入名单的人,explicit 准入单元格有优先权——即使是在有 Tickets 的 Thread 上,并且在任何 accept mode 下都成立。否则他的跟进按 inferred 处理,在 `off` 模式下不予准入。只为上下文而关注的 Threads 上的消息走准入表,而不是跟进强度。 + +## 准入 + +每个 Source 的 Policy 都有一张准入表。它的行是接收范围: + +- **Feishu:** 每个已配置的 chat、bot 收到过 @ 的其他 chats、以及私聊。其他 chats 只有当 Feishu 卡片上的 **其他群** 设为 **仅 @** 时才接收。 +- **GitHub:** 新的 Issues、新的 pull requests、评论、以及默认分支的 CI。 + +一行至多三个单元格。每个单元格决定一条消息变成显式请求、inferred 候选、还是什么都不是: + +| 单元格 | 选项 | 默认 | +| --- | --- | --- | +| 名单内、未点名 agent | inferred、ignore | inferred | +| 名单外、点名 agent | explicit、inferred、ignore | ignore | +| 名单外、未点名 agent | inferred、ignore | ignore | + +未点名消息的单元格,只在 Source 全量接收消息的地方可用,例如设为 **全部消息** 的 Feishu chat、或开了全量接收开关的 GitHub scope。CI 结果没有发送者,所以它的行只提供最后一个单元格。bot 消息用 bot 名单判断,不看这些单元格。 + +把"名单外、点名 agent"单元格设为 explicit,等于允许那个范围内的任何人通过 @ agent 请求工作。这对一个专门的支持群可能有用。inferred 候选仍然取决于 accept mode。跟进通常绕过这张表,唯一的例外是 explicit 单元格仍会给出 explicit 强度。 + +准入表是 Policy 版本的一部分。削弱一个单元格不会立刻停止按它接单的那些 open Tickets。在恢复前的重新校验中,它可以吊销它们的 Mandates。 + +### Bots 与自动化评审者 + +host 把 GitHub Apps、以 `[bot]` 结尾的 logins、以及 Feishu bots 认作 bots。Source 可以列出 `dependabot[bot]` 这样的 bots,它们的消息可以成为 inferred 候选。它们要么点名 agent,要么发在一个全量接收消息的范围里。它们不能构成显式请求,也不能停止 Tickets。 + +用普通账号的自动化评审者,在 host 看来是人。它的评论会到达订阅的 Tickets 并产生跟进通知。团队的提示词文本必须把这类账号指认为自动化评审者。 + +## 路由候选 + +main Session 用 `orchestrator_route` 路由显式候选。一次调用可以包含多个 accept、attach、stop 或 ignore 决定。每一项独立处理:某一项被拒或字段缺失,不妨碍其余各项执行。 + +inferred 候选通常需要一次完整的评估。有一个捷径:如果它的卡片明显不触及任何启用的规则——例如纯闲聊或无关讨论——main Session 可以直接忽略它。Decision 会记录 `ignoredFromCard`,以区别于一次规则评估。main Session 在它的 Policy 提示词节里读取启用的规则。 + +接一个 inferred 候选永远需要评估。直接 accept 会被拒绝——如果 gate 并没有把它准入为 explicit。 + +## Accept mode 与规则 + +accept mode 是 Policy 的一部分: + +| 模式 | 效果 | +| --- | --- | +| `off` | 任何 inferred 的东西都不成为候选,例如未点名的消息、bot 的消息、名单外的人的跟进 | +| `observe`(默认) | inferred 候选会被评估;一次正向的评估记为 ignore 并标上 "would accept",什么都不接单 | +| `auto` | 对 inferred 候选的一次正向评估即接单 | + +显式请求在所有模式下都有效。从 `observe` 起步,评审过记录的 Decisions 之后再切到 `auto`。每次评估对每条启用的规则恰好判断一次,无论命中与否。一次主动接单至少要求一条命中的规则。 + +每条规则描述一类主动工作。Ticket 的 Mandate 记录命中的规则,它的 Executor 会收到这些规则的名字与指令。 + +对那个 Ticket 而言,规则的流程与沟通要求会覆盖默认值。一条静默规则可以压掉认领回执;一条评审规则可以指定合并前所需的评审人与批准。适用于每个 Ticket 的要求应当放进团队提示词分节。 + +## Policy 版本 + +规则、accept mode 与各 Source 的 Policy 共同构成 Policy 版本。每个 Mandate 都记录它接单时所在的版本。 + +一次版本变更会丢弃正在运行的 Evals,并把它们的 Triggers 重新排队。它还会给每个受影响的活 Ticket 发一条 policy-changed Input。这条 Input 说明是哪种情况: + +- 一条命中的规则变了(包括它的新文本),或不再适用; +- 发起人不再可以开始工作;或 +- 主动工作不再被接受,例如模式从 `auto` 改走了。 + +未受影响的 Ticket 收不到通知。这次变更不会立刻吊销活着的 Tickets 的 Mandates。 + +Source 配置与 Policy 是分开的。它控制什么能进入系统:读哪些 chats、某个 scope 是接收全部消息还是只收 @。一个 Mandate 记录它的 Source 与接收范围。恢复要求 Source 仍然监听这个 Thread,但不要求它仍然全量接收那里的一切。 + +答复在它们的 Source 关闭时等待。若 Source 不再监听它们的目的地,答复被丢弃。 + +关于 Executor 可以拿自己的结果做什么的条件——例如它何时可以合并——不决定谁可以授权工作。它是 agent 指令的一部分,不是 Policy 的一部分。 + +## 恢复与停止 + +一个 ended Ticket 保留它的 Mandate。有人在它自己的某个 Thread 上发一条消息,无需新的 Eval 或 Decision 就能恢复同一份工作,无论发送者是谁。Bots 与 CI 不会唤醒 ended Tickets。由 Executor 决定这个请求是否合 scope,不合就结束这一轮。 + +只有三个操作会改变一个 Mandate:accept 创建它,人的 stop 撤回它,重新校验失败吊销它。被人停掉的 Ticket 保持 ended,之后什么都不收。那里要请求的工作需要一次新的接单。 + +### 恢复前的重新校验 + +Source 必须仍然监听原始的 Thread。其余的检查取决于 Mandate 的依据: + +- 一条 explicit 跟进类的 Mandate 直接通过。 +- 一条 inferred Mandate 需要 `auto` 模式、并且它命中的规则仍然启用。 +- 由准入表单元格准入的 Mandate,按今天的准入表和发送者的成员身份,必须至少仍能收到它原来的强度。因此把发送者移出名单可以吊销一个经"名单内发送者"单元格接下的 Mandate。 +- bot 的 Mandate 要求该 bot 仍在 Source 的 bot 名单上。inferred 的表类与 bot 类 Mandates 还需要 `auto` 模式与仍然启用的命中规则。 +- 其余 Mandates 要求发送者仍在该 Source 的允许名单上。 + +若重新校验失败,Ticket 保持 ended,一条等待中的消息都不取。每条消息按"没有到达任何 Ticket"处理;若没有其他 Ticket 认领它,它进入 Eval。main Session 不会被告知它到达过 worker。处理被暂停之后也是如此。attach 到该 Ticket 会被拒绝,继续工作需要一次新的接单。 + +- **一个人在 Thread 里说"停"是一条普通 Trigger,** 由 Eval 决定 stop。Executor 不能声称有人要求它停止。 +- **面板里的 Stop 是一条命令。** 它直接作用于 Ticket,并被记录为一次手动停止。 +- **等待类条件属于 Sources。** 一个像"N 小时无人 assign"这样的条件,应当是一个会自己等待并再次检查的 Source;Eval 从不等待。 + +## Eval 没有完成时 + +时限从候选到达 main Session 时开始计。等待投递的时间不计入,一个从未送达的候选永不过期。 + +一次超时的 Eval 把它的 Triggers 重新排队再试。连续两次重试之后,第三次超时记为 `unavailable`。一次评估失败也记 `unavailable`;后续的一个 Trigger 可以发起新的 Eval。一次 Policy 变更、或依据过期 Policy 做出的判断,记为 `unavailable` 并立刻按当前版本把该批次重新排队。 + +`evals` 健康条目报告最近一小时内未解决的 unavailable Decisions。`scripts/dsh-superagent status` 会包含那个错误。 + +面板只在以下条件全部满足时才请求人工注意:这条 unavailable Decision 距今不足 24 小时、仍是它所在 Thread 上最新的 Decision、没有被 dismiss、且该 Thread 既不处于 queued 也不处于 evaluating。人可以再次评估这个 Trigger,或 dismiss 这个问题。 diff --git a/docs/003-execution.md b/docs/003-execution.md deleted file mode 100644 index 6cb33a8..0000000 --- a/docs/003-execution.md +++ /dev/null @@ -1,203 +0,0 @@ -# 003 Execution - -This document follows an accepted Ticket through execution, delivery and recovery. It builds on [001 Domain model](001-domain.md) and [002 Authorization](002-authorization.md). - -## Ticket stages - -Each open Ticket has one stage, determined by what it is waiting for. Read the table from top to bottom; the first matching row wins. - -| Stage | Condition | Panel label | -| --- | --- | --- | -| person / delivery | Delivering an Input to the Executor Session failed; it is retried after 2 seconds, then after a delay that doubles up to a minute, and a success, a new Assignment or the Ticket's end clears it | Input undelivered | -| person / answer | It has an Attention | Awaiting answer | -| person / review | The latest Report is `ready` | Awaiting review | -| person / blocked, failed | The latest Report is `blocked` or `failed`, and no person's message reached the Ticket since | Blocked · Failed | -| working / queued | No Assignment, or a pending one | Queued | -| idle | The latest Report is `waiting`, or the Assignment ended | Waiting for a Trigger | -| working | Everything else | Working | - -A `person` stage means the Ticket cannot continue without human input. A new human message releases a `blocked` or `failed` Ticket to its Executor, so it moves to the next matching stage, usually `working`. - -An `idle` Ticket has finished its turn without an outstanding question. A Trigger on a subscribed Thread can continue it. - -## The Executor - -Each Ticket gets an Executor Session named `worker-` and a directory named after the Ticket's short name. Directories are created under the configured execution root, which defaults to `/work`. - -Executor Sessions run commands without approval as the operator's OS user. Their `dsh-superagent-worker` preset contains the tools needed for work and moves foreground shell commands to background jobs after 5 seconds. This lets a worker receive new messages at the next step instead of waiting for a long command to finish. - -The main Session uses `dsh-superagent-router`, with the same 5-second shell and job-wait limits and a smaller tool set for routing. Sessions retain the preset selected at creation. Worker subagents use DeepSeek Harness's `standard` preset. - -### Background work and worker slots - -When a turn ends with shell jobs or subagents still running, the Ticket keeps its worker slot and stays working. It is not reported as waiting, and the main Session receives no waiting report. A completed job or subagent wakes the Executor with its result in the same slot. Once a turn ends with no background work, the Executor reports waiting normally. - -After each turn, background work has at most 30 minutes to finish. At that limit, shell jobs are killed and the Ticket is reported waiting. The report includes the Executor's last words and which jobs were killed or subagents remain active. Subagents are allowed to continue. The Executor reads killed-job results on its next turn. - -A handoff to a person frees the slot immediately and leaves the Report in effect. This applies after a `ready` Report or a question, unless another Input arrived since, and after any `blocked` or `failed` Report. - -At handoff, subagents stop. Shell jobs, such as a server offered for review, may continue until the Assignment ends. They hold no slot and are exempt from the 30-minute limit, including after later turns. Their completion does not wake the Executor; it reads the results on its next turn. - -A person can also start a turn by writing directly in the Executor Session. Its background work follows the same rules. - -When an Assignment ends, its shell jobs are killed without waking the Session. Its subagents are released, and any completion turns they wake are dropped. The replacement Assignment and its waiting Inputs start after that shutdown finishes. - -Stopping or restarting the plugin inside the DeepSeek Harness process uses the same shutdown path. Inputs not yet taken into Session history are retained. Shutdown waits up to 10 seconds for each worker's background work; a process restart ends that work outright. An Assignment interrupted while busy restarts with a recovery note. - -Worker subagents, including nested ones, use DeepSeek Harness's `standard` preset with the worker's permissions. They retain only the worker's allowed work tools, and lose those tools when the Ticket ends. Under `standard`, a shell command moves to the background after 60 seconds. A subagent's jobs are killed when it ends. - -### Preparing a Ticket directory - -Before the first Assignment, if the Ticket directory does not exist, the prepare command runs once with `sh` in the execution root. It has ten minutes to finish. Without a prepare command, the worker starts in an empty directory. - -A failed command fails the Ticket and records its output. A retry or a person's message on one of the Ticket's Threads tries preparation again. A typical command uses `git worktree` to create a checkout. A pull request belongs to the Ticket when its branch's last path segment matches the Ticket's short name. - -### Concurrency - -A Ticket holds a worker slot from the start of a launch, including preparation, or from the delivery that wakes its idle Executor. It also holds the slot throughout an active turn. - -The configured concurrency limit applies to these slots, with one extra slot for a person's message or a status request. Such a wake can use the extra slot when normal slots are full, and takes the next free slot before a new launch. New work therefore cannot prevent a person from continuing an existing Ticket. - -Other Inputs, such as CI results, wait for a normal slot. A launch waits in `working / queued` until a slot is free. Launches run concurrently, so a slow prepare command delays only its own Ticket. - -### Execution variables - -The prepare command and Executor instructions receive the same execution variables. The command reads environment variables; the Executor reads their values and descriptions in its prompt. None contains credentials. - -| Variable | Registered by | Value | -| --- | --- | --- | -| `DSH_SUPERAGENT_DIR`, `DSH_SUPERAGENT_NAME`, `DSH_SUPERAGENT_TICKET` | Executor | The Ticket's directory, short name and id | -| `DSH_SUPERAGENT_BLOCKED_COMMANDS` | Executor | Commands the Executor may not run | -| `DSH_SUPERAGENT_GITHUB_REPOSITORY` | GitHub | The repository the Source watches, `owner/name` | -| `DSH_SUPERAGENT_GITHUB_ACCOUNT`, `DSH_SUPERAGENT_GITHUB_COMMIT_EMAIL` | GitHub | The agent account and its commit email | -| `DSH_SUPERAGENT_GITHUB_NUMBER`, `DSH_SUPERAGENT_GITHUB_PR_HEAD` | GitHub | The Issue or pull request that started the Ticket, and its head ref when it is a pull request | -| `DSH_SUPERAGENT_FEISHU_BOT`, `DSH_SUPERAGENT_FEISHU_CHAT` | Feishu | The bot, and the chat that started the Ticket | - -Ticket-specific values come from its directory and originating Trigger. Other values come from the current deployment settings. Only the panel can change execution environment settings; text sent to the main Session cannot change the configured shell command. - -Workers reach GitHub through the agent account's `gh` and Feishu through the bot's `lark-cli`. Blocked commands fail when invoked by name. - -These wrappers and directories establish defaults, not a security boundary. A worker can still execute a program by absolute path. - -## Merging - -Auto-merge is governed by the **Delivery requirements** prompt. The host has no merge-specific decision logic. - -By default, the Executor never enables auto-merge as the agent account: GitHub attributes the merge to whoever enabled it. Delivery requirements must name another account's CLI for merging. Without one, people merge the pull request. - -The default instructions allow auto-merge only for a pull request opened for the Ticket, when it is ready, the required approvals cover the head commit, and additions plus deletions total fewer than 200 lines. Before pushing another revision, the Executor withdraws auto-merge. - -Reviews without a body, such as approvals, also arrive as Triggers and can wake an Executor waiting for review. These instructions govern the agent, not GitHub's merge rules. To enforce approvals, require them in branch protection and dismiss stale approvals. Omit the merge CLI or remove the auto-merge paragraph to require manual merging. - -## How a Ticket ends - -- **Ready without a pull request:** a `ready` Report ends the Ticket as completed. -- **Nothing to do:** an Executor that finds no work for a person to act on, such as a request that does not reproduce or is superseded, reports `dropped`. The Ticket ends without a message, unless a matched rule asks for one, and a person's later message on its Threads resumes it. -### Pull requests - -A Ticket follows the pull request it started from once its Executor reports it. It also follows each pull request opened from its branch once reported by the Executor or found by the GitHub Source. Reopened pull requests are followed again. - -The Ticket is done when none of the pull requests it opened remain open. If it opened none, all pull requests it follows must be closed. It then ends with the outcome of the last relevant close and keeps its Subscriptions, allowing a later human comment to resume it. - -A close does not end the Ticket while resumed, retried or recovered work is unfinished. It also waits if the Executor acknowledged or answered a follow-up it owns but has not since made a non-progress Report or ended a turn after reading that message. A later `ready` Report ends the Ticket once the same pull-request completion rule is satisfied. - -Chat Channels receive one final notice about the pull-request outcome. GitHub already displays the outcome on the pull request itself. Removing a Ticket's Subscription to a pull request also stops it following that pull request. - -"Awaiting review" therefore means a `ready` Report with a pull request. - -### Follow-ups and context Threads - -The Executor decides human follow-ups on its Ticket's own Threads. It handles in-scope requests and hands back requests beyond its scope or for another Ticket. Once no Executor owns the message, it becomes a candidate for the main Session. The main Session may otherwise act on the follow-up only to stop work at the person's request. - -An Input counts as read after the Session takes it into a step and subsequently reports or ends a turn. When a Ticket ends, unread messages that were not acknowledged, answered or handed back return to Eval, as described in [002 Authorization](002-authorization.md). Read messages left without action, such as thanks, do not. - -Threads followed only for context still deliver messages, but do not give the Ticket ownership of human follow-ups or resume ended Tickets. Those messages can be assessed independently. - -Sources poll subscribed Threads while a live Ticket subscribes, and for two weeks after the later of the last Trigger and the subscribing Tickets' end. Afterwards, messages arrive only through ordinary intake: GitHub notifications or Feishu live events. - -## Failure and recovery - -### Executor blocked or failed - -When an Executor Session reports `blocked` or `failed`, or fails to start or errors, which records `failed`, the Ticket takes no automatic Input. - -A passing failure of the model provider or the network, an HTTP 408, 429 or 5xx or a refused, reset or timed-out connection, is tried again first, three times with a growing backoff: a launch runs again, and a worker whose turn failed is told to go on; a failed prepare command is not tried again. - -Once failed, CI and bot messages are recorded on it, but they are not delivered and do not wake the Session. - -Its tools stay available, so a person can continue in the Session directly. - -A launch that failed is not tried again on its own. - -A person's message, a later Report that is neither `blocked` nor `failed`, or a retry ends the hold. - -It is worked out from the stored Reports and Inputs, so a restart keeps it. -### Retry - -Available while an open Ticket is in `person / delivery`, `person / blocked` or `person / failed`: a new Assignment, which starts its worker again and takes the Inputs that wait, is the remedy for each. - -Once a person's message reached a blocked or failed Ticket, the Executor has the message, so the Ticket shows as working, or as queued when a failed launch starts again, and offers no retry, unless delivering that message failed. - -A retry ends the current Assignment and starts a new one in the same Executor Session, directory, branch and pull request. - -The Executor model applies only when a Session is created; an existing Session keeps its model. -### Restart - -Running Assignments end with "host restarted" and new ones start with a recovery note that tells the Executor to check its directory, git history and GitHub before repeating any outside action, oldest Ticket first. - -Assignments that are waiting, ready, blocked or failed stay as they are, unless an Input reached them after that Report, in which case they start again too. - -Sessions, directories, Subscriptions, pending messages and the stored work below survive a restart, and so does the record of which messages each Executor Session took into its history, which a step does once it claimed them and before its model call: a message taken by a turn the restart cut off counts as read once a Report or the end of a turn follows, while one a step claimed but a cancel or error cut off before the history counts as unread. - -A retry or a restart gives the new Assignment again the messages delivered to the Assignment it replaces that its Executor Session did not take, which a cancel or a cut-off step may have dropped; a copy still waiting in the Session, kept by a restart or persisted across a crash, is taken out before the new Assignment's first message, so the new delivery is the only copy. -### Stored work - -What dsh-superagent does after a decision, such as relaying a candidate, a follow-up or a report to the main Session, launching an Assignment or delivering an Input, is stored first and done afterwards. - -A relay or a delivery that fails, such as when its Session cannot be reached, is retried after 2 seconds, then after a delay that doubles up to a minute; a launch that fails fails its Ticket, unless the failure was a passing one, as above. - -Each kind of work reports an error among the overview's system problems only after one item failed three times in a row; a failed Input delivery shows on its Ticket instead. - -Relays to the main Session keep their order, and so do notices; a failed one holds back the later ones of its kind. -### Source failure - -The GitHub Source reads each Issue, pull request and Thread on its own: a failed read keeps its error on its Thread, is read again on the next poll, and counts towards an error of the Source, which it reports after three failed polls in a row by default; any successful poll resets the count. - -GitHub answers 404 also when the agent lost access to a private repository, so a 404 is retried. - -A notified Issue or pull request whose reads keep failing is dropped a day after its first failed read, and at once when GitHub reports it as deleted; it then no longer fails the poll, its Thread keeps the error, and the Source's state names it. - -Its read position stays where it was, so a later notification or a poll of its Thread reads what the failed reads missed. - -The Feishu event consumer reports every failure, and polling of subscribed topics keeps each failure on its Thread. - -A malformed item in a GitHub list or a Feishu page is skipped, and the Source's state counts it. -### Channel failure - -Outgoing messages are stored and sent as stored work, retried with backoff from 5 seconds, doubling up to 300. - -A Ticket's messages to one conversation go out in the order they were queued, so its acknowledgement comes before its later answer or result: a message waits while an earlier one of the same Ticket to the same conversation has not gone out, until that one has failed three times in a row and shows as a problem. - -Messages of other Tickets, and messages of no Ticket, do not wait for it. - -A message with failed attempts is a system problem until it goes out or is withdrawn. - -A message is dropped only when its Source no longer listens to the conversation, or when the Source answers that the conversation can never receive it; while its Source is off, it waits. - -The Sources answer that for a deleted Issue or a locked conversation on GitHub, and for a recalled message or one Feishu no longer finds on Feishu. - -A reply to a review comment that no longer exists goes to the pull request's conversation instead. - -A dropped message keeps its record, with when and why it was dropped and the Ticket it was for, so it is never queued again; a dropped acknowledgement reads as dropped, not sent. -### Pause processing - -Stops dispatch, new Evals, Eval timeouts, Input delivery and replies; a launch already under way waits until processing resumes. - -Sources keep recording Triggers, and a running Executor turn is not interrupted. - -A person's message to an ended Ticket waits: the first pass after the pause resumes the Ticket, if its Mandate holds under the Policy then. - -An Eval in progress does not expire during a pause; its deadline moves later by the pause's length. -### Invalid configuration - -A stored Source configuration that its kind rejects disables that Source instance. diff --git a/docs/003-执行.md b/docs/003-执行.md new file mode 100644 index 0000000..1d30f1f --- /dev/null +++ b/docs/003-执行.md @@ -0,0 +1,203 @@ +# 003 执行 + +本文带着一份已接单的 Ticket 走完执行、交付与恢复。它建立在 [001 领域模型](001-领域模型.md) 与 [002 授权](002-授权.md) 之上。 + +## Ticket 阶段 + +每个 open Ticket 恰好处于一个阶段,由它在等什么决定。表从上往下读,第一条匹配的行生效。 + +| 阶段 | 条件 | 面板标签 | +| --- | --- | --- | +| person / delivery | 向 Executor Session 投递一个 Input 失败;2 秒后重试,之后延迟逐次翻倍、封顶一分钟;一次成功、一个新的 Assignment、或 Ticket 的结束都会清掉它 | Input undelivered | +| person / answer | 它有一个 Attention | Awaiting answer | +| person / review | 最近的 Report 是 `ready` | Awaiting review | +| person / blocked, failed | 最近的 Report 是 `blocked` 或 `failed`,且此后没有人的一条消息到达过该 Ticket | Blocked · Failed | +| working / queued | 没有 Assignment,或只有一个 pending 的 | Queued | +| idle | 最近的 Report 是 `waiting`,或 Assignment 已结束 | Waiting for a Trigger | +| working | 其余一切 | Working | + +`person` 阶段意味着这个 Ticket 没有人的输入就走不下去。一条新的人类消息会把 `blocked` 或 `failed` 的 Ticket 释放给它的 Executor,于是它进入下一条匹配的阶段,通常是 `working`。 + +一个 `idle` Ticket 结束了它的回合,没有悬而未决的问题。被订阅 Thread 上的一个 Trigger 可以让它继续。 + +## Executor + +每个 Ticket 得到一个名为 `worker-` 的 Executor Session,和一个以 Ticket 短名命名的目录。目录创建在配置的执行根之下,默认为 `/work`。 + +Executor Sessions 以操作者的 OS 用户身份免审批地运行命令。它们的 `dsh-superagent-worker` 预设包含干活所需的工具,并把前台 shell 命令在 5 秒后移入后台任务。这让一个 worker 能在下一个步骤接收新消息,而不是干等一条长命令跑完。 + +main Session 使用 `dsh-superagent-router`,带同样的 5 秒 shell 与 job 等待上限,以及一个更适合路由的更小工具集。Sessions 保留创建时选定的预设。Worker subagents 使用 DeepSeek Harness 的 `standard` 预设。 + +### 后台工作与 worker 槽位 + +当一个回合结束、还有 shell 任务或 subagents 在运行时,Ticket 保留它的 worker 槽位、维持 working。它不会被上报为等待,main Session 也不会收到 waiting 报告。一个完成的 job 或 subagent 带着它的结果、在同一个槽位里唤醒 Executor。一旦某个回合在没有后台工作的情况下结束,Executor 才照常上报 waiting。 + +每个回合之后,后台工作至多还有 30 分钟完成。到点时,shell 任务被杀掉,Ticket 被上报为 waiting。报告会带上 Executor 最后说的话、以及哪些 job 被杀、哪些 subagents 仍在活动。Subagents 允许继续运行。Executor 在它的下一个回合读取被杀 job 的结果。 + +向人的移交会立刻释放槽位,并让 Report 保持有效。这适用于一次 `ready` Report 或一个问题之后——除非此后又有一个 Input 到达——以及任何 `blocked` 或 `failed` Report 之后。 + +移交时,subagents 停止。Shell 任务——比如一个为评审而开着的服务器——可以继续到 Assignment 结束为止。它们不占槽位、也不受 30 分钟上限约束,包括在之后的回合里。它们的完成不唤醒 Executor;Executor 在下一个回合读它们的结果。 + +人也可以直接在 Executor Session 里写一条消息来发起一个回合。它的后台工作遵循同样的规则。 + +一个 Assignment 结束时,它的 shell 任务被杀掉且不唤醒 Session。它的 subagents 被释放,它们唤醒的任何完成回合都被丢弃。接替的 Assignment 及其等待中的 Inputs 在那次关闭完成之后才开始。 + +在 DeepSeek Harness 进程内停止或重启插件走同一条关闭路径。尚未进入 Session 历史的 Inputs 会被保留。关闭会为每个 worker 的后台工作至多等待 10 秒;一次进程重启会直接终结这些工作。一个在忙碌中被中断的 Assignment 会带着恢复备注重启。 + +Worker subagents(包括嵌套的)使用 DeepSeek Harness 的 `standard` 预设,并带着 worker 的权限。它们只保留 worker 允许的工作工具,并在 Ticket 结束时失去这些工具。`standard` 之下,一条 shell 命令在 60 秒后转入后台。一个 subagent 的 jobs 在它结束时被杀掉。 + +### 准备 Ticket 目录 + +在第一个 Assignment 之前,若 Ticket 目录不存在,prepare command 会在执行根用 `sh` 运行一次。它有十分钟完成。没有 prepare command 时,worker 在一个空目录里启动。 + +命令失败会让 Ticket 失败并记录它的输出。一次重试、或 Ticket 某个 Threads 上的一条人类消息,会再次尝试准备。一条典型的命令用 `git worktree` 创建一个 checkout。当一个分支的最后一段路径与 Ticket 的短名一致时,那个 pull request 就属于这个 Ticket。 + +### 并发 + +一个 Ticket 从一次启动(包括准备)开始、或从唤醒其空闲 Executor 的那次投递开始,持有 worker 槽位。在一个活跃回合的整个过程里,它也持有该槽位。 + +配置的并发上限作用于这些槽位,并为人类消息或状态请求多留一个额外槽位。这样的唤醒在普通槽位占满时可以使用额外槽位,并在新启动之前拿走下一个空槽。因此,新的工作永远不会妨碍一个人继续一个已有的 Ticket。 + +其他 Inputs——例如 CI 结果——等待普通槽位。一次启动在 `working / queued` 里等到有槽位空出。启动们并发运行,所以一条慢的 prepare command 只拖慢它自己的 Ticket。 + +### 执行变量 + +prepare command 与 Executor 指令收到同样的执行变量。命令读环境变量;Executor 在它的提示词里读这些变量的值与说明。其中没有凭据。 + +| 变量 | 注册方 | 值 | +| --- | --- | --- | +| `DSH_SUPERAGENT_DIR`、`DSH_SUPERAGENT_NAME`、`DSH_SUPERAGENT_TICKET` | Executor | Ticket 的目录、短名与 id | +| `DSH_SUPERAGENT_BLOCKED_COMMANDS` | Executor | Executor 不得运行的命令 | +| `DSH_SUPERAGENT_GITHUB_REPOSITORY` | GitHub | 该 Source 盯守的仓库,`owner/name` | +| `DSH_SUPERAGENT_GITHUB_ACCOUNT`、`DSH_SUPERAGENT_GITHUB_COMMIT_EMAIL` | GitHub | agent 账号及其 commit email | +| `DSH_SUPERAGENT_GITHUB_NUMBER`、`DSH_SUPERAGENT_GITHUB_PR_HEAD` | GitHub | 发起该 Ticket 的 Issue 或 pull request,以及它是 pull request 时的 head ref | +| `DSH_SUPERAGENT_FEISHU_BOT`、`DSH_SUPERAGENT_FEISHU_CHAT` | Feishu | 发起该 Ticket 的 bot 与 chat | + +Ticket 专属的值来自它的目录与发起它的 Trigger。其他值来自当前的部署设置。执行环境设置只有面板能改;发给 main Session 的文本改不了配置好的 shell 命令。 + +Workers 通过 agent 账号的 `gh` 访问 GitHub,通过 bot 的 `lark-cli` 访问 Feishu。被屏蔽的命令按名字调用时会失败。 + +这些包装器与目录立的是默认值,不是安全边界。worker 仍可以用绝对路径执行一个程序。 + +## 合并 + +Auto-merge 由 **Delivery requirements** 提示词管辖。host 没有合并专属的决策逻辑。 + +默认情况下,Executor 从不以 agent 账号开启 auto-merge:GitHub 会把合并记在开启它的人头上。Delivery requirements 必须点名另一个账号的 CLI 来合并。没有指定时,由人来合并 pull request。 + +默认指令只在这个 pull request 为该 Ticket 所开、已经就绪、所需批准覆盖了 head commit、且新增加删除合计少于 200 行时,允许 auto-merge。在推送下一个 revision 之前,Executor 先撤回 auto-merge。 + +没有正文的评审——例如批准——也作为 Triggers 到达,并能唤醒一个在等评审的 Executor。这些指令管的是 agent,不是 GitHub 的合并规则。要强制评审,就在分支保护里要求评审并让过期批准失效。想强制人工合并,就不配合并 CLI、或删掉 auto-merge 那一段。 + +## Ticket 如何结束 + +- **没有 pull request 的就绪:** 一次 `ready` Report 把 Ticket 以 completed 结束。 +- **无事可做:** 一个发现没有可供人行动的工作的 Executor——例如无法复现或已被取代的请求——上报 `dropped`。Ticket 静默结束,除非命中的规则要求说一声;此后其 Threads 上的人类消息会让它恢复。 +### Pull requests + +一个 Ticket 一旦它的 Executor 上报了它由此开始的那个 pull request,就开始跟踪它。它同样跟踪每个从其分支开出、并被 Executor 上报或被 GitHub Source 发现的 pull request。重新打开的 pull request 会被重新跟踪。 + +当它开出的 pull requests 没有一个还开着时,这个 Ticket 就完成了。若它一个都没开,则它跟踪的所有 pull requests 都必须已关闭。于是它带着最后一次相关关闭的结果结束,并保留它的 Subscriptions,让后来的评论能让它恢复。 + +在有被恢复、被重试或被找回的工作尚未完成时,一次关闭不会结束 Ticket。若 Executor 认领或答复过一条它认领的跟进、但读完那条消息之后既没有再发过一次非 progress 的 Report、也没有结束过一次回合,同样要等。之后的 `ready` Report 一旦满足同样的 pull-request 完成规则,就结束 Ticket。 + +Chat Channels 会收到一条关于 pull-request 结果的最终通知。GitHub 本来就会在 pull request 上显示结果。移除 Ticket 对某个 pull request 的 Subscription,也同时让它不再跟踪那个 pull request。 + +因此"Awaiting review"意味着一份带着 pull request 的 `ready` Report。 + +### 跟进与上下文 Threads + +Executor 决定其 Ticket 自己 Threads 上的人类跟进。它处理 scope 内的请求,把超出 scope 或属于另一个 Ticket 的请求交还。一旦没有 Executor 认领这条消息,它成为 main Session 的候选。否则 main Session 对跟进只能应人的要求去停止工作。 + +一个 Input 在 Session 把它纳入一个步骤、并且该步骤随后上报或结束回合之后,算作已读。Ticket 结束时,那些未读、未被认领、未答复、未交还的消息回到 Eval,如 [002 授权](002-授权.md) 所述。已读而未处理的,例如感谢,不会。 + +仅为上下文关注的 Threads 仍会投递消息,但不让 Ticket 拥有人类跟进的所有权,也不恢复 ended Tickets。那些消息可以被独立评估。 + +Sources 在一个活着的 Ticket 订阅期间轮询被订阅的 Thread,并在最后一个 Trigger 与订阅的 Tickets 结束两者中较晚者之后继续轮询两周。此后消息只经普通准入到达:GitHub notifications 或 Feishu 实时事件。 + +## 失败与恢复 + +### Executor 受阻或失败 + +当一个 Executor Session 上报 `blocked` 或 `failed`,或启动失败、出错(后者记录为 `failed`)时,Ticket 停止接受自动的 Input。 + +一次会过去的模型 provider 或网络失败——HTTP 408、429、5xx,或被拒、被重置、超时的连接——先重试:共三次、退避递增;一次启动会重新运行,一个回合失败的 worker 会被告知继续;一条失败的 prepare command 不再重试。 + +一旦失败,CI 与 bot 消息会记录在它身上,但不投递、也不唤醒 Session。 + +它的工具保持可用,所以人可以直接在 Session 里继续。 + +一次失败的启动不会自行重试。 + +一条人类消息、一次此后既非 `blocked` 也非 `failed` 的 Report、或一次重试,会解除这个扣留。 + +它是从存储的 Reports 与 Inputs 推算出来的,所以重启后仍在。 +### 重试 + +当 open Ticket 处于 `person / delivery`、`person / blocked` 或 `person / failed` 时可用:一个新的 Assignment——重启它的 worker、拿走那些等待中的 Inputs——分别是它们的补救。 + +一旦一条人类消息到达过 blocked 或 failed 的 Ticket,消息就已在 Executor 手里,于是 Ticket 显示为 working——failed 启动重新进行时显示为 queued——并不再提供重试,除非那条消息的投递失败了。 + +一次重试结束当前的 Assignment,并在同一个 Executor Session、目录、分支与 pull request 里开始一个新的。 + +Executor 模型只在创建 Session 时生效;已有的 Session 保持它的模型。 +### 重启 + +运行中的 Assignments 以 "host restarted" 结束,新的带着一条恢复备注启动,要求 Executor 在重复任何对外动作之前先检查它的目录、git 历史与 GitHub,最老的 Ticket 优先。 + +处于 waiting、ready、blocked 或 failed 的 Assignments 保持原样——除非那份 Report 之后有 Input 到达过它们,那种情况下它们也会重新启动。 + +Sessions、目录、Subscriptions、待处理的消息、以及下文的 stored work 都挺过重启;"每条消息被哪个 Executor Session 纳入了历史"的记录同样挺过重启——一个步骤在它认领消息之后、发起模型调用之前写这条记录:重启打断的那个回合纳入的消息,在随后出现一次 Report 或回合结束时算作已读;被一个步骤认领、但在写入历史之前被取消或错误打断的,算作未读。 + +一次重试或重启,会把曾投递给被替换的那个 Assignment、而其 Executor Session 未纳入的消息——取消或被中断的步骤可能丢掉的那些——重新交给新的 Assignment;一份仍在 Session 队列里等待、被重启保留、或跨崩溃持久化的副本,会在新 Assignment 的第一条消息之前被取走,让新的投递是唯一的一份。 +### Stored work + +dsh-superagent 在一个决定之后做的事——把一个候选、一条跟进或一份报告转交给 main Session,启动一个 Assignment,投递一个 Input——都先存储、后执行。 + +一次失败的转交或投递——例如它的 Session 联系不上——在 2 秒后重试,之后延迟逐次翻倍、封顶一分钟;一次失败的启动会失败它的 Ticket,除非那是一次会过去的失败,如上文。 + +每一种 work 只有在同一条目连续失败三次之后,才在 overview 的系统问题里报一个错;失败的 Input 投递改为显示在它的 Ticket 上。 + +向 main Session 的转交保持顺序,通知同样;一条失败会压住它后面同类的条目。 +### Source 失败 + +GitHub Source 逐个读取每个 Issue、pull request 与 Thread:一次失败的读取把它的错误留在它的 Thread 上,下一次轮询重读,并计入 Source 的错误——默认连续三次轮询失败后才上报;任何一次成功的轮询会重置计数。 + +GitHub 在 agent 失去对一个私有仓库的访问权时也回答 404,所以 404 会被重试。 + +一个被通知的 Issue 或 pull request,若读取持续失败,会在首次失败读取的一天后被放弃;GitHub 报告它已删除时则立刻放弃。此后它不再让轮询失败,它的 Thread 保留着错误,Source 的状态会点名它。 + +它的读取位置停在原地,所以后来的通知或对它 Thread 的轮询会补上失败读取错过的内容。 + +Feishu 事件消费者报告每一次失败;对订阅话题的轮询把每次失败留在它的 Thread 上。 + +GitHub 列表或 Feishu 分页里的一个畸形条目会被跳过,Source 的状态为它计数。 +### Channel 失败 + +对外消息作为 stored work 存储并发送,从 5 秒开始退避重试、逐次翻倍、封顶 300 秒。 + +一个 Ticket 发往同一个对话的消息按排队的先后发出,所以它的认领回执先于它后来的答复或结果:只要同一 Ticket 发往同一对话的更前一条还没发出,当前这条就等着——直到那一条连续失败三次、显示为一个问题。 + +其他 Tickets 的消息、以及不属于任何 Ticket 的消息,不为它等待。 + +一条有失败尝试的消息,在发出或被撤回之前,是一个系统问题。 + +一条消息只有在它的 Source 不再监听那个对话时、或 Source 回答那个对话永远收不到它时才被丢弃;它的 Source 关闭期间,它等着。 + +GitHub 对已删除的 Issue 或被锁定的对话给出这样的回答;Feishu 对被撤回或已不存在的消息给出这样的回答。 + +对一条已不存在的 review comment 的答复,改发到那个 pull request 的对话里。 + +一条被丢弃的消息保留它的记录——丢弃的时间、原因、以及它本属于哪个 Ticket——因此它绝不会被重新排队;一次被丢弃的认领回执读作 dropped,而非 sent。 +### 暂停处理 + +停止派发、新的 Evals、Eval 超时、Input 投递与答复;已在进行中的启动会等到处理恢复。 + +Sources 继续记录 Triggers,正在运行的 Executor 回合不被打断。 + +发往 ended Ticket 的人类消息会等待:暂停解除后的第一个回合若该 Ticket 的 Mandate 按当时的 Policy 仍然成立,就恢复它。 + +一次进行中的 Eval 在暂停期间不过期;它的截止时间按暂停的时长顺延。 +### 无效配置 + +一份被其 kind 拒绝的、已存储的 Source 配置,会禁用那个 Source 实例。 diff --git a/docs/004-prompts.md b/docs/004-prompts.md deleted file mode 100644 index d53639a..0000000 --- a/docs/004-prompts.md +++ /dev/null @@ -1,96 +0,0 @@ -# 004 Prompts - -This document explains what the agents read, what a team can edit, and when changes take effect. It builds on [000 Philosophy](000-philosophy.md) through [003 Execution](003-execution.md). Prompt text is written in English. - -## What a team writes - -A team can edit three team sections, add skills, and override every built-in system-prompt part. Most defaults live under `prompts/` and `skills/`. Tool descriptions, notices and cards remain in code and cannot be edited through prompt settings. - -| Section | Holds | -| --- | --- | -| Communication | When to message people, how messages read, the line that begins every GitHub message (`This is by an automated agent, not a human` by default), how to handle reviews, and which ordinary accounts are review bots | -| Ticket scope | When a Ticket goes on and when it stops with a draft | -| Delivery requirements | Evidence in pull requests, the auto-merge condition with its line threshold, and the commit author; without one, the Executor commits as the agent account's no-reply address | -| Team skills | Knowledge the agents load when they need it, such as an `identities` skill that says which Feishu user is which GitHub account | - -The built-in text covers the roles, tool usage, host mechanics, authorization rules, and the GitHub and Feishu skills. It refers to the team sections by their headings instead of repeating them. - -### Built-in parts and templates - -Overrides use these keys: - -| Parts | Keys | -| --- | --- | -| Shared instructions and roles | `runtime/shared`, `runtime/orchestrator`, `runtime/worker` | -| Working instructions | `runtime/subscriptions`, `runtime/replies`, `runtime/dispatch`, `runtime/work`, `runtime/admission` | -| Handoff instructions | `runtime/handoff/orchestrator`, `runtime/handoff/worker` | -| Source skills | `skill/feishu`, `skill/github` | -| Generated sections | `runtime/environment`, `runtime/policy`, `runtime/policy/none` | - -The generated sections are editable templates. They must retain these placeholders: - -- `runtime/environment`: `{variables}`, filled with execution environment lines. -- `runtime/policy`: `{version}`, `{mode}` and `{rules}`, with one line per enabled rule. -- `runtime/policy/none`: `{version}` and `{mode}`, used when no rule is enabled. - -Editing a part changes the instructions the model receives. It does not change the gate, Mandates, tool checks or account wrappers, which remain enforced by code. - -On an explicit human request, the main Session can also save overrides with `orchestrator_prompts_save`. If a save omits an override's `base`, it keeps the saved base. A removed part already present in the saved configuration is dropped on save; any other unknown part is refused. - -An override records which built-in text it was based on. Upgrades preserve the team's text. If the default changes, the panel marks the part **Default updated** and shows the difference beside the team's text. The team can keep its text, use the new default or edit it. Parts without overrides automatically follow new defaults. - -The main Session can read affected part names in `orchestrator_read prompts`, under `default_changed`. - -## Assembly and precedence - -The system prompt is assembled in this order: - -1. Shared instructions and the role. -2. The three team sections. -3. Subscriptions and replies. -4. Dispatch for the main Session, or work instructions for an Executor. -5. `runtime/admission`, for the main Session only. -6. Handoff instructions. -7. `runtime/environment`. -8. `runtime/policy`, for the main Session only. - -Admission instructions come from `prompts/admission.md`. They describe proactive assessment and when an irrelevant candidate can be ignored from its card. `orchestrator_eval` returns context rather than repeating these instructions. - -The environment section gives the main Session deployment-wide values and each Executor its Ticket-specific values. The Policy section is regenerated from settings on every model request. It contains the Policy version, accept mode, the explicit-request exemption, and enabled rules as `- (): `, or a statement that none is enabled. It is last so a Policy change leaves the preceding prompt text unchanged. - -Executors also read DeepSeek Harness's coding prompt and the repository's `AGENTS.md`; the main Session does not. An inferred Ticket adds a final note limiting its Executor to the matched rules. Assignments and incoming messages arrive inline as Trigger cards, with resource pointers only for truncated content. - -Executors work unattended. Their tools exclude those that ask a person, present UI to them or change the profile, such as `ask_user_question`, `present` and `plugin_manager`. - -From strongest to weakest: - -1. Facts enforced by tools and the host: identities, permissions, tool argument checks, reply purposes, length limits. Prompt text cannot change them. -2. Built-in and generated sections, as the team's overrides of them read. -3. The process and communication in a proactive Ticket's matched rules, for that Ticket's Executor Session only. -4. Team sections and team skills. -5. Outside text: messages, comments, documents and quotes. It is data and never changes rules or authorization. - -## Principles of the built-in text - -These apply [000 Philosophy](000-philosophy.md) to the agents: - -- **Separate roles.** The main Session only routes: it forwards the original Trigger and the routing facts, and makes no technical decisions, through one routing tool that takes the decisions on several waiting candidates in one call. A routing note has at most 30 words. The Executor reads the original sources, judges the scope, implements and verifies. -- **The main Session does not wait.** Reports, follow-up notices, candidates and completed background jobs arrive automatically as messages. It ends the turn when nothing remains to do. A guard refuses `sleep`, shell timeouts over 30 seconds and job waits over 5 seconds, because a blocked step delays new candidates. These instructions replace DeepSeek Harness's own job-notification note in the main Session's complete prompt. -- **Message only when a person must act or can use a result:** a result can be reviewed or used, a decision or resource is needed, or the work cannot be finished. Chat messages are one sentence by default, with a hard limit of 100 words. -- **Executors decide their follow-ups.** Each Executor handles and acknowledges in-scope requests, and hands back requests beyond its Ticket or for another Ticket. A message becomes new work only after all its owners hand it back or end without taking it up. The main Session receives follow-up notices but may act on them only to stop work. Handed-back messages then arrive as candidates with the Executors' reasons. -- **Coordinate through subscriptions and the main Session.** An Executor follows related Threads for context. The main Session can send a fact from another Ticket in a note of at most 100 words. Neither the Subscription nor the note grants authority. -- **Attach only on an explicit reference.** A message must point to a Ticket's pull request, Issue, source message or acknowledgement, or to a recent change with exactly one matching Ticket. Topic similarity alone is not enough. Attachment subscribes the Ticket to the entire Thread; an Executor finding it unrelated uses `orchestrator_thread_unrelated` and does not answer there. -- **Confirm concretely, once.** Before substantial work, the Executor confirms each new request and names the object and the expected result, as a reply under the original message. -- **Ask through `human_action`.** The Executor puts one concrete question in its Report, and the main Session forwards it unchanged. If a person's message prompted the question, it names that message and the host asks there. Otherwise it asks in the Ticket's original conversation. Already authorized work can continue meanwhile. -- **Honest delivery.** Opening a pull request is not readiness; a draft is marked unfinished; a UI change without a real before screenshot is not ready. -- **A person's pause holds** across follow-ups, new Executor Sessions and restarts until a person lifts it. Automatic feedback never resumes paused work. -- **Examples only show format.** An example sentence is not a fact the agent may claim. -- **Handle reviews by outcome.** A fixed and verified comment is resolved silently; a declined one gets one sentence of reason. An automated reviewer's thread is resolved once it has an answer, and each automated reviewer gets at most three rounds per pull request. -- **Yield to duplicate work.** When a person's branch or pull request already contains the Ticket's change, the Executor closes its own pull request with one sentence pointing to theirs. - -## Versions - -- Each save creates an immutable version containing the full configuration, revision number, predecessor, timestamp and origin (panel or tool). Restoring a version or defaults creates another version. -- Saves name their base revision. A conflicting save is refused without discarding the draft. -- Sessions read the latest version on every model request, including later steps in the same turn. Saving does not restart, cancel or wake a Session. The host records the revisions each Session used; an Executor can read any revision it worked under. -- The deployment sets a total size limit, including overrides. Validation refuses unknown keys, reserved built-in skill names, unknown override parts and missing template placeholders. It never silently truncates text. An override identical to the default is removed so the part follows future defaults. diff --git a/docs/004-提示词.md b/docs/004-提示词.md new file mode 100644 index 0000000..e169217 --- /dev/null +++ b/docs/004-提示词.md @@ -0,0 +1,96 @@ +# 004 提示词 + +本文解释 agent 读到什么、团队能改什么、改动何时生效。它建立在 [000 理念](000-理念.md) 到 [003 执行](003-执行.md) 之上。提示词文本用英文书写。 + +## 团队写什么 + +团队可以编辑三个团队分节、添加技能(skills),并覆盖每一个内置系统提示词部件。多数默认文本在 `prompts/` 与 `skills/` 下。工具描述、通知与卡片留在代码里,不能通过提示词设置编辑。 + +| 分节 | 内容 | +| --- | --- | +| Communication | 何时给人发消息、消息读起来什么样、每条 GitHub 消息的开头固定句(默认是 `This is by an automated agent, not a human`)、如何处理 reviews、以及哪些普通账号是 review bots | +| Ticket scope | Ticket 何时继续、何时停在一份草案上 | +| Delivery requirements | pull request 里的证据、auto-merge 条件及其行数阈值、以及 commit 作者;不指定时,Executor 以 agent 账号的 no-reply 地址提交 | +| Team skills | agent 在需要时装载的知识,例如一个 `identities` 技能,写明哪个 Feishu 用户对应哪个 GitHub 账号 | + +内置文本覆盖角色、工具使用、host 机制、授权规则,以及 GitHub 和 Feishu 技能。它按标题引用团队分节,而不是重复它们。 + +### 内置部件与模板 + +覆盖使用这些键: + +| 部件 | 键 | +| --- | --- | +| 共享指令与角色 | `runtime/shared`、`runtime/orchestrator`、`runtime/worker` | +| 工作指令 | `runtime/subscriptions`、`runtime/replies`、`runtime/dispatch`、`runtime/work`、`runtime/admission` | +| 交接指令 | `runtime/handoff/orchestrator`、`runtime/handoff/worker` | +| Source 技能 | `skill/feishu`、`skill/github` | +| 生成节 | `runtime/environment`、`runtime/policy`、`runtime/policy/none` | + +生成节是可编辑的模板。它们必须保留这些占位符: + +- `runtime/environment`:`{variables}`,填充执行环境行。 +- `runtime/policy`:`{version}`、`{mode}` 与 `{rules}`,每条启用的规则占一行。 +- `runtime/policy/none`:`{version}` 与 `{mode}`,没有任何启用规则时使用。 + +编辑一个部件改变的是模型收到的指令,不改变 gate、Mandates、工具校验与账号包装器——它们仍由代码强制执行。 + +在显式的人类请求下,main Session 也可以用 `orchestrator_prompts_save` 保存覆盖。若某次保存省略了覆盖的 `base`,则保留已存的 base。已从配置中移除、但仍在已存设置里的部件在保存时被丢弃;其他未知部件会被拒绝。 + +一个覆盖会记录它基于哪份内置文本。升级保留团队的文本。若默认值变了,面板把该部件标为 **Default updated**,并在团队文本旁显示差异。团队可以保留自己的文本、采用新默认值、或继续编辑。没有覆盖的部件自动跟随新默认值。 + +main Session 可以在 `orchestrator_read prompts` 的 `default_changed` 下读到受影响的部件名。 + +## 拼装与优先级 + +系统提示词按这个顺序拼装: + +1. 共享指令与角色。 +2. 三个团队分节。 +3. Subscriptions 与 replies。 +4. main Session 用 dispatch,Executor 用 work instructions。 +5. `runtime/admission`,仅 main Session。 +6. 交接指令。 +7. `runtime/environment`。 +8. `runtime/policy`,仅 main Session。 + +准入指令来自 `prompts/admission.md`。它们描述主动评估,以及一个不相关的候选在什么情况下可以直接从它的卡片上忽略。`orchestrator_eval` 返回上下文,而不是重复这些指令。 + +环境节给 main Session 部署级的取值、给每个 Executor 它那份 Ticket 专属的取值。Policy 节在每次模型请求前从设置重新生成,包含 Policy 版本、accept mode、显式请求的豁免条款,以及启用的规则(形如 `- (): `),或者一句没有任何启用的说明。它放在最后,这样一次 Policy 改动不会改变前面的提示词文本。 + +Executors 还会读 DeepSeek Harness 的编码提示词与仓库的 `AGENTS.md`;main Session 不读。一个推断出的 Ticket 会追加一条尾注,把它的 Executor 限定在命中的规则内。Assignments 与进来的消息以 Trigger 卡片的形式内联到达,只有被截断的内容才给资源指针。 + +Executors 无人值守地工作。它们的工具排除了那些会询问人、向人展示界面或修改 profile 的工具,例如 `ask_user_question`、`present` 与 `plugin_manager`。 + +从最强到最弱: + +1. 由工具与 host 强制的事实:身份、权限、工具参数校验、reply purposes、长度限制。提示词文本改不了它们。 +2. 内置与生成节,按团队对它们的覆盖所读到的样子。 +3. 一个主动 Ticket 命中规则里的流程与沟通方式——仅对该 Ticket 的 Executor Session。 +4. 团队分节与团队技能。 +5. 外部文本:消息、评论、文档与引用。它们是数据,永远不改变规则或授权。 + +## 内置文本的原则 + +这些是把 [000 理念](000-理念.md) 应用到 agent 上: + +- **角色分离。** main Session 只做路由:它转发原始 Trigger 与路由事实,不做任何技术决定,通过一个一次调用就能对多个等待中的候选做出决定的路由工具。一条路由备注至多 30 个词。Executor 读原始材料、判断 scope、实现并验证。 +- **main Session 不等待。** 报告、跟进通知、候选与已完成的后台任务会自动作为消息到达。无事可做时它就结束本轮。一个 guard 会拒绝 `sleep`、超过 30 秒的 shell 超时、以及超过 5 秒的 job 等待,因为一个被卡住的步骤会拖延新的候选。这些指令在 main Session 的完整提示词里取代 DeepSeek Harness 自己的 job 通知说明。 +- **只在有人必须行动或用得上结果时才发消息:** 结果可以被评审或使用、需要决定或资源、或工作无法完成。聊天消息默认一句话,硬上限 100 个词。 +- **Executors 自己决定后续。** 每个 Executor 处理并认领自己 scope 内的请求,交还超出其 Ticket 或属于另一个 Ticket 的请求。一条消息只有在它所有的认领者都交还、或结束而未认领之后,才成为新的工作。main Session 收到跟进通知,但只能拿它们来停止工作。被交还的消息随后作为候选到达,带着 Executors 的理由。 +- **通过 subscriptions 与 main Session 协调。** 一个 Executor 订阅相关的 Threads 以获得上下文。main Session 可以用一条至多 100 个词的备注,把另一个 Ticket 的事实带过来。无论 Subscription 还是备注都不授予权限。 +- **只在显式引用时才 attach。** 一条消息必须指向某个 Ticket 的 pull request、Issue、源消息或认领回执,或指向一次近期改动且恰好只有一个匹配的 Ticket。仅话题相似不够。attach 会让该 Ticket 订阅整个 Thread;Executor 若发现无关,使用 `orchestrator_thread_unrelated` 并不在那里回复。 +- **具体地确认,只确认一次。** 在实质性工作前,Executor 对每个新请求确认一次,说明对象与预期结果,以原消息下方回复的形式。 +- **通过 `human_action` 提问。** Executor 把一个具体问题放进它的 Report,main Session 原样转发。若这个问题由某条人类消息引出,就点名那条消息,host 到那里去问;否则在 Ticket 的原始对话里问。已获授权的工作可在此期间继续。 +- **诚实交付。** 开 pull request 不等于就绪;草案标未完成;没有真实 before 截图的 UI 改动不算完成。 +- **人的 pause 会顶住。** 跨跟进、新的 Executor Sessions 与重启持续生效,直到人来解除。自动反馈永远不会恢复被暂停的工作。 +- **示例只展示格式。** 一个例句不是 agent 可以援引的事实。 +- **按结果处理 reviews。** 被修复并验证通过的评论静默 resolve;被拒绝的给一句话理由。自动化评审者的 thread 一旦有其答复即可 resolve,每个自动化评审者在一个 pull request 里至多三轮。 +- **为重复工作让路。** 当某人的分支或 pull request 已包含该 Ticket 的改动时,Executor 关闭自己的 pull request,用一句话指向对方的那份。 + +## 版本 + +- 每次保存创建一个不可变版本,包含完整配置、revision 号、前驱版本、时间戳与来源(面板或工具)。恢复某个版本或默认值会再创建一个版本。 +- 保存会点名其 base revision。冲突的保存被拒绝,且不丢弃草稿。 +- Sessions 在每次模型请求时读取最新版本,包括同一轮里的后续步骤。保存不重启、不取消、也不唤醒任何 Session。host 记录每个 Session 用到的 revisions;Executor 可以读取它工作期间的任何版本。 +- 部署设置一个含覆盖在内的总大小上限。校验拒绝未知键、被保留的内置技能名、未知覆盖部件与缺失的模板占位符,且从不静默截断文本。与默认值完全相同的覆盖会被移除,让该部件跟随将来的默认值。 diff --git a/docs/005-panel.md b/docs/005-panel.md deleted file mode 100644 index cffcac8..0000000 --- a/docs/005-panel.md +++ /dev/null @@ -1,58 +0,0 @@ -# 005 Panel - -Open **Superagent** in the DeepSeek Harness Web sidebar to supervise the agent. The panel prioritizes work that needs a person, then system health and history. This document builds on [000 Philosophy](000-philosophy.md) through [004 Prompts](004-prompts.md). - -## Three questions - -Opening the panel answers three questions, in this order: - -1. Does anything need me now? -2. Is the system healthy and doing the right things? -3. What is the history of one piece of work, from Trigger through Eval to Ticket? - -## Tabs - -The tabs follow the workflow: **Overview**, **Source**, **Trigger**, **Eval**, **Execution**, and **Settings**. Until repository and account setup succeeds, Overview shows what is missing and links to the relevant setting. - -| Tab | Shows | -| --- | --- | -| Overview | Tickets needing action, system problems, activity counts for the last 24 hours, and Executor load. Each block links to its detailed page | -| Source | Source state, setup problems and non-zero counts. Filtered Triggers and dropped messages cover the same 24-hour window as Overview | -| Trigger | Threads filtered by Subscribed, Evaluating, New Triggers in 24 h, or All Threads. Each Thread shows its Triggers newest first, Decisions and Subscriptions | -| Eval | Decisions with their reason and matched rules | -| Execution | Executor state, Pause processing, and Tickets filtered by Open, Pending, Working, Waiting for a Trigger or Ended. Ticket details show actions, authorization, waiting Inputs, Subscriptions and Assignments | -| Settings | Sources, Eval, Execution, Prompts and Deployment. Each group includes its save history | - -### Reading individual records - -A dropped message links to its Ticket, or to its Thread when it has no Ticket. The Source page explains whether its conversation disappeared or the Source stopped listening to it. - -A human follow-up still owned by an Executor is marked **Delivered to Ticket**, without an Eval. The Thread also shows hand-back reasons and context-only Subscriptions. **Evaluate again** can send that follow-up to Eval, or retry a Trigger with no effective judgement: one filtered by its Source, an unavailable Eval, or a delivered message with neither a non-stop Decision nor an Executor acknowledgement. - -Ticket details show the originating Trigger, Decision and reason, scope and Policy version. Assignments and secondary details are folded below the immediate actions. **Retry** is available for failed Input delivery or a Ticket waiting for a person after a blocked or failed Report. Ended Tickets show outcomes such as **Pull request merged** or **Stopped by a person**. - -## Settings groups - -- **Sources:** one card per Source with its switch, fields and verified account facts. GitHub also offers receive-all switches for new Issues, pull requests, comments and default-branch CI. Polling and read limits are under Advanced. -- **Eval:** the Policy version and rules, followed by **On each Source** and the Eval settings. Each Source tab contains its allowed list, membership sync, admission table and bot list. Sync runs at startup and on **Sync now**. The admission table distinguishes receiving scope, sender membership and whether the message addresses the agent. Eval settings select the accept mode, timeout and assessment model. -- **Execution:** worker model, concurrency, directory root, prepare command and blocked commands. Clicking an available variable inserts it into the prepare command. -- **Prompts:** **Edit** changes team sections, built-in parts and skills. **Full prompt** shows the assembled text and compares it with saved text or defaults. **History** lists versions. Edited built-in parts are marked changed and can be reset; templates list their required placeholders. - - An upgrade that changes an overridden default marks it **Default updated** (默认内容已更新). The panel compares the old and new defaults above the team's text. If the old default was not recorded, it compares the team's text with the new default instead. Choose **Keep my text** or **Use the new default**. Overrides for removed parts are listed below the tab bar and dropped on the next save. -- **Deployment:** startup problems with **Check again**, the state directory and machine limits. Saved limits apply without restart. The DeepSeek Harness Plugin Manager exposes the same page. - -## Search - -The header search finds Triggers by text, id, Feishu message id or a stored link, such as a GitHub comment URL. A Feishu link missing from the original message becomes searchable only after the host reads it during the current process lifetime. - -Search also finds Threads by references such as `#12` or a GitHub URL, and Tickets by title or a matching Thread. - -## Principles - -1. **Focus on what matters.** Every element answers one of the three questions, or it is removed. A number appears only when it changes what the supervisor does. -2. **Precise definitions.** Categories have checkable definitions, are mutually exclusive and cover every case, as the Ticket stages do. A label says whom a row waits for and what it needs, not an internal status code. -3. **One place per concept.** Source state belongs in Source; Threads and Triggers in Trigger; Decisions in Eval; Tickets in Execution. Settings are grouped by the concept they configure. Overview summarizes these pages and links to them instead of becoming another detailed view. -4. **Domain structure.** The panel uses the domain terms. Internal identifiers appear only in folded details, and names replace identifiers where they can be resolved. -5. **Honest state.** A count equals the length of the list it links to, and no state contradicts another fact. -6. **Quiet by default.** Healthy states take no extra space. Problems must be actionable; a one-off network error is not enough. Rows needing a person offer their action directly. Keep page explanations to one sentence and put further detail in the (?) tooltip. Destructive actions require confirmation. -7. **Consistent controls.** Use DeepSeek Harness design tokens and control behavior. The same information should look the same throughout the panel. Each settings block has one save bar, visible only while changes exist, a save runs or a save fails. diff --git a/docs/005-面板.md b/docs/005-面板.md new file mode 100644 index 0000000..3e2613c --- /dev/null +++ b/docs/005-面板.md @@ -0,0 +1,58 @@ +# 005 面板 + +在 DeepSeek Harness Web 侧边栏打开 **Superagent** 来监督 agent。面板优先显示需要人的工作,其次是系统健康与历史。本文建立在 [000 理念](000-理念.md) 到 [004 提示词](004-提示词.md) 之上。 + +## 三个问题 + +打开面板回答三个问题,按这个顺序: + +1. 现在有事需要我吗? +2. 系统健康吗,在做对的事吗? +3. 一件工作从 Trigger 经 Eval 到 Ticket 的历史是什么? + +## 标签页 + +标签页跟着工作流走:**Overview**、**Source**、**Trigger**、**Eval**、**Execution**、**Settings**。在仓库与账号设置成功之前,Overview 显示缺了什么,并链接到相关设置。 + +| 标签页 | 显示 | +| --- | --- | +| Overview | 需要处理的 Ticket、系统问题、最近 24 小时的活动计数、以及 Executor 负载。每个块链接到它的明细页 | +| Source | Source 状态、设置问题、以及非零计数。被过滤的 Triggers 与被丢弃的消息覆盖与 Overview 相同的 24 小时窗口 | +| Trigger | 按 Subscribed、Evaluating、New Triggers in 24 h 或 All Threads 过滤的 Threads。每个 Thread 按新到旧显示它的 Triggers、Decisions 与 Subscriptions | +| Eval | Decisions 及其理由与命中的规则 | +| Execution | Executor 状态、Pause 处理,以及按 Open、Pending、Working、Waiting for a Trigger、Ended 过滤的 Tickets。Ticket 详情显示 actions、授权、等待中的 Inputs、Subscriptions 与 Assignments | +| Settings | Sources、Eval、Execution、Prompts 与 Deployment。每个分组都带有自己的保存历史 | + +### 读单条记录 + +一条被丢弃的消息链接到它的 Ticket,没有 Ticket 时链接到它的 Thread。Source 页面说明是那个对话消失了,还是 Source 不再监听它。 + +仍由某个 Executor 认领的人类跟进会标为 **Delivered to Ticket**,不经过 Eval。Thread 上还会显示交还原因与仅收上下文的 Subscriptions。**Evaluate again** 可以把这条跟进送进 Eval,或重试一个从未得到有效判断的 Trigger:被其 Source 过滤掉的、Eval 不可用的、或已送达但既无非 stop 的 Decision 也无 Executor 认领的消息。 + +Ticket 详情显示发起的 Trigger、Decision 与理由、scope 和 Policy 版本。Assignments 与次要细节折叠在直接操作的下方。Input 投递失败、或一次被阻/失败的 Report 之后 Ticket 在等人时,可以 **Retry**。Ended Tickets 显示结果,例如 **Pull request merged** 或 **Stopped by a person**。 + +## Settings 分组 + +- **Sources:** 每个 Source 一张卡片,带它的开关、字段与已验证的账号事实。GitHub 还提供对 Issue、pull request、评论与默认分支 CI 全量接收的开关。轮询与读取上限在 Advanced 里。 +- **Eval:** Policy 版本与规则,然后是 **On each Source** 与 Eval 设置。每个 Source 标签里含它的允许名单、成员同步、准入表与 bot 名单。同步在启动时与点 **Sync now** 时运行。准入表区分接收范围、发送者成员身份、以及消息是否点名 agent。Eval 设置选择 accept mode、超时与评估模型。 +- **Execution:** worker 模型、并发、目录根、prepare command 与被屏蔽的命令。点一个可用变量会把它插入 prepare command。 +- **Prompts:** **Edit** 修改团队分节、内置部件与技能。**Full prompt** 显示拼装后的全文,并与已存文本或默认文本对比。**History** 列出各版本。编辑过的内置部件标为 changed 并可重置;模板列出其必需占位符。 + + 升级改动了某个被覆盖的默认值时,会标 **Default updated**(默认内容已更新)。面板在团队文本上方比较新旧默认值;若旧的默认值没被记录,则改为比较团队文本与新默认值。选 **Keep my text** 或 **Use the new default**。已移除部件的覆盖列在标签栏下方,下次保存时被丢弃。 +- **Deployment:** 启动问题与 **Check again**、状态目录与机器限制。保存的限制无需重启即生效。DeepSeek Harness 的 Plugin Manager 暴露同一页面。 + +## 搜索 + +头部搜索按文本、id、Feishu 消息 id 或已存的链接(例如一条 GitHub 评论的 URL)查找 Triggers。原始消息里缺失的 Feishu 链接,只有在当前进程存活期间被 host 读到之后才可搜。 + +搜索还能按 `#12` 或 GitHub URL 这类引用找 Threads,按标题或匹配 Thread 找 Tickets。 + +## 原则 + +1. **只关心要紧的。** 每个元素要么回答三个问题之一,要么删掉。一个数字只有当它改变监督者的动作时才出现。 +2. **定义精确。** 各类别有可核对的定义、互斥、且覆盖所有情形,像 Ticket 阶段那样。标签说这一行在等谁、需要什么,而不是内部状态码。 +3. **一个概念一个地方。** Source 状态归 Source;Threads 和 Triggers 归 Trigger;Decisions 归 Eval;Tickets 归 Execution。设置按它们配置的概念分组。Overview 汇总这些页面并链接过去,而不是变成又一个明细视图。 +4. **领域结构。** 面板使用领域术语。内部标识符只出现在折叠的细节里,能解析出名字的地方用名字代替标识符。 +5. **状态诚实。** 一个计数等于它链接去的列表的长度,且没有任何状态与其他事实矛盾。 +6. **默认安静。** 健康的状态不多占空间。问题必须可操作;一次偶发的网络错误不算。需要人的行直接就地提供它的动作。页面解释保持一句话,更多细节放(?)提示里。破坏性动作要确认。 +7. **控件一致。** 使用 DeepSeek Harness 的设计令牌与控件行为。同样的信息在整个面板里应当长得一样。每个设置分组只有一条保存栏,仅在存在改动、保存进行中或保存失败时可见。 diff --git a/docs/006-deployment.md b/docs/006-deployment.md deleted file mode 100644 index dcf4a1d..0000000 --- a/docs/006-deployment.md +++ /dev/null @@ -1,274 +0,0 @@ -# 006 Deployment - -Use this guide to install, configure and operate dsh-superagent. It covers accounts, startup checks, upgrades and troubleshooting, building on [001 Domain model](001-domain.md) through [005 Panel](005-panel.md). - -## What runs - -- **A dedicated DeepSeek Harness profile**, called `superagent` in this guide. Create it from the Web profile template. It loads the bundle and serves its panel on `127.0.0.1`. Keep it separate from the profile you work in: the plugin changes process-wide `PATH` and git settings, and its process needs independent restarts and upgrades. -- **An agent GitHub account.** The agent comments, pushes branches and opens pull requests as this account, through a GitHub CLI signed in only as that account. -- **Optional:** a Feishu bot for chat requests. - -Plugin configuration holds the state directory and machine limits. Use the panel for everything else: Sources for the repository and accounts, Policy for allowed lists, and Execution for worker directories and commands. - -### Permissions and isolation - -The bundle defines `dsh-superagent-worker` for Executors and `dsh-superagent-router` for the main Session. They appear in the DeepSeek Harness preset list but are intended only for dsh-superagent. Do not select them for personal Sessions or disable their configuration rows. The application needs them to create and resume its Sessions. - -Opening a blank managed Session in the Web UI can select the profile's default preset. dsh-superagent restores its role-specific preset before the first turn. - -Both presets run with `danger-full-access` as the operator's OS user and read messages from subscribed Threads. Account wrappers choose the agent's credentials by default; they do not isolate the process. A worker can execute programs by path and read the operator's home directory. For isolation, use a dedicated OS account containing only the agent's credentials. - -## Prerequisites - -- Node.js 22.19+ or 24+, pnpm, git 2.32+, Python 3.8+ and tmux -- GitHub CLI (`gh`) 2.48 or later. Source CLI fields must point to `gh` or `lark-cli` wrappers; the host relies on their subcommands and output formats. -- A repository on github.com; GitHub Enterprise Server is not supported. To create a checkout per Ticket, keep a local clone from which the prepare command can add worktrees. Install `git-lfs` if that repository uses Git LFS. -- A model provider configured for DeepSeek Harness. dsh-superagent uses the profile's default model unless the Eval or Execution settings name another (optionally with a reasoning effort); a reused Session is re-pointed to its role's configured model on the next dispatch. -- For Feishu: `lark-cli` (tested with 1.0.90) and a Feishu or Lark app with a bot - -## Accounts - -### Agent GitHub account - -1. Create a GitHub user account for the agent and give it write access to the repository. A GitHub App cannot be the agent, because dsh-superagent reads the account's notifications. -2. Give it a GitHub CLI that uses only its own credentials: - - ```sh - mkdir -p ~/.local/bin - cat > ~/.local/bin/gh-agent <<'EOF' - #!/bin/sh - GH_CONFIG_DIR="$HOME/.config/gh-agent" exec gh "$@" - EOF - chmod +x ~/.local/bin/gh-agent - gh-agent auth login - gh-agent api user --jq .login - ``` - - Sign in as the agent account; the last command prints its login. dsh-superagent reads the login the same way. - -### Feishu bot (optional) - -1. Create a self-built app with the bot capability. Grant it the permissions to receive message events, read and send chat messages, and read chat members, then publish the app version. -2. Give the bot its own `lark-cli` configuration, restricted to the bot identity, and a wrapper: - - ```sh - LARKSUITE_CLI_CONFIG_DIR="$HOME/.lark-cli-dsh-superagent" lark-cli config init --app-id --app-secret-stdin - LARKSUITE_CLI_CONFIG_DIR="$HOME/.lark-cli-dsh-superagent" lark-cli config strict-mode bot --global - cat > ~/.local/bin/lark-bot <<'EOF' - #!/bin/sh - export LARKSUITE_CLI_CONFIG_DIR="$HOME/.lark-cli-dsh-superagent" - exec lark-cli "$@" - EOF - chmod +x ~/.local/bin/lark-bot - ``` - - The first command reads the app secret from standard input. Use a separate configuration directory: `lark-cli` allows one profile per app id, and your personal configuration may already contain a user login for that app. - - The host refuses a CLI that can act as a user. It reads the app id, Feishu/Lark brand and bot `open_id` from the bot configuration. - - Both configurations share the keychain entry for the app secret. To remove only the bot configuration, delete its directory; do not run `lark-cli config remove`, which would affect the shared secret. - -## Install - -Run this block once, on a machine that has no `superagent` profile yet: - -```sh -npm install -g @deepseek-ai/dsh@0.2.1-rc.3 -git clone dsh-superagent && cd dsh-superagent -pnpm install && pnpm build -dsh superagent --from-default-profile web --dump-config > /dev/null -dsh plugin --profile superagent add "link:$PWD" -``` - -The pinned version above is public; other versions must satisfy `engines.dsh` in `package.json`. - -The fourth command creates `~/.dsh/profiles/superagent` from the DeepSeek Harness Web template. The fifth installs this bundle. Set `DSH_HOME` to relocate the default `~/.dsh` directory. - -Run the profile-creation command only for a new installation. If the profile already exists, that command replaces it and deletes its `cordis.patch.yml`, including machine configuration. Do not use it for upgrades. - -## Machine configuration - -Copy [examples/profile.cordis.patch.yml](../examples/profile.cordis.patch.yml) to `~/.dsh/profiles/superagent/cordis.patch.yml`, replacing that file, and replace every ``. For Feishu or the Gitea Source of this fork, also enable the `dsh-superagent-feishu` / `dsh-superagent-gitea` entry there. The `webserver` entry sets the panel's port. - -| Key | Required | Meaning | -| --- | --- | --- | -| `stateDir` | yes | Absolute path: the database, the lock, the command shims in `worker-bin`, the agent's git configuration and, by default, the Ticket directories in `work`. Back it up before upgrades | -| `promptMaxChars`, `admissionContextMaxPages`, `admissionContextTimeoutMs` | no | The largest saved prompt configuration, and how many Thread pages one assessment reads within what time | - -After the first start, edit machine limits in **Settings → Deployment** or the DeepSeek Harness Plugin Manager. Changes apply without restart. `stateDir` cannot be changed there. Invalid values or unknown configuration keys prevent startup; the log identifies the key. - -## Start and stop - -```sh -scripts/dsh-superagent start -scripts/dsh-superagent status -``` - -`start` runs `dsh --profile superagent` in tmux session `dsh-superagent`, or `dsh-` for another profile. It waits up to one minute for the runtime to become ready or report a setup requirement. On a first run it prints the missing setup information. It refuses duplicate instances, including ones started outside this script. - -`stop` and `restart` request graceful shutdown and wait up to five minutes for Executors. They never force-kill the process or touch other tmux sessions. - -`status` prints process ids, health and setup problems. For a different profile or port, use `--profile` and `--url`, or set `DSH_SUPERAGENT_PROFILE` and `DSH_SUPERAGENT_URL`. - -`status`, `start` and `restart` exit with one of four codes: - -| Code | Meaning | -| --- | --- | -| 0 | The service runs normally | -| 1 | It does not run, runs twice, does not answer, or its runtime reports an error; a restart may fix it | -| 2 | It runs, but a part other than the runtime, such as a Source or a Policy sync (`sync:`), reports an error; a restart does not fix it | -| 3 | It runs and waits for setup; a restart does not fix it | - -A Source that is still starting counts as normal operation. So does a Feishu bot that setup could not check. - -`status` returns failed parts and their details under `errors`, and setup requirements under `setup`. `start` and `restart` print those problems as lines; they exit 1 if the service never comes up. Only an intentional `stop` pauses automatic recovery. - -Each profile stores logs and control files under `.artifacts/`: - -- Startup logs: `dsh-superagent---