71 KiB
007 架构
这是实现层面的开发者指南。它把 001 领域模型 至 006 部署 的概念映射到类、接口与文件。先看 runtime 与 Engine,再把 module 表和 test 表当作代码索引使用。
Core classes and glue
Core 类拥有领域概念,并在小接口之后持有外部边界。Glue 把工具参数、HTTP 请求、Source 载荷与 DeepSeek Harness 事件翻译成对这些类的调用,再把结果变成卡片、视图和消息。
flowchart TB
plugins[Source plugins] -->|SourceContext| engine
tools[Session tools] --> engine
http[Management routes] --> engine
engine[Engine] -->|Repositories| store[Store]
engine -->|Policy| policy[Policy]
engine -->|SourceScope| registry[SourceRegistry]
engine -->|Assessor| assessor[MasterAssessor]
engine -->|Executor| executor[DshExecutor]
assessor -->|decide| engine
engine -->|Outbox| outbox[Outbox]
executor -->|assignmentStarted, inputsDelivered, report| engine
settings[Settings] -->|policyChanged| engine
assessor & executor & sessions[Sessions] & outbox -->|enqueue| effects[Effects]
effects --> store
Runtime(src/app/runtime.ts)构建应用、供给依赖,并拥有启动与关闭。Engine 构建它自己的内部部件。Executor 与 Assessor 会回调 Engine,所以 Runtime 给它们的是 getter,而不是要求按循环顺序构造。
Runtime
Runtime 是 composition root;它不含领域规则。它的构造函数锁住状态目录、打开 Store 并构建每个部件。若构造失败,它会关闭 Store 并释放锁。
启动注册 management 路由并运行 Setup。setup 解析完成后,它安装 Session 与 Executor 钩子、确保 main Session 存在、启动 Policy 同步、恢复被打断的 Assignments,并启动 passes 与 effects。ready 标记启动完成。
关闭取消回合、等待 passes、effects 与 agents、恢复进程环境、关闭 Store 并释放锁。Source 插件经由窄接口 SourceHost 把 Runtime 作为 orchestrator 服务注入。
| 部件 | 模块 | 做什么 |
|---|---|---|
| Setup | src/app/setup.ts |
按 worker shell 运行 CLI 的方式探测 CLI,由此解析仓库与账号,写出并应用 worker 的环境与命令垫片,并保留 setup 问题;Settings 经由它检查变更的账号,也经由它重启,而重启会再次运行 setup |
| Passes | src/app/passes.ts |
一次一趟,每两秒一次,以及每当有命令要求时:先到期 effects,再 Engine.evaluateDue;一个 Source 的 Trigger 经由它到达 Engine |
| Outbox | src/app/outbox.ts |
Engine 的 Outbox 端口,也是 receipts 的唯一写入者:send 在一个事务里保存消息的 receipt 并排队它的 reply effect,而该 effect 的 handler 经由 SourceRegistry.send 发送;消息在其 Source 停用时等待,在 conversation 已消失或不被监听时丢弃 |
| Notices | src/app/notices.ts |
把领域事件与 worker 活动转换为 main-Session 消息。eventNotice 处理领域通知;reportRelay 立刻转发提问、把普通进度留在其 Ticket 上、并转发其他报告;followupRelay 点名拥有某条人类跟进的那些 Tickets。在一个回合进行中途到达的 relay,会在下一步经 DeepSeek Harness inbox 一起投递 |
Engine
Engine(src/core/engine.ts)是 Triggers、Threads、Decisions、Tickets 与 Assignments 的唯一写入者。它从 src/core/ports.ts 接收这些端口:Repositories、一个当前-Policy getter、SourceScope、Assessor、Executor 与 Outbox。可选依赖是 prepareTicket、now 与 emit。
Engine 的所有方法都是同步的。每个命令在一个事务里改动它的聚合。evaluateDue 对它改动的每个 Eval 或 Ticket 使用单独的事务。端口在事务内 enqueue 外部工作;适配器在 commit 之后执行它们。非法的请求抛出 UserError。
Engine 对外暴露一个入口点,并委托给五个内部部件。它拥有每个命令的事务。跨部件的命令——如 attach、evaluateDue 与 policyChanged——按顺序调用各部件。
每个部件都继承 EnginePart(src/core/part.ts),后者提供选项与共享读取。部件之间的依赖单向流动:
| 部件 | 职责 | 依赖于 |
|---|---|---|
Intake |
一个 Source 的 Trigger:连同其处置结果存储一次、记录在其 Threads 及其 correlations 上、扇出到订阅的 Tickets,并且除非它是某条抵达了某个 Ticket 的人类消息,否则在其 primary Thread 上排队等待 Eval | Tickets |
Evals |
Threads 的 Evals:批处理、gate 给出的强度、actor 与依据以及给出它们的 Trigger(primary)、依据候选的投递而定的 deadline 及其因暂停而后移、过期 Eval 的重新排队、候选、重放与交回、Policy 变更时的重新排队,以及各自最终的 Decision:accept、stop、ignore 或 unavailable |
Tickets |
Tickets |
Ticket 生命周期:从一次 accept 创建及其 Mandate、Subscriptions 与首个 Assignment,它跟随的 Threads 及其关闭(followsDone),附着的 Threads 与为上下文而跟随的 Threads,Inputs 与 notes,resume 及 resume 拒绝的消息,retry、restart、stop 与 end,它丢弃的 Subscriptions,以及向它所涉 Tickets 发出的 Policy 变更通知 |
Replies |
Assignments |
Executor 及其 workers 上报的事:Assignment 已启动、Inputs 抵达或未能抵达、restart 打断了它,以及 worker 的 Report 及其给人类的提问 | Tickets, Replies |
Replies |
经 Outbox 写 Channel:确认、回答、结果与求关注消息,各自按允许它的规则 |
无 |
命令用祈使式命名,如 stopTicket;适配器上报的事实用过去式命名,如 assignmentStarted。查询 candidate 用名词。
调用方只拿到它们角色的 Engine Pick:EngineForSources、EngineForMainSession、EngineForPeople、EngineForSettings 或 EngineForExecutor。
| 角色 | 方法 | 效果 |
|---|---|---|
| Sources | receive(trigger) |
把一个 Trigger 存储一次并回答 stored、duplicate,或在没有 Source 接收它时 filtered;把它扇出到订阅其 Threads 的那些 Tickets——人类的消息除非处理处于暂停,会立刻 resume 一个已结束的 Ticket——并在其 primary Thread 上排队等待 Eval,除非它是某条抵达了某个 Ticket 的人类消息,那种情况它归属那个 Ticket |
| Sources | bindThread(ticketId, thread, own) |
让一个开着的 Ticket 跟随该 Source 的某个 Thread,例如某个 pull request 的;own 表示该 Ticket 开启了它。一个已关闭的 Thread 再次绑定时就重新开启。结束 Ticket 的是那些 Threads 的关闭,而不是 ready Report |
| Sources | threadClosed(thread, outcome, at) |
一个 Thread 关闭了:每个跟随它的开着 Ticket 记录这次关闭,并在它跟随完所有 Thread 时(followsDone)结束:它开启的 Thread 都不再开着;若它没开启任何 Thread,则它跟随的每个 Thread 都已关闭。它按这些关闭里最晚的那次以 merged 或 closed 结束——除非关闭之后恢复的工作还没有上报 ready,或它的 Executor 通过确认或回答,接管了某条它在其最新 progress 之外的 Report 之后、或其最后一个回合结束之时才决定的、属于人类的跟进;那样一条 dropped Report 会按那次关闭结束它 |
| Main Session | candidate(triggerId) |
持有某 Trigger 的那个 Eval,按 assessor 看到的样貌 |
| Main Session | candidateDelivered(evalId) |
该 Eval 的候选已抵达 main Session:它的 deadline 从现在起算 |
| Main Session | decide(triggerId, outcome) |
记录一条 Eval Decision,或为 explicit 请求记录一条直接 Decision。Accept 原子地创建 Ticket、Mandate、Assignment、Subscriptions 与确认。一个 inferred 候选若从其卡片上被 ignore,会在给定理由之前先记录 ignoredFromCard。只要 followupOwners 还拥有某条人类跟进,就只允许 stop |
| Main Session | attach(ticketId, triggerId, note?) |
把一个 Trigger 路由到它指向的那个 Ticket,其 Executor 随后把它当作跟进决定,并结束持有它的那个 Eval——该 Eval 的其他 Triggers 会等待一次新的 Eval。一个已结束的 Ticket 除非处理被暂停,会立刻在其 Mandate 下 resume。一条 Executor 仍在决定的跟进会被拒绝,一条被路由回当初交回它的那个 Ticket 的也会被拒绝 |
| Main Session | requestStatus(ticketId) |
请求一个正在等待的 Executor Session 报告状态,并回答是否已请求 |
| Main Session | note(ticketId, text) |
把来自其他工作的一条事实作为文本 Input 交给某个 Ticket 的 Executor,它不授予任何东西 |
| Main Session, Executor | reply(request) |
为一个 Channel 排队其用途点名的消息:acknowledgement、answer、result 或 attention |
| People | stopTicket(ticketId, reason?) |
来自人类的停止,它会撤回 Mandate |
| People | retryTicket(ticketId) |
结束这样一个 Ticket 停滞的 Assignment——它在 failed 或 blocked Report 之后正等待人类,或其 Input 投递失败(canRetry)——并启动一个新的 |
| People | reevaluate(triggerId) |
把一个已存储的 Trigger 排队等待一次新的 Eval |
| People | unsubscribe(thread) |
丢弃某个 Thread 上的所有 Subscriptions,跟随它的 Tickets 从此不再跟随它;每个 Ticket 继续决定它在那里收到的跟进 |
| People | dismiss(decisionId) |
解除某条 unavailable Decision 展示的问题,Decision 会记录这件事 |
| Executor | assignmentStarted(ticketId, assignmentId) |
一个 Assignment 正在运行;它等待中的 Inputs 发出 |
| Executor | inputsDelivered(ticketId, assignmentId, inputIds) |
Inputs 已抵达该 Assignment 的 Executor Session,各自等待一个回合步骤来领取 |
| Executor | inputsRead(ticketId, inputIds) |
Executor Session 已把这些 Input 所承载的消息纳入其历史(一个 user/message Session 事件),其模型由此拥有它们;一个领取了它们但在结束前被取消或失败的步骤不算。每条在其 Report 或回合结束之后计为已读,重启之后同样如此,其 Ticket 的结束也不会把它放行;一条在开始记录读取之前投递的 Input,在其投递之后出现过 Report 或回合结束时计为已读 |
| Executor | turnEnded(ticketId, assignmentId) |
Executor Session 的一个回合结束了——Report 之后同样,只要没有东西在后台为其运行、或在其交接给人类时。一个回合开始前领走的东西,即为已读(inputsRead) |
| Executor | deliveryFailed(ticketId, assignmentId, error) |
投递 Inputs 失败;Ticket 会展示它,直到一次投递成功、一个新 Assignment 开始或该 Ticket 结束 |
| Executor | report(ticketId, report) |
记录一条 Report 及其给人类的提问;dropped 结束该 Ticket,ready 完成一个不跟随任何 Thread 的 Ticket,并在一个 Ticket 跟随完其 Threads(它们均已结束)时结束它 |
| Executor | markUnrelated(ticketId, thread, reason) |
只丢弃该 Ticket 对一个被发现无关的附着 Thread 的 Subscription,并连同理由交回那里由该 Ticket 决定的跟进 |
| Executor | followForContext(ticketId, thread) |
为一个开着的 Ticket 为上下文订阅一个相关的 Thread:它的消息会到达该 Ticket,但不给它带来跟进强度,也不会 resume 它 |
| Executor | handback(ticketId, triggerId, reason) |
记录该 Ticket 交回一条抵达它的 Trigger,连同其候选携带的理由;若此前是本 Ticket 决定的,则在没有其他 Ticket 决定它时把它排队等待一次新的 Eval;它回答是否排了队。每个 Ticket 对每条 Trigger 只交回一次并保持其 Subscription |
| Executor | assignmentInterrupted(ticketId, assignmentId) |
host 停止时某个 Assignment 正在运行:一个在工作途中被打断,或一个在其最新 Report 之后收到过 Input 的,带着恢复备注和那些已投递给它但其 Executor Session 未领取的 Inputs 重新开始,如同 retry;一个在最新 Report 之后仍有上报的,保留其 Session。DshExecutor 在启动时逐一上报(resume),最旧的 Ticket 在前,并重新排队一个正在等待的 launch |
| Passes | evaluateDue() |
除非处理被暂停:为那些排队 Trigger 正在等待的 Threads 启动 Evals,每个 Thread 一个、其间排队的 Triggers 合为一批;把过期 Eval 的 Triggers 重新排队,连续两次,第三次记录为 unavailable;并 resume 暂停期间被人类消息抵达的已结束 Tickets——若它们的 Mandates 仍然成立。候选尚未抵达 main Session 的 Eval 不过期。处理暂停期间没有 Eval 会过期,恢复后每个 deadline 按暂停时长后移 |
| Settings | policyChanged(from, to, effects) |
告诉每个受变更影响的开着 Ticket 如何受影响(src/policy.ts 的 policyEffects:它据以接受的一条规则变了或不再适用、其请求者可能不再能启动工作、或主动工作不再被接受),并在新版本下重启进行中的 Evals |
Engine 让批处理、授权、扇出、resume 与求关注的生命周期规则保持私有。几个共享谓词让这些规则保持一致:
waitsForPerson(src/core/model.ts):最新的 Assignment 上报了blocked或failed,且此后没有人类消息到来。自动 Inputs 与失败的 launch 在此为真期间保持扣留。followupOwners:仍对其自有 Threads 上收到的人类跟进负责的 Tickets。一个 Ticket 可以处于 live 状态,或未经人类 stop 而结束并等待 resume。交回那条消息即终结那份所有权。settled:某条 Decision 已裁定该请求;stop、unavailable、routed 与 refused-stop 这些 Decision 不算。ticketStage(src/core/stage.ts):实现 003 执行 的阶段表。
当一个 Ticket 结束时,未读的跟进回到 Eval——除非另一个 Ticket 仍拥有它们,或别的处置结果已将其裁定。已确认、已回答与已交回的消息被排除。对于没有确认处置结果的旧记录,outbox receipt 提供该事实。一条最终 Report 还覆盖了 Assignment 开始时正在等待的消息,包括那条 resume 它的消息。使 Ticket 停止的那条消息不会被自动重判。
变更后,Engine 从 src/core/ports.ts 的 DomainEvents 发出一个事件:
trigger.received、eval.started、decision.recorded、ticket.created、ticket.resumed、ticket.ended、assignment.started、assignment.ended、thread.subscribed、thread.unsubscribed、trigger.requeued 或 policy.changed。
Runtime 把 ticket.ended、trigger.requeued 与 thread.unsubscribed 变成通知。投递到 Tickets 的人类 trigger.received 会变成一条跟进 relay。pullEndNotices 在一个 Ticket 以 merged 或 closed 结束时,使用一个稳定 id 发送一条 chat 通知。其他事件目前没有消费者。
Model
src/core/model.ts 持有领域类型,src/core/stage.ts 持有 Ticket 阶段。一条 Decision 把理由记录为 reason,即一个 Reason 列表(src/core/reasons.ts):每个是一个 code 及其 values,对于一批中的某个 Trigger,还带其 triggerId。
core 的代码——如 timeout、policyChanged、routed 与 startedTicket——在 REASONS 里,配有 agents 通过 reasonText 读取的英文;gate 或 Source scope 给出自己的代码,如 not_a_member;而 said 承载 assessor 或人类写下的话。面板按 reason.<code> 给每个代码措辞,Source 过滤掉的 Trigger 也以同样方式记录 Source 的代码。
一个 Ticket 把每个事实保存一次。它没有 ended 时就是开着的,it.ended 记录它何时结束、被谁、为何、以什么结局;它最后说过的是它最新的 Assignment Report(latestReport);其 Executor Session 以它命名(src/executor/dsh.ts 里的 workerSessionId)。
它的字段有类型:origin,即给出其 Eval 批次之强度的可准入 Trigger 及批次本身(triggerId、triggerIds);execution,Executor 选择的东西(name、directory 与 assignment 赋值文本);delivery,最近一次失败的 Input 投递(error、at),直到一次投递成功、一个新 Assignment 开始或该 Ticket 结束;以及 follows,Source 让它跟随的那些 Threads,每个 {thread, own, closed?}:Ticket 开启了它时 own 为真,其关闭后 closed 带 at 与 outcome。只有 Tickets 写 follows;GitHub Source 用 pullsOf 与 pullOf(src/sources/github-pull.ts)读取它的 pull requests。
一个承载 Trigger 的 Input 记录是否由人类所写,waitsForPerson 会读取。一个 Thread 还在 context 里列出只为上下文而跟随它的已订阅 Tickets,并在 expired 里计数其排队 Triggers 的 Evals 连续过期次数;一次 Evaluation 记录其 primary Trigger 及其候选被 delivered 的时间。
一个 Trigger 的处置结果记录它抵达过的 Tickets——经扇出或附着、人类的 requeue、每个交回它的 Ticket 及其理由,以及每个其 Executor 确认或回答过它的 Ticket。
一个 Mandate 的 basis(MandateBasis)记录 gate 当初如何准入它的 Trigger。重新校验会读取接收的 source、scope 与 row、名单里的 sender、命中的 rules,以及 via:
via |
准入依据 |
|---|---|
addressed |
一个名单里的发送者 @ 了智能体 |
followup |
一条消息到达了一个有 Tickets 的 Thread |
admission |
一个准入表单元格;observation 标记一个 inferred 候选 |
bot |
一个名单里的 bot 产出了 inferred 候选 |
Trigger 与 Thread 的 meta 属于 Source。core 不解读它们。
Policy
src/policy.ts 定义设置 schema、授权与研判规则。settingsPolicy(settings, scope) 每次使用都返回一个全新的、无状态的 Policy:
version、paused与evalTimeoutMs描述其当前状态。gate(trigger, thread, tickets)返回准入强度与依据。revalidate(mandate)检查一个既有 Mandate 是否仍成立。admitsInferred(rules)只在 auto 模式且有启用的匹配规则时允许主动接受。
assessmentOutcome(assessment, settings, strength) 把模型的研判转换为一个 Engine 结果。它要求每条启用的规则都有判断、确定的方向、一条匹配的规则与意图证据。auto 模式之外,一次 inferred 的 accept 会变成一条 “would accept” 记录。
policyEffects(tickets, old, now, scope) 找出一次变更影响哪些开着的 Tickets、如何影响。Engine.policyChanged 只通知那些 Tickets。policyVersion 对规则、accept 模式与各 Source 的 Policy 部分(含 bot 列表)做哈希。
Store
Store(src/app/store.ts)拥有一个 SQLite 数据库。Runtime 在打开它之前先取得目录锁,阻止第二个进程迁移或写入同一份状态。
它的接口按用途分组:
- 领域 repositories:
atomic,加上triggers、threads、tickets与decisions,各带get、save与列表查询。带索引的工作集是threads.active()、tickets.open()、tickets.active()与read.subscribedThreads。 - 读取查询:
read通过threadTriggers、triggerStats、search与droppedReplies等方法服务视图、工具与 Source 轮询。这些方法从不写入。 - Source 元数据:
threads.describe与threads.patchMeta更新 Source 拥有的 Thread 元数据。 - 设置:
settings、saveSettings、blockRevision与settingsHistory。设置与提示词使用编号的Versions(src/app/versions.ts)。 - Outbox receipts:
replyStatus与receipts,只由 Outbox 写入。Receipt 操作是get、waiting、behind、add、settle与withdraw。behind检查同一 Ticket 与 conversation 的一条更早消息是否在等待且失败未满三次。一条被撤回的消息以channelGone或channelIgnored结算。读取查询通过failingReplies、droppedReplies与droppedSince暴露失败与被丢弃的回复。 - 有类型的 key-value 记录:
get、set、delete与keys,使用来自KV的条目。
afterCommit 只在事务 commit 之后执行动作,rollback 之后绝不执行。Runtime 以存储形态读取记录;schema 11 迁移在启动时转换受支持的更旧记录。
Effects
Effects(src/app/effects.ts)是持久化的副作用队列。一个命令在其事务内调用 enqueue(kind, key, payload);执行只在 commit 之后开始。
每个 kind/key 对有一行。重复 enqueue 同一个处于等待的 key 会替换其负载并使其立刻到期,但不改变队列顺序。handle(kind, run, options) 为每个 kind 注册一个 handler。它按最旧优先处理到期行,至多到并发上限。
失败以指数退避重试。抛出 Later 会推迟工作而不计一次失败,例如没有可用的 worker 槽位时。start 在 setup 之后开始处理,drain 在关闭期间停止处理但保留待处理行,pending/idle 暴露队列状态。
| Kind | Handler | 做什么 | 同时 | 重试 |
|---|---|---|---|---|
assessment |
MasterAssessor | 把一个 Eval 的候选卡片引入 main Session 的回合,然后告诉 Engine 它已投递,由此启动该 Eval 的 deadline | 4,暂停时无 | 2 秒倍增到 60 秒 |
launch |
DshExecutor | 启动一个待处理的 Assignment:prepare command、Executor Session、赋值消息 | 空闲的 worker 槽位数,暂停时无 | 模型 provider 或网络的一次路过性失败在 5 秒后重跑,倍增到 300 秒,共三次;任何其他失败,以及失败的 prepare command,会使其 Ticket 失败。被暂停或被占满的 worker 槽位扣住的,5 秒后再试 |
delivery |
DshExecutor | 把一个 Ticket 等待中的 Inputs 按序交给它运行中的 Assignment | 4,暂停时无 | 2 秒倍增到 60 秒;等待 worker 槽位的 2 秒后再试 |
relay |
Sessions | 把一个 worker 的报告或提问、或一条人类跟进,引入 main Session 的回合 | 1,按队列顺序 | 2 秒倍增到 60 秒 |
notice |
Sessions | 把一个通知注入 main Session 而不唤醒它 | 1,按队列顺序 | 2 秒倍增到 60 秒 |
reply |
Outbox | 在一个 Ticket 发往其 conversation 的、尚未失败满三次的更早消息之后,经由其 Source 为一个 Channel 发送一条消息;撤回该消息会取消它 | 1,暂停时无 | 5 秒倍增到 300 秒;其 Source 停用的、或排在其更早消息之后的,5 秒后再试 |
每个 kind 以其名字在健康板上有一个条目。它在其某个 effect 连续失败三次后上报一个错误,delivery 除外——它的失败显示在其 Ticket 上。Relays 保持顺序,notices 也一样;一条失败的会扣住其后同 kind 的那些。
Sources
Sources 通过定义在 src/sources/kind.ts 的 SourceKind 扩展 host。一个 kind 声明它的身份、title、description、配置 schema 与 fields、派生的 facts,以及 health 条目。可选行为包括 scope、一个 channel(allowed、send、link)、threadContext、find、policy、environment 与 refreshLabels。
SourceRegistry(registry.ts)拥有已注册的 kinds 与已存储的实例,并实现 Engine 的 SourceScope。注册会在 HealthBoard 上认领健康条目;重复认领与保留的 sync: 名字会被拒绝。SourceContext 只通过其注册所认领的条目写入,因此注销之后的上报什么都不做。
Registry 的配置方法包括 kind、instances、instance、validate、validatePolicy、policyPart、channelAllowed、declarations、environments、find 与 scope。find 会询问每个支持搜索的 kind。
泛型代码经由 registry 访问 Source 行为。send(message, commands) 调用该 kind 的 Channel,返回 sent、gone,或在 kind 未安装时返回 waiting。link 解析其 Channel 链接。threadContext 把一个 kind 的读取器与其配置配成一对,或在 kind 没有读取器时不返回任何东西。无效的设置配置会禁用该实例。
插件收到 SourceHost:registry 与 context(kind)。kind 本身使用一个更窄的 SourceContext,由 src/app/source-context.ts 里的 sourceContextOf 构建。
那个 context 供给 deployment 与 commands、setup 就绪状态(ready、configured、problem)、配置、receive、atomic,以及有类型的 state。它还暴露 Thread 读取与元数据写入、Ticket 读取与绑定/关闭命令、Trigger 读取、经 Outbox 的 notify,以及为已声明健康条目准备的 report。
DshExecutor
DshExecutor(src/executor/dsh.ts)为每个 Ticket 提供一个 Session 与目录。start 与 deliver 排队 launch 与 delivery effects。stop 通过 Store.afterCommit 取消回合,于是一个被 rollback 的结束不会停掉 worker。
prepare(ticket, outcome) 提供 Engine 的 prepareTicket 钩子:短名、目录与 ticketExecution 赋值文本。workerSessionId(ticket.id) 派生 Session id。
WorkerSlots 在 launch 或会唤醒空闲 worker 的 delivery 之前预留容量。一条人类消息可以使用超出 maxWorkers 的一个槽位,且唤醒优先于 launch。activeCount 为面板提供负载。
暂时性的 provider 与网络故障获得三次重试。Launches 使用 effect 退避;失败的回合在 15、30 与 60 秒后收到继续消息。inputMessage 依据当前 Ticket 状态渲染每个 Input,mandateInstructions 为 inferred 工作追加匹配规则的限制。
install 订阅 Session 事件;dispose 退订。一个回合结束通常上报 waiting,除了就绪之后或需要人类行动时。错误上报 failed。启动时,resume 通过 assignmentInterrupted 上报被打断的 Assignments,并重新排队那些不等待人类的待处理 launches,最旧的 Ticket 在前。
MasterAssessor
MasterAssessor(src/app/assessment.ts)让 main Session 研判 proactive 候选。request 排队一个研判 effect;其 Eval 结束时 release 丢弃缓存状态。
它的 context、read 与 decide 方法服务于 orchestrator_eval、orchestrator_eval_read 与 orchestrator_eval_decide。它们通过 SourceRegistry.threadContext 提供有界的 Thread 上下文、repository 上下文与决定提交。一个没有上下文读取器的 kind 只提供 Trigger 与批次。
assessmentOutcome 为 Engine.decide 转换模型的回答。Evals 会依当前 Policy 再次核对 inferred 的接受。
Settings
Settings(src/app/settings.ts)读写带 revision 的设置 blocks。current 与 view 读取它们;save(block, value, revision, origin) 校验一次保存;pause(paused, origin) 更改独立的 paused 设置。
每次保存都声明其来源:panel、tool 或 sync。只有 panel 能更改执行环境或 SETUP_FIELDS(src/deployment.ts)里列出的字段。
一次 Policy 版本变更会计算 policyEffects、记录哪些 Tickets 被通知,并调用 Engine.policyChanged。syncable 与 sync(id) 刷新一个 Source 的 Policy 部分。refreshLabels(id) 刷新显示名。
Prompts
Prompts(src/app/prompts.ts)把配置存为 Versions。current、version 与 history 读取它们;validate、preview 与 save(value, baseRevision, source) 检查并保存它们。compose 与 skills 构建 Session 文本。
builtInParts 列出覆盖键、角色、默认文本与必需的模板占位符。每个覆盖保存团队 text 与一个 base:其原始默认的 builtInBase 键。保存时把那个默认值保留在 KV.prompts.builtIn 下。defaultChanged 随后报告变更的默认值、可用时的旧文本,以及被移除的部件。
校验会从已保存的覆盖继承一个省略的 base、丢弃已存在于保存数据中的被移除部件,并拒绝空白文本或其他未知键。这让保存工具能对它读到的一份配置做往返。environmentSection 与 policySection 只用自己的键做一次模板替换。没有覆盖的配置使用默认值。
Sessions
Sessions(src/app/sessions.ts)为两种角色管理 DeepSeek Harness Sessions。ensure 用一个角色的模型创建 Session。install 跟踪 agent 事件,并为受管 Sessions 注册提示词、技能与工具。
prompt 按请求 id 投递一次。relay 与 notify 排队由 relay 与 notice effects 处理的 main-Session 消息。src/app/notices.ts 定义其内容;delivery 检查一条消息是否已到达。
每次模型请求把 Session 绑定到当前的提示词 revision。它使用过的 revisions 会被持久保存:promptVersion 标识活动的那份,mayRead 限制 worker 能查看哪些版本。注册表按每个 agent 一个 map 跟踪。
status 与 promptVersion 服务于提示词页面。保存经由 Prompts.save 进行并通知 main Session。modelRetry 暴露模型预算重试状态。
Glue
| Glue | 翻译什么 |
|---|---|
src/app/runtime.ts |
composition root:用各部件要用的接口构建它们,注册路由与 Source host,并启动与停止 dsh-superagent |
src/app/reclaim.ts |
已结束 Tickets 的工作区目录,哪些可移除;其占用的磁盘,变成 overview 上的读数 |
src/app/notices.ts |
领域事件与 worker 报告,变成给 main Session 的通知与 relay |
src/app/tools/main.ts、src/app/tools/executor.ts |
main Session 与 Executor Sessions 的工具参数,变成对 core 类的调用,及其结果变成卡片 |
src/app/http.ts、src/app/views/、src/app/api.ts |
HTTP 请求变成调用,Store 读取变成面板的视图;api.ts 为面板给每条路由定类型 |
src/app/resources.ts、src/app/links.ts、src/app/cards.ts |
资源名变成读取,源消息变成链接,记录变成给模型的紧凑卡片 |
src/sources/github*.ts、src/sources/feishu*.ts、src/sources/poll.ts |
GitHub 与 Feishu 载荷变成 Triggers 与 Thread 事实,消息变成 CLI 调用 |
src/deployment.ts、src/setup.ts、src/infra/ |
Setup 探测与问题、CLI 传输、agent 的进程环境 |
Dependency rules
-
显式、窄的依赖。 每个类收到一个点名其所用内容的接口,大多做成其他类的
Pick类型:core 类的ExecutorHost、AssessorHost、SessionsHost、SettingsHost、SetupHost、PassesHost与OutboxHost,glue 的HttpHost、MainToolsHost、ExecutorToolsHost、ResourceHost、LinkHost与SourceContextDeps,Engine 则是调用它那个角色的Pick。Runtime 只在这样一个接口后面传自己。一个 Source 插件注入orchestrator服务,其类型把它命名为SourceHost:registry 与context(kind);其 kind 的代码只见其SourceContext。 -
谁能 import 谁。
src/core除 Node 的哈希之外什么都不 import 自自身之外,也不做 I/O。src/sources不从src/appimport 任何东西。面板只从 host 侧 import 类型,以及src/admission.ts、src/limits.ts、src/setup.ts、src/navigation.ts、src/sources/host.ts、src/core/errors.ts、src/core/reasons.ts的普通值和src/app/api.ts的过滤列表。 -
每种状态一个写入者。 每种状态有一个写入者,其余要么读它,要么请求那个写入者。
状态 写入者 Triggers、Threads、Decisions、Tickets、Assignments Engine 一个 Source 的读取位置,及它在其 Threads 上保存的 meta( label、url、facts及其自有键)那个 Source,经 SourceContext设置及其历史 Settings 提示词版本 Prompts Effect 行 Effects Outbox receipts Outbox:Engine 的消息与 Sources 的 notify都经它的send,其replyeffects 标记它们一个健康条目 在 HealthBoard(src/infra/health.ts)上认领它、并拒绝第二次认领的那个部件:runtime归 Runtime,Passes 经它上报,evals归 Runtime(每趟之后),models归 Sessions,每种 effect kind 归 Effects,worker归 DshExecutor,一个 Source kind 的条目归代表该 kind 之 Source 的 SourceRegistry,sync:<id>归 SettingsSource 只能通过调用 Engine 的
SourceContext.tickets.bindThread与threadClosed来改动一个 Ticket。 -
请求与事实。 索取某物的方法以祈使动词加其宾语命名,如
stopTicket或bindThread,可以被拒绝;记录适配器做过之事的方法以过去式命名,如assignmentStarted或deliveryFailed。每个 host 收到 Engine 的它所必需的那个角色类型。 -
有类型的字段,Source 数据放在 Source 名下。 代码读取 Tickets 与 Mandates 的有类型字段。Source 不在 Ticket 上保存任何东西:一个 Ticket 跟随它的哪些 Threads 是 Ticket 的
follows,而它关于某个 Trigger 或 Thread 所保存的在它们的meta里。 -
固定的拒绝。 一个人或一个 agent 能引起的拒绝是
src/core/errors.ts中来自USER_ERRORS的UserError(code, values)。management API 对missing代码回答 404、其他回答 400,面板以其语言为每个代码措辞。Setup 问题是SetupError代码(src/setup.ts)与固定的健康文本HealthCode(src/sources/host.ts),以同样的方式措辞。
Source 专属的依赖
一些泛型模块仍直接引用 GitHub 或 Feishu:
- Outbox 应用 Feishu 的 chat 字数上限。
src/app/links.ts解析 Feishu 消息链接与repliedTo。它与 Runtime 用feishuSettings读取设置。src/app/resources.ts读取feishu/与github/的原始记录;src/app/cards.ts用pullOf制作 Ticket 卡片。- Runtime 对 merged 或 closed 的 Tickets 发送
pullEndNotices。 - Executor 工具用
readPull与claimPull绑定上报的 pull requests,使用 Runtime 暴露的 GitHub 上下文。 - MasterAssessor 用
readRepositoryPath读取 repository 文件。 - Sessions 与 Prompts 只为 GitHub 与 Feishu 注册内建技能。迁移会转换它们的 schema-10 记录。
- Setup 只探测 GitHub 与 Feishu 账号字段(
SETUP_FIELDS)。只有那些 Sources 经由SourceContext.problem收到账号 setup 问题。
Modules
| Path | Responsibility |
|---|---|
src/index.ts |
The main plugin entry: reads the machine settings, starts the Runtime, and gives it the restart that a save of a Source's account uses |
src/deployment.ts |
The plugin Config and Machine; resolveDeployment and resolveFeishuBot read the repository and the accounts from the Source settings, each CLI run as Executor shells run it; SETUP_FIELDS lists the Source fields setup reads. The code names no person, account or organization |
src/setup.ts |
Setup problems: SetupError, with a code, the place that fixes it and the English text the log, the main Session and the panel share |
src/policy.ts |
The Policy: the settings schemas by block, the Policy version, the gate and Mandate revalidation |
src/admission.ts |
The admission table's values, each cell's options and defaults, shared with the panel |
src/limits.ts |
The limits of the values a person sets, which the settings schemas check and the panel's fields offer |
src/kv.ts |
KV: every family of kv records with its key, its value's type and its owner, and the Key and Family types the Store and SourceContext.state take |
src/navigation.ts |
Opens a DeepSeek Harness Session from the panel once the host's catalog lists it |
src/core/model.ts |
The domain types, among them the typed Ticket and MandateBasis, with threadKey, ticketOutcome, reached, followupOwners, settled, waitsForPerson, liveTicket, runningAssignment, latestAssignment and latestReport |
src/core/ports.ts |
The ports: Repositories, Policy, SourceScope, Assessor, Executor, Outbox, and the domain events |
src/core/engine.ts |
The Engine, the core's entry point, and the role interfaces EngineForSources, EngineForMainSession, EngineForPeople, EngineForSettings and EngineForExecutor |
src/core/part.ts |
EnginePart, the base of the Engine's parts, and EngineOptions |
src/core/intake.ts |
Intake: Triggers from Sources, to their Tickets and, unless a person's message reached one, to Eval |
src/core/evals.ts |
Evals: the Evals of Threads and their Decisions |
src/core/tickets.ts |
Tickets: the Ticket lifecycle, the Threads a Ticket follows, Subscriptions and Inputs |
src/core/assignments.ts |
Assignments: the Executor's facts and its workers' Reports |
src/core/replies.ts |
Replies: the Engine's Channel writes |
src/core/stage.ts |
ticketStage and canRetry: the Ticket stages of 003 执行 |
src/core/errors.ts |
UserError and USER_ERRORS |
src/core/reasons.ts |
Reason, REASONS and reasonText: why the core recorded a Decision or set a Trigger aside, by code |
src/core/ids.ts |
key: ids derived from their inputs, equal for equal inputs |
src/core/memory.ts |
MemoryRepositories: the Store's repositories in memory, in the Store's order and with its nested rollback, for domain tests |
src/core/index.ts |
The core's exports |
src/app/runtime.ts |
The Runtime, the composition root, with its start and stop |
src/app/setup.ts |
Setup, and setupChanged, whether a save changes a field setup reads |
src/app/passes.ts |
Passes, the pass scheduler |
src/app/reclaim.ts |
Reclaimer: hourly scans of the execution root that remove an Ended Ticket's directory once it outlives the retention, and the summary the overview reports |
src/app/outbox.ts |
The Outbox: the Engine's Outbox port, the receipts and the reply effects that send them |
src/app/notices.ts |
The main Session's notices and relays: eventNotice, reportRelay, resultRelay and followupRelay |
src/app/store.ts |
The Store, and Receipt, a message of the outbox |
src/app/store-read.ts |
storeReads: the Store's read queries for the views, the tools and the Sources' polls, which never write |
src/app/migration.ts |
The schema 11 migration: one step from schema 10 that writes every stored record in its current shape |
src/app/effects.ts |
Effects |
src/app/versions.ts |
Versions: a record kept as numbered snapshots with a pointer to the current one, for the settings and the prompts |
src/app/cache.ts |
TextCache: texts kept in memory within a size, the least recently used dropped first |
src/app/settings.ts |
Settings |
src/app/settings-history.ts |
policyHistory: each saved settings revision with what it changed from the one before |
src/app/prompts.ts |
Prompts, builtInParts, the built-in parts a team may override, and promptParts, the order of a role's prompt sections |
src/app/context.ts |
The built-in prompt text, read from prompts/, and the transcript entries Sessions read |
src/app/skill-files.ts |
Packaged text: promptFile, which joins a file's one-sentence lines into paragraphs, and the bundled skills |
src/app/sessions.ts |
Sessions |
src/app/delivery.ts |
AgentDelivery: delivers a message to a DeepSeek Harness agent once per request id, also across a restored Session |
src/app/retry-health.ts |
RetryHealth: which Sessions wait on a model budget retry, for the models health entry |
src/app/role-models.ts |
Resolves the Eval and Executor models: seeds a new Session with its role's model and re-points a reused Session whose durable route drifted |
src/app/default-model.ts |
switchDefaultModel: changes the deployment default model, optionally with a reasoning effort the catalog offers, through the Session Controller, which persists it into the profile |
src/app/assessment.ts |
The MasterAssessor, the assessment schema and the window orchestrator_eval answers |
src/app/execution-environment.ts |
The execution environment: its variables, a Ticket's values, the directory root and the lines of the prompt section, which src/app/prompts.ts fills into its template |
src/app/source-context.ts |
sourceContextOf: each Source kind's SourceContext |
src/app/links.ts |
sourceLink, the page of a Trigger's message, and candidateCard |
src/app/cards.ts |
Compact cards of Triggers, Tickets, Threads and Decisions for models, the closed set of ticket card states TICKET_STATES, and textPage |
src/app/resources.ts |
RESOURCES and readResource: what orchestrator_read reads, for each role |
src/app/tools/tool.ts |
The Tool type, readTool, the orchestrator_read of both roles, and AcknowledgementSchema |
src/app/tools/main.ts |
The main Session's tools |
src/app/tools/executor.ts |
An Executor Session's tools |
src/app/http.ts |
The /orchestrator/api routes: the same-origin check, request limits and one status per kind of failure |
src/app/api.ts |
Api, every route with its parameters, query, body and response, and the filters of the lists; it imports only types, so the panel bundles it |
src/app/views/text.ts |
The two forms of a view's text: preview, plain text on one line, and markdown without HTML; plain and readable |
src/app/views/records.ts |
The rows the views share: Triggers, Tickets, Threads, Decisions and Channel links, and ownersOf, the owners of follow-ups as one request reads them |
src/app/views/tickets.ts |
The Ticket lists by stage, a Ticket's page, the Mandate basis facts and the Executor's load |
src/app/views/threads.ts |
The Thread lists, a Thread's timeline, the search and the Decision list |
src/app/views/sources.ts |
The Source status: each Source instance's state, its last day's counts, and what it filtered and dropped in that day |
src/app/views/settings.ts |
The settings page's view: each block with its revision, each Source's settings with its kind's form, and the execution environment |
src/app/views/overview.ts |
The overview: Tickets that need a person, problems, the last day's flow and the Executor's load |
src/executor/dsh.ts |
The DshExecutor, WorkerSlots, ticketExecution, inputMessage and mandateInstructions |
src/executor/background.ts |
A worker's background work in DeepSeek Harness, the shell jobs its Session owns (ctx.jobs) and its subagents (ctx.subagents): runningJobs, untilQuiet, killJobs, collectJobs, stopSubagents, stopBackground, until, and cancelled, the workers whose turn a stop of their subagents cancelled |
src/infra/commands.ts |
Commands, the CLI transport: run; api and pages, limited to the configured repository and validated; lark as the bot; git. CliError carries the code a command's output reports, such as a GitHub HTTP status, and a command that prints nothing fails with how it ended, never with its arguments; validItems skips and counts the malformed items of a list |
src/infra/environment.ts |
The agent's process environment: command shims, the git configuration and credential helper, runAsWorker, and the checks of the environment and of a Ticket's directory |
src/infra/paths.ts |
The package's root, where packaged files are |
src/infra/text.ts |
Word counts and the chat message limit, fill and sameName |
src/infra/health.ts |
The HealthBoard, every part's health entry, each claimed by its one writer |
src/sources/kind.ts |
The SourceKind protocol |
src/sources/registry.ts |
SourceRegistry |
src/sources/host.ts |
SourceHost, SourceContext, Health, health and the fixed health texts |
src/sources/poll.ts |
What GitHub and Feishu polling share: POLL_HORIZON_MS, polled, cursorAt, and pollThreads, which keeps a failure on its Thread as pollError |
src/sources/github.ts |
The GitHub kind and plugin entry, exported as ./github-watch: a poll round of notifications, the pull requests of open Tickets with their CI, and subscribed Threads |
src/sources/github-trigger.ts |
GitHub comments, reviews, Issue bodies and CI changes as Triggers, and GitHub addresses |
src/sources/github-pull.ts |
The pull request rules: which pull requests a Ticket follows (claimPull, observePull), the closes it reports to the Engine, and pullEndNotices, the chat notice of a Ticket that ended merged or closed |
src/sources/github-channel.ts |
The GitHub Channel: comments and review thread replies, sent or gone |
src/sources/github-read.ts |
CI snapshots, repository reads, the github/ resources, and githubThreadContext: an Issue's or pull request's conversation by page |
src/sources/feishu.ts |
The Feishu kind and plugin entry, exported as ./feishu-watch |
src/sources/feishu-trigger.ts |
Feishu messages as Triggers, by one rule for live events, history pages and topic pages |
src/sources/feishu-loops.ts |
The Feishu loops: the event consumer, history recovery, chat names and polling of subscribed topics |
src/sources/feishu-channel.ts |
The Feishu Channel: topic replies, sent or gone |
src/sources/feishu-read.ts |
The feishu/ reads of a Trigger's message, its root, parent and topic, and feishuThreadContext, which gives them to assessments with their images |
src/sources/gitea.ts |
The Gitea kind and plugin entry, exported as ./gitea-watch: a poll round of notifications, the observed repository streams, and subscribed Threads |
src/sources/gitea-api.ts |
The minimal Gitea REST client: token auth from a named environment variable, zod-validated answers, pagination, an injectable fetch |
src/sources/gitea-trigger.ts |
Gitea Issue bodies, comments and reviews as Triggers, and Gitea addresses |
src/sources/gitea-channel.ts |
The Gitea Channel: Issue and PR comments, sent or gone |
src/sources/admission-context.ts |
The bounded window an assessment starts from: the first pages of a Trigger's Thread, read through its kind, and the messages around the Trigger |
src/sources/feishu-image.ts |
Reads a Feishu message's image as an attachment for the assessing Session |
src/client/index.tsx |
The client plugin entry: the sidebar entry, the main panel and the page in DeepSeek Harness's Plugin Manager |
src/client/app.tsx |
The panel: header, search, glossary, tabs and the page of the current route |
src/client/provider.tsx |
What every page reads from its host: copy, calls, navigation, notices, the state poll, and restart, which reads the state quickly while dsh-superagent restarts |
src/client/default-model.tsx |
The header's default-model switcher, and how a refused switch is worded |
src/client/api.ts |
Calls and polls of the management API, typed by src/app/api.ts, and the Decision filters of the Eval status |
src/client/route.ts |
The tabs, the settings groups, the typed routes that parseRoute reads, and the route memory with the settings group opened last |
src/client/overview.tsx |
The overview |
src/client/source.tsx, trigger.tsx, eval.tsx, execution.tsx |
One file per stage: its status, and its settings where it has some |
src/client/thread.tsx, ticket.tsx |
The record pages: a Thread with its timeline, and a Ticket with its authorization, Inputs and Assignments |
src/client/settings.tsx |
The Settings tab: the group navigation and the group on screen |
src/client/settings-kit.tsx |
The building blocks of settings blocks: the settings the stage groups share, useBlockDraft, the fields, among them a Source kind's, and the history of saves |
src/client/prompts.tsx |
The Prompts settings: the page owns the view it read and its editor the draft, and the Edit, Full prompt and History tabs render from them; a built-in part's editor shows a changed default and keeps or drops the override |
src/client/prompt-config.ts |
The prompt configuration by section, by the team sections, skill names, built-in parts (part/<key>, with withOverride) and part headings the prompt views give, and the line diff of the full prompt, as pure functions |
src/client/deployment.tsx |
The Deployment settings, also shown in DeepSeek Harness's Plugin Manager |
src/client/draft.tsx |
Settings drafts: draftReducer, their states and their merge with a newer saved revision, saveBarText, the save bar's line, and the save bar |
src/client/controls.tsx |
Local copies of DeepSeek Harness's controls, which follow its markup, keyboard behaviour and design tokens, since the bundle does not import @deepseek-ai/dsh-client-ui-primitives; they read no context |
src/client/widgets.tsx |
The small components the pages share, which read the panel's context: sections, page heads, load states, times, links, confirmed actions, the setup check and Markdown |
src/client/rows.tsx |
The rows the pages share: a Ticket, a Thread and a Decision, a Thread's chip, and the folding of runs of automated messages |
src/client/markdown.ts |
The Markdown subset of reports and questions, and the bare addresses in messages, as blocks and inline parts |
src/client/text.ts |
Copy by key and the wording of domain values, reasons, senders and times |
src/client/marks.ts |
The marks of the states the views classify: a Ticket's or a Thread's stage, a Source's state and a Decision's outcome, each as a dot, an icon, a tone and a label |
src/client/locales.ts |
All copy in both languages, and the glossary of 001 领域模型 |
src/client/client.css, css.d.ts |
The panel's stylesheet, loaded as text |
src/sources/github.ts、src/sources/feishu.ts 与 src/sources/gitea.ts 同时是三个 Source 插件的 entries,由 tsdown.config.ts 构建,并在 package.json 中以 ./github-watch、./feishu-watch 与 ./gitea-watch 导出。bundle 的 cordis.patch.yml insert 这四个插件,Feishu 与 Gitea 处于禁用状态。
Tests
| Test | Covers |
|---|---|
tests/core-engine.spec.ts |
Domain rules on in-memory repositories: receiving, Eval, fan-out, resume and stop, interrupted Assignments, the pause, Policy changes, reports and outcomes, attaching, replies, failed deliveries and dismissed problems |
tests/stage.spec.ts |
The Ticket stages, row by row |
tests/policy.spec.ts |
Source scope, the gate, the admission table, Mandate revalidation, the Policy version, settings migrations and assessment outcomes |
tests/registry.spec.ts |
SourceRegistry: instances, validation, Policy parts, Channels and Thread context |
tests/sources.spec.ts |
The Feishu and GitHub Sources on a SourceContext: translation, polls and read positions, the pull request rules, health |
tests/gitea.spec.ts |
The Gitea Source on a SourceContext against a fetched stub of the Gitea API: translation, notification and stream polls, cursors, the Channel, health |
tests/default-model.spec.ts |
The default-model switcher: the routes it refuses, the switch it makes through the Session Controller, and the wording of a refusal |
tests/commands.spec.ts |
CLI calls as the deployment's accounts, the Channels' answers, and repository-scoped GitHub calls |
tests/feishu-read.spec.ts, tests/github-read.spec.ts |
Feishu and GitHub reads |
tests/admission-context.spec.ts, tests/feishu-image.spec.ts |
The Thread window of assessments, and Feishu image reads |
tests/assessment.spec.ts |
What the MasterAssessor keeps per Eval, and when it lets go |
tests/background.spec.ts |
Waiting for a worker's shell jobs and subagents: an end that woke it, and a host that stops |
tests/bundle-patch.spec.ts |
The bundle's patch, read as DeepSeek Harness reads it, and the agent preset of Executor Sessions it defines |
tests/effects.spec.ts |
The effect queue: coalescing, limits, retries with backoff, health and shutdown |
tests/passes.spec.ts |
Passes: one at a time, the ones asked for during a pass run as one after it, and the runtime health a failed pass reports |
tests/outbox.spec.ts |
The Outbox on a running Runtime: messages wait while processing is paused or their Source is not installed |
tests/cache.spec.ts |
TextCache |
tests/queue.spec.ts |
The Runtime's work through the effect queue, and what earlier versions stored |
tests/followups.spec.ts |
A person's follow-up on a Ticket's Thread: its notice to the main Session, the tools that act on it, what a burst costs; a worker's hand-back and context follow, and the main Session's note |
tests/health.spec.ts |
One health entry per part, each cleared by its own next success |
tests/delivery.spec.ts |
AgentDelivery |
tests/retry-health.spec.ts, tests/role-models.spec.ts |
Model budget retries, and the models of the two roles |
tests/runtime.spec.ts |
The whole application on a simulated DeepSeek Harness host, through tools and HTTP |
tests/tools.spec.ts |
The Sessions' tools: what orchestrator_read reads for each role, and the shapes and checks of the other main Session tools |
tests/runtime-concurrency.spec.ts |
Work that overlaps: Triggers during a pass, Tickets that change during deliveries and launches |
tests/http.spec.ts |
The same-origin and loopback checks, the status of each kind of failure, the body limit, path parameters and queries |
tests/settings.spec.ts |
Settings saves that race a model or account check, a Policy sync that a save outran, and the refusals of the pause, a sync and a block |
tests/views.spec.ts |
What the management views derive from the Store, such as the Thread a dropped message belongs to, and the overview's liveness and workspace readouts |
tests/reclaim.spec.ts |
Which Ended Tickets' directories may be removed: the retention, the execution root, an open Ticket sharing one, and the end times that cannot be read |
tests/timeline.spec.ts |
A Thread's pages and a Trigger's neighbours by time and then id, with every received Trigger's time in one form |
tests/migration.spec.ts |
The schema 11 migration: every stored record in its current shape, and the refusal of older schemas |
tests/store.spec.ts |
Repositories and their working sets, in SQLite as in memory, the kv area, with every key of KV spelled only in src/kv.ts and the migration, settings history and queries |
tests/prompts.spec.ts |
Prompt validation, assembly and versions |
tests/cards.spec.ts |
The rules the built-in text must state, file by file |
tests/model-surface.spec.ts |
Everything models read: tools, prompts, skills and inputs, checked against the tool schemas, the execution environment and the glossary, within a size budget per role |
tests/deployment.spec.ts, tests/environment.spec.ts, tests/index.spec.ts |
The plugin configuration and setup, the agent's process environment, the plugin entry |
tests/service-script.spec.ts |
scripts/dsh-superagent against fake ps, tmux and dsh |
tests/client.spec.tsx, tests/navigation.spec.ts, tests/controls.spec.tsx |
The client plugin's registration and polling, Session navigation and route memory, the local controls, and the panel's imports, which have no cycle |
tests/panel-overview.spec.tsx, panel-source.spec.tsx, panel-trigger.spec.tsx, panel-eval.spec.tsx, panel-execution.spec.tsx |
Each page of the panel, against a fake management API answered by route |
tests/panel-settings.spec.tsx, panel-prompts.spec.tsx, panel-deployment.spec.tsx, panel-panel.spec.tsx |
The settings groups, drafts and history, the prompts, the Deployment page, and the panel's search, glossary and route memory |
tests/draft.spec.ts, tests/prompt-config.spec.ts, tests/markdown.spec.ts, tests/marks.spec.ts |
Draft states, merges and the save bar's line; the prompt configuration by section and the line diff; the Markdown subset and its links; the mark of every state the views classify |
tests/locales.spec.ts, tests/styles.spec.ts |
The copy in both languages, the stylesheet |
tests/class-docs.spec.ts |
Every class in src has a JSDoc block before it |
tests/docs.spec.ts |
Numbered documents link only to earlier ones, every local link resolves, no document has merge conflict markers, and this document's tables list every module and every test |
tests/fixtures/ 存放若干测试共享的东西:host.ts,一个模拟的 DeepSeek Harness host;panel-server.ts 与 panel.tsx,围绕一个页面的假 management API 与面板;deployment.ts,一个已解析的 deployment;git.ts,测试启动的每个 git 的环境,其中不点名任何 repository;以及 async.ts。
Persistence
一个 SQLite 文件,<stateDir>/orchestrator.sqlite,只有 Store 打开它。
| 表 | 存放 | 由谁写入 |
|---|---|---|
trigger |
每个 Trigger,其时间 at 用 Date#toISOString 给出的那一种形态,在 Engine 接收它时设置,因此时间按文本比较 |
Engine |
disposition |
每个 Trigger 的去向:filtered 带 Source 的理由代码、它的 Eval 与 Decision、它抵达过的 Tickets(经扇出或附着)、它是否被 requeue、交回它的 Tickets 及其理由,以及其 Executor 确认或回答过它的那些 |
Engine |
trigger_thread |
每个 Trigger 在哪些 Threads 上,带其时间与是否由人类所写,于是一个 Thread 的 Triggers 无需扫描所有 Trigger、按时间与随后 id 的顺序读取:一个 Thread 时间线的一页从 ` | ` 游标续读,共享同一时间的 Triggers 不会被跳过 |
thread |
Threads:Subscriptions、为上下文跟随它的 Tickets、排队的 Triggers、进行中的 Eval、其过期计数与 meta;生成列 active(有 Triggers 排队或 Eval 运行时置位)与 subscribed(有 Ticket 订阅它时是其 Source) |
Engine;Sources 写 meta |
ticket |
Tickets 及其 Mandate、origin、execution、Assignments、Channels、Inputs 与它们跟随的 Threads;生成列 open(未结束时置位)与 active(有 Inputs 未投递或最新 Assignment 处于 pending 或 running 时置位) |
Engine |
decision |
Decisions,按 Thread | Engine |
effect |
effect 队列:按 kind 与 key,其负载、其顺序、失败的尝试与最后的错误,以及其到期时间 | Effects;schema 11 迁移把早期版本为主 Session 保留的东西排队 |
outbox |
每条发往 Channel 的消息的 receipt,按 id,带它所属的 Ticket 与其 status——pending、sent 或 dropped——并保存以确保一个 id 不会被排队两次;对被丢弃的消息,何时与为何,即生成列 dropped、它的时间与一个理由代码。一条等待的消息有一个 reply effect,其持有它的尝试次数 |
Outbox |
kv |
其他所有记录,按 key | 点名它的那个部件 |
一个生成列由所在行的 JSON 计算得出、在其集合之外为空,且每个都有一层覆盖其集合的索引,因此一趟只读取活跃的工作。早于某列的数据库在 Store 打开它时获得该列。
KV(src/kv.ts)定义每个 key-value 记录的属主、key 与类型。所有读取者与写入者都使用这些条目;只有迁移代码点名更旧的 keys。
| 记录 | 键或内容 |
|---|---|
| Schema | 当前 schema 版本 |
| 设置版本 | settings-revision、settings-version:<n>、settings-block-revision:<block>;快照包含来源、Policy 版本与受影响的 Tickets |
| 提示词版本 | prompt-revision、prompt-version:<n> |
| Main Session | master-id |
| Session 提示词历史 | session-prompts:<session id> |
| Source 状态 | 读取位置,如 github-cursor:<number>、PR CI 指纹与已见过的 Feishu-topic 消息;keys 以 kind 或 thread- 加一个 Thread key 开头 |
| 显示名 | source-labels:<source id> |
Dismissal 保存在其 Decision 上。Input 文本在读取时从 Ticket 渲染,所以两者都不需要单独的 key-value 记录。
读取缓存使用有界的内存 TextCache 实例,重启即失。一个 Session 若要翻页浏览一份原始 Source 记录或提示词预览,重启后必须重读 offset 0。Feishu 链接在收单后解析并以同样方式缓存;基于链接的搜索只在当前进程存续期内读过那个链接之后才能找到它们。
还没有任何东西删除旧记录,所以这些会无界增长:trigger、disposition 与 decision 行;已结束的 Tickets 及其 Threads;outbox 的 receipts;以及 kv 里每次保存的设置 revision 与提示词版本、每个 Session 工作于其下的提示词 revisions,以及各 Sources 按 Thread、按 Issue 或 pull request 的记录。
SCHEMA_VERSION 只增不减。一个更新版本的数据库拒绝启动;src/app/migration.ts 在启动时把 pre-release schema 10 的数据库升级一次,更旧的则拒绝。
Model-facing interfaces
Main Session tools
main Session 有 14 个应用工具:
| 用途 | 工具 |
|---|---|
| 读取与路由 | orchestrator_read、orchestrator_route |
| 管理 Tickets | orchestrator_ticket_retry、orchestrator_ticket_request_status、orchestrator_ticket_note |
| 重新研判 | orchestrator_trigger_requeue |
| 研判主动工作 | orchestrator_eval、orchestrator_eval_read、orchestrator_eval_decide |
| 沟通与退订 | orchestrator_reply、orchestrator_thread_unsubscribe |
| 配置 | orchestrator_settings_save、orchestrator_policy_sync、orchestrator_prompts_save |
orchestrator_route 按序接受 1–20 条决定。每条 accept、attach、stop 或 ignore 作为独立的 Engine 命令运行。它还允许直接从一个 inferred 候选的卡片上 ignore 它。orchestrator_ticket_note 把一条事实作为上下文而非权威发送。
设置保存选择 policy、eval、executor、pause 或 source;最后那种还要点名其 Source id。带 preview: true 的提示词保存只组合文本而不保存。
main Session 还拥有 DeepSeek Harness 的 skill、bash、read、read_image、job_output、job_list 与 job_kill。避免读取代码是一条提示词规则,不是工具限制。
预设与等待上限
新的 main Sessions 使用 dsh-superagent-router(ROUTER_PRESET)。shell 命令与 job 等待在 5 秒后返回。若 Web UI 在第一个回合之前选了别的预设,Sessions 会恢复这个预设,worker 则恢复 WORKER_PRESET。
src/app/sessions.ts 里的 noWait guard 拒绝 main-Session 中位于命令位置的 sleep、timeoutMs 超过 30 秒的 shell,以及超过 5 秒的 job_output 等待。报告与 job 结果会自动到来,所以等待只会推迟即将到来的候选。
处于 standard 的既有 main Sessions 还需要为前台命令与 job 等待显式给出超时;否则该预设的默认值会把该步骤分别扣住 60 与 30 秒。host 经由 presetForSession 判定当前预设,回退到 Session 投影或头部,读不到值时假定 standard。
Executor tools
每个 Executor 有六个应用工具:
| 工具 | 用途 |
|---|---|
orchestrator_read |
读取对其自有 Ticket 可用的资源 |
orchestrator_ack |
确认一个请求 |
orchestrator_report |
报告进度或结果,按 pull-request 规则绑定一个 pull request |
orchestrator_thread_unrelated |
丢弃一个无关的附着 Thread,交回其拥有的跟进并告知 main Session 原因 |
orchestrator_thread_follow |
为上下文订阅一个相关的 Thread 并告知 main Session |
orchestrator_trigger_handback |
交回一条消息一次,带其候选承载的理由 |
一次 hand-back 在另一个 Ticket 仍拥有该消息时返回 queued: false,或本 Ticket 从未拥有过它。例如,一条 bot 消息独立地进入了 Eval。
worker 预设定义在 cordis.patch.yml 并经 WORKER_PRESET 选择。src/app/sessions.ts 里的 WORK_TOOLS 保留可用的文件、搜索、shell、job、web、skill、todo 与 subagent 工具。Workers 不会收到用于向人类提问或展示、更改 profile、在 worker 槽位之外排程工作、启动或给其他 Sessions 发消息、或编辑外部文档的工具。Ticket 结束后工具调用会被拒绝。
结果形状与研判上下文
路由结果使用 {decision?, reason, ticket?}。orchestrator_route 返回 {results},保持条目顺序并给每条加上其 trigger_id 与 kind。无效或被拒绝的条目返回 refused 与一个理由,而不中断整批。
设置与提示词保存返回被保存的配置。Policy 同步返回该 Source、允许名单的大小,以及 added 与 removed 的名字。
orchestrator_eval 返回上下文而不是指令。研判指令位于 runtime/admission(prompts/admission.md),规则位于 runtime/policy。它的上下文包括:
- Trigger 文本,至多 4,000 字符,带图片 ids 与批次里的其他 Triggers。
- 至多八个
related_tickets,开着的在前,以及tickets_total。 - 该 Eval 的
policy_version。 - 一个 Thread 窗口:前五条消息,以及 Trigger 两边各五条。消息使用
{id, from, at, text},通常限制在 400 字符,两侧相邻的两条为 1,200。被截断的消息包含chars;图片引用使用images。 - 其他已加载的消息作为 50 字符的
{id, from, preview}存根。orchestrator_eval_read按 id 展开它们。
Ticket 卡片使用 src/app/cards.ts 里封闭的 TICKET_STATES 集合:一个开着的阶段,如 person/review 或 working/queued,或一个结束的结局。确认语无论经 accept、研判还是 orchestrator_ack 提交,都限于 100 个词。
读取资源
RESOURCES 在 src/app/resources.ts 里定义这些资源并生成工具的 resource 描述。
| 资源 | 内容 |
|---|---|
tickets、triggers、threads、decisions |
分页列表,形如 {items, next_offset};活动列表把最新的放最前 |
decision/<id> |
一条 Decision |
thread/<key> |
标签、Tickets、最近的 Decisions 与一页 Triggers |
trigger/<id> |
消息文本、被回复的 Trigger 与相邻消息,不含 CI 运行 |
ticket/<id> |
相关 ids、source-batch Triggers、Threads,以及 Assignments 与 Inputs 的第一页 |
| Ticket 子资源 | /assignments、/inputs、/assignment、/result、/report/<assignment id>、/transcript、/message/<n>、/input/<id> |
settings、settings/<block> |
revisions 与摘要,或一个可直接保存的完整 block |
prompts、prompts/<revision>、prompts/current、prompts/defaults、prompt-preview/<id> |
提示词配置与组合文本 |
models、models/<provider> |
可用的模型 |
feishu/<trigger id>/…、github/… |
原始 Source 记录,在 Source 技能里有描述 |
Ticket 的 Threads 对仅为上下文的跟随标记 context、对 Source 绑定标记 bound。Assignment 分页最新在前并包含报告预览。列表用 next_offset 续读。/input/<id> 接受一个 Input 的名字或 id。settings 完整包含小的 blocks,并对 Policy 与 Source blocks 做摘要。
Source 资源包括消息近邻、github/pulls/<n>/thread/<comment id> 与 github/commits/<sha>/checks。
原始记录的消息统一以一个紧凑形状读取,{id, from, at, text, reply_to?, path?, line?, url?},而不是按 GitHub 或 Feishu 存储它们的样子。
一个 Executor Session 只能读取它自己的 Ticket、它的 Threads、抵达过它的或其请求所持有的 Triggers、它工作于其下的提示词版本与原始记录,它的页面会略去它读不到的东西的 ids——Decisions 与其他 Tickets。
一个页面展示的每个 id 都能经由这里面的某条读取,于是一个 Session 能从一个对象走到下一个;没有东西按其读取的哈希——比如一个 Eval 的 id 或一个 Policy 版本——不会出现在页面上。
一个文本页默认 4,000 个 UTF-16 code units、至多 8,000;一个列表默认 20 条、至多 50 条。
早期版本的 event/、task/ 与 observation/ 这些名字仍然可以解析。
投递给 Sessions 的消息
这些消息被引入 main Session 的回合:
| 消息 | 内容 |
|---|---|
| Candidate | 批次 Trigger 卡片、Thread、related_tickets、tickets_total、delivered_to、来自 Issue/PR 链接的被引用 Tickets、handed_back 的理由与下一步动作 |
follow_up |
一条消息按其 Executor 看到的样貌、replies_to、接收它的 Tickets,以及一条提醒:Executor 决定它,而 main Session 只能停止工作 |
worker_report |
Ticket 卡片、发起的 Trigger、状态与至多 600 字符的摘要;截断时的 chars 与一个读取指针 |
worker_attention |
携带那个提问的 Ticket 卡片 |
其他通知在不唤醒 main Session 的情况下注入:ticket_ended、settings_saved、提示词保存、Trigger requeues、被丢弃的 Subscriptions 与上下文跟随。一条设置通知点名 block、变更的路径与 read: settings/<block>。对 main Session 自己执行的保存、requeue、unsubscribe 或 attach,它不会收到多余的通知。
Workers 就地收到赋值与 Inputs。第一条消息包含标题、至多 1,500 字符的赋值文本与 source Trigger 卡片。Inputs 携带一个至多 600 字符、带 replies_to 的 Trigger 卡片,或纯 Input 文本。读取指针只在内容被截断时出现。
面板专属的动作
没有工具用于解除一条 unavailable Decision、重跑 setup、刷新 Source 显示名,或把一个 Executor 拥有的人类跟进送进 Eval。最后那个动作被 orchestrator_trigger_requeue 刻意拒绝。其他面板动作都有等价的工具。
大多数内建指令位于 prompts/*.md,每行一句话、由 promptFile 拼接。Source 技能位于 skills/dsh-superagent-*/SKILL.md。工具描述与固定通知——如重启恢复备注——留在代码里。研判指令只位于 prompts/admission.md。
tests/model-surface.spec.ts 检查这些表面有无漂移。004 提示词 解释团队可编辑的部件。
Configuration reload
除 stateDir 外,每个插件配置键都是 volatile 的。面板的 Deployment 设置与 DeepSeek Harness 的 Plugin Manager 经由 DeepSeek Harness 的配置表单编辑它们;host 写 cordis.patch.yml 并就地更新取值,dsh-superagent 在每次使用时读取每个限额,所以改动无需重启即生效。
一次更改了 setup 所读取的某个 Source 字段——例如账号的 CLI——的设置保存,以及 setup 等待期间的任何保存,会重启 dsh-superagent。DeepSeek Harness 的设置服务在那次重启期间不提供该条目,因此在它自己重启后,dsh-superagent 会请它重新读取该条目,一个打开的表单就能再次编辑。
用真实数据审查一次变更
把 DSH_HOME 指向一个临时目录并在那里创建一个新 profile。从一份备份复制数据库,禁用所有 Sources,暂停处理并移除 main Session id。给 GitHub 与 Feishu 的 CLI 只读的替身。
Open questions
- 信任:哪些 Trigger 字段是被信任的——如 Source 的 actor 与 Thread 成员资格——哪些不是——如正文与引文。
- 部署隔离:agent 的 profile 位于它自己钉住的 DeepSeek Harness 安装上,这样在其他地方升级 DeepSeek Harness 不会弄坏它。