refactor(goal): persist state with domain events
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-16-harness-level-loop.md
|
||||
2026-07-16-harness-level-loop.md: cf449affe77bf18feabbd163cd778f1496870995
|
||||
2026-07-16-harness-level-loop.zh.md: 6d1da72e885d65b54dceb235f18dbd3090e844a2
|
||||
2026-07-16-harness-level-loop.md: 0cebbcc305b1532d28aeffd6824edd870d42aec6
|
||||
2026-07-16-harness-level-loop.zh.md: 911a5990b72e63f6a840b544648f94cadf0d1345
|
||||
|
||||
@@ -42,11 +42,11 @@ Time-based `/loop` or scheduled execution is a third policy and is not implement
|
||||
| `@deepseek-ai/dsh-command-goal` | `packages/goal/command-goal/`, human-command producer | Registers `/goal` status, creation, edit, pause, resume, and clear over the goal domain for TUI. |
|
||||
| `@deepseek-ai/dsh-tool-ralph` | `packages/workflow/tool-ralph/`, fixed workflow consumer | Registers `ralph({ objective, maxRounds? })`, validates the fresh structured provider and bounded `RalphRoundReport`, and returns `complete`, `blocked`, or `budget-limited`. |
|
||||
|
||||
The detailed contracts live in the [goal-domain](2026-07-19-persisted-same-session-goal-domain.md), [model goal-tools](2026-07-19-model-facing-goal-tools.md), [goal-round driver](2026-07-19-same-session-goal-round-driver.md), [command registry](2026-07-19-plugin-command-registration.md), [human goal-command](2026-07-19-human-goal-command.md), and [Ralph workflow-tool](2026-07-19-fresh-agent-ralph-workflow-tool.md) Agent Notes.
|
||||
The detailed contracts live in the [goal-domain](2026-07-19-persisted-same-session-goal-domain.md), [goal-owned event](../architecture/2026-07-31-goal-owned-durable-events.md), [model goal-tools](2026-07-19-model-facing-goal-tools.md), [goal-round driver](2026-07-19-same-session-goal-round-driver.md), [command registry](2026-07-19-plugin-command-registration.md), [human goal-command](2026-07-19-human-goal-command.md), and [Ralph workflow-tool](2026-07-19-fresh-agent-ralph-workflow-tool.md) Agent Notes.
|
||||
|
||||
### Durable goal state and live authority
|
||||
|
||||
One session has at most one current goal. Every mutation commits through the durable `agent/inbox/spliced` insertion produced by `Agent.inject()`, carrying a full versioned snapshot or revisioned clear tombstone. Its queued context becomes model-visible only if later admitted, and discarding it does not roll back the goal. The session log is the only durable source of truth, so normal persistence, resume, and `SessionStore.fork()` carry the goal without a second database or an artificial cancellation record.
|
||||
One session has at most one current goal. Every mutation commits through a durable `goal/change` event carrying a full versioned snapshot or revisioned clear tombstone; inbox state does not participate. The session log is the only durable source of truth, so normal persistence, resume, and `SessionStore.fork()` carry the goal without a second database or an artificial cancellation record.
|
||||
|
||||
Durable phases are only `active`, `paused`, `blocked`, and `complete`. A blocked goal carries a required `GoalBlockReason` with a stable lower-kebab-case `code` and a non-empty human-readable `message`; usage limits, round exhaustion, model failures, and policy rejection are reason codes rather than extra lifecycle phases. Separate activation is `armed` or `disarmed` and is never persisted. Creation and explicit resume arm a goal; stop transitions, session start, fork replay, driver replacement, and driver teardown leave it disarmed.
|
||||
|
||||
@@ -58,7 +58,7 @@ Forked sessions inherit the durable goal prefix because that is the natural repl
|
||||
|
||||
### Same-session continuation
|
||||
|
||||
The goal-round driver owns at most one pending reservation per exact live agent. It admits a reservation only when the goal is active and armed, the agent is idle, no competing human work exists, pending mutation insertions have passed their durability checkpoint, the exact goal id/revision/round still matches, and downstream pre-step policy accepts it. Its `agent/pre-step` fence checks those facts both before and after downstream listeners, preventing an edit, pause, human message, or unload race from admitting obsolete work.
|
||||
The goal-round driver owns at most one pending reservation per exact live agent. It admits a reservation only when the goal is active and armed, the agent is idle, no competing human work exists, the latest mutation has passed its durability checkpoint, the exact goal id/revision/round still matches, and downstream pre-step policy accepts it. Its `agent/pre-step` fence checks those facts both before and after downstream listeners, preventing an edit, pause, human message, or unload race from admitting obsolete work.
|
||||
|
||||
Only an admitted positive-round goal-sourced `user/message` charges a round. A stale reservation is rejected before a turn opens without consuming the cap. A concurrent goal revision wins over settlement from an older round.
|
||||
|
||||
@@ -122,7 +122,7 @@ The six owning Agent Notes record unit, integration, process, snapshot, cancella
|
||||
- **Aggregate budgets** — `maxGoalRounds` and Ralph `maxRounds` are the only aggregate effort limits. Token, currency, elapsed-time, provider-usage, and per-round price admission policies are absent.
|
||||
- **No persistent autonomous runner** — same-session goal facts persist, but activation and scheduling are process-local and deliberately wait for human input after restore. Ralph runs are foreground and cannot resume after process loss. Background collection, restart recovery, and unattended resident execution are deferred.
|
||||
- **No time scheduler** — interval `/loop`, cron, proactive maintenance, and cloud or desktop scheduling are outside this decision.
|
||||
- **No generic loop journal or execution-world rewind** — session replay reconstructs model-visible goal history, not prior files, processes, environment, credentials, or external side effects. Ralph treats the current workspace as authority and carries no cross-run journal.
|
||||
- **No generic loop journal or execution-world rewind** — session replay reconstructs goal history, not prior files, processes, environment, credentials, or external side effects. Ralph treats the current workspace as authority and carries no cross-run journal.
|
||||
- **No goal reflector** — concern events, automatic no-progress heuristics, goal revision by an independent reflector, stuck-pattern detection, and `loop_split` are not implemented. Humans can edit, pause, clear, or resume the goal directly.
|
||||
- **Ralph policy remains narrow** — one round creates one fresh child; within-round fan-out, evaluator/worker role separation, dynamic provider/model selection, and structural recursive-Ralph tool denial need separate policy surfaces. Prompt guidance is not enforcement.
|
||||
- **Ralph does not retry a failed child** — an ordinary failure preserves the failed round and last good handoff, while fatal workflow infrastructure failures can end before that state is available. Retry count, backoff, and richer failure transport need separate policy and seam design.
|
||||
|
||||
@@ -42,11 +42,11 @@ Status: implemented
|
||||
| `@deepseek-ai/dsh-command-goal` | `packages/goal/command-goal/`,人类命令生产方 | 为 TUI 注册构建在目标领域之上的 `/goal` 状态、创建、编辑、暂停、恢复与清除。 |
|
||||
| `@deepseek-ai/dsh-tool-ralph` | `packages/workflow/tool-ralph/`,固定工作流消费者 | 注册 `ralph({ objective, maxRounds? })`,验证全新结构化 provider 与有界 `RalphRoundReport`,并返回 `complete`、`blocked` 或 `budget-limited`。 |
|
||||
|
||||
详细契约分别由[目标领域](2026-07-19-persisted-same-session-goal-domain.md)、[模型目标工具](2026-07-19-model-facing-goal-tools.md)、[目标回合驱动器](2026-07-19-same-session-goal-round-driver.md)、[命令注册表](2026-07-19-plugin-command-registration.md)、[人类目标命令](2026-07-19-human-goal-command.md)与 [Ralph 工作流工具](2026-07-19-fresh-agent-ralph-workflow-tool.md) Agent Note 拥有。
|
||||
详细契约分别由[目标领域](2026-07-19-persisted-same-session-goal-domain.md)、[Goal 自有事件](../architecture/2026-07-31-goal-owned-durable-events.md)、[模型目标工具](2026-07-19-model-facing-goal-tools.md)、[目标回合驱动器](2026-07-19-same-session-goal-round-driver.md)、[命令注册表](2026-07-19-plugin-command-registration.md)、[人类目标命令](2026-07-19-human-goal-command.md)与 [Ralph 工作流工具](2026-07-19-fresh-agent-ralph-workflow-tool.md) Agent Note 拥有。
|
||||
|
||||
### 持久目标状态与实时权限
|
||||
|
||||
一个会话至多有一个当前 goal。每次变更都通过 `Agent.inject()` 产生的持久 `agent/inbox/spliced` 插入项提交,并携带带版本的完整快照或带修订号的 clear 墓碑。其排队上下文只有在后续获准时才对模型可见,丢弃它不会回滚 goal。会话日志是唯一持久真源,因此普通持久化、恢复与 `SessionStore.fork()` 会携带 goal,无需第二个数据库或人为取消记录。
|
||||
一个会话至多有一个当前 goal。每次变更都通过持久 `goal/change` 事件提交,并携带带版本的完整快照或带修订号的 clear 墓碑;inbox 状态不参与其中。会话日志是唯一持久真源,因此普通持久化、恢复与 `SessionStore.fork()` 会携带 goal,无需第二个数据库或人为取消记录。
|
||||
|
||||
持久阶段只有 `active`、`paused`、`blocked` 与 `complete`。阻塞目标必须携带 `GoalBlockReason`,其中包含稳定的小写 kebab-case `code` 与非空的人类可读 `message`;用量限制、Round 耗尽、模型失败与策略拒绝都是原因代码,而不是额外生命周期阶段。独立激活态是 `armed` 或 `disarmed`,且永不持久化。创建与显式恢复会激活目标;停止转换、会话启动、fork 重放、驱动器替换和驱动器拆卸都会让目标保持未激活。
|
||||
|
||||
@@ -58,7 +58,7 @@ fork 会话会继承持久目标前缀,因为这是自然的重放结果。for
|
||||
|
||||
### 同会话续行
|
||||
|
||||
Goal Round 驱动器为每个准确实时 agent 至多拥有一个待定预留。只有 goal 处于活跃且已激活状态、agent 空闲、不存在竞争性人类工作、待定变更插入项已经通过持久性检查点、准确 goal id/revision/Round 仍匹配,并且下游 pre-step 策略接受时,它才会接纳预留。其 `agent/pre-step` 围栏会在下游监听器前后检查这些事实,防止编辑、暂停、人类消息或卸载竞争接纳陈旧工作。
|
||||
Goal Round 驱动器为每个准确实时 agent 至多拥有一个待定预留。只有 goal 处于活跃且已激活状态、agent 空闲、不存在竞争性人类工作、最新变更已经通过持久性检查点、准确 goal id/revision/Round 仍匹配,并且下游 pre-step 策略接受时,它才会接纳预留。其 `agent/pre-step` 围栏会在下游监听器前后检查这些事实,防止编辑、暂停、人类消息或卸载竞争接纳陈旧工作。
|
||||
|
||||
只有已准入、Round 为正数且来源为 goal 的 `user/message` 会计入一个 Round。陈旧预留会在轮次打开前被拒绝,不会消耗上限。并发 goal revision 会胜过旧 Round 的结算。
|
||||
|
||||
@@ -122,7 +122,7 @@ Codex 提供了这里采用的最小可观察目标 UX:一个附着于聊天
|
||||
- **聚合预算**——`maxGoalRounds` 与 Ralph `maxRounds` 是唯一聚合工作量限制。token、货币、耗时、provider 用量与逐 Round 价格准入策略均不存在。
|
||||
- **没有持久自治运行器**——同会话目标事实会持久化,但激活与调度只存在于进程内,并且有意在恢复后等待人类输入。Ralph 位于前台,进程丢失后无法恢复。后台收集、重启恢复与无人值守常驻执行均予以延期。
|
||||
- **没有时间调度器**——间隔 `/loop`、cron、主动维护以及云端或桌面调度不在本决策范围内。
|
||||
- **没有通用 loop 日志或执行世界回退**——会话重放会重建模型可见目标历史,而不会恢复此前文件、进程、环境、凭据或外部副作用。Ralph 把当前工作区作为权威,并且没有跨运行日志。
|
||||
- **没有通用 loop 日志或执行世界回退**——会话重放会重建目标历史,而不会恢复此前文件、进程、环境、凭据或外部副作用。Ralph 把当前工作区作为权威,并且没有跨运行日志。
|
||||
- **没有目标反思器**——concern 事件、自动无进展启发式、由独立反思器执行的目标修订、卡住模式检测与 `loop_split` 均未实现。人类可以直接编辑、暂停、清除或恢复目标。
|
||||
- **Ralph 策略仍然狭窄**——一个 Round 创建一个全新子 agent;Round 内扇出、评估器/工作者角色分离、动态 provider/模型选择与结构化递归 Ralph 工具禁止都需要独立策略表面。提示词指导不是强制执行。
|
||||
- **Ralph 不会重试失败的子 agent**——普通失败会保留失败 Round 与上一份有效交接,而致命工作流基础设施错误可能在该状态可用前结束。重试次数、退避与更丰富的失败传输需要独立的策略与接缝设计。
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-human-goal-command.md
|
||||
2026-07-19-human-goal-command.md: 1847985b49c9728df7068d866ee698a5ee8d27e6
|
||||
2026-07-19-human-goal-command.zh.md: 9a515a7b9d40b2849a2554118fb4f493c4fa5633
|
||||
2026-07-19-human-goal-command.md: 5fdd80f7423b80e84e58f7379130ee59a2e8a723
|
||||
2026-07-19-human-goal-command.zh.md: ef0b42370963aced9a20d5aae230c2a9da55def3
|
||||
|
||||
@@ -34,7 +34,7 @@ Status output omits branded ids and compare-and-set revisions because those are
|
||||
|
||||
Expected `GoalError` failures become one stable, branded-id-free `CommandResult.error`, so domain diagnostics do not leak compare-and-set internals into the human surface and invalid operations never enter model history. The current status supplies the actionable state-specific recovery. Other exceptions remain adapter-visible command failures; treating programmer faults as ordinary domain errors would hide defects. The command handler performs only synchronous domain mutations, so request cancellation is decided by the command registry before the mutation begins and there is no escaped asynchronous side effect to unwind.
|
||||
|
||||
Generic slash input, status text, and errors are not persisted. Successful goal mutations use the existing `Agent.inject()` path: the durable inbox insertion commits the raw goal snapshot or clear tombstone, while later admission independently makes that queued context model-visible. The command therefore changes no session format and introduces no second audit record that could disagree with the domain event.
|
||||
Generic slash input, status text, and errors are not persisted. Successful goal mutations append the domain-owned `goal/change` event and do not queue model context. The command introduces no second audit record that could disagree with the domain event.
|
||||
|
||||
### App composition
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Status: implemented
|
||||
|
||||
预期的 `GoalError` 失败会变为一个稳定且不含品牌化 id 的 `CommandResult.error`,使领域诊断不会向人类表面泄露比较并交换内部细节,非法操作也绝不会进入模型历史。当前状态负责提供针对具体状态且可执行的恢复路径。其他异常仍是适配器可见的命令失败;若把程序缺陷当成普通领域错误,就会隐藏问题。命令处理器只执行同步领域变更,因此请求取消会在变更开始前由命令注册表决定,不存在需要回滚的外逸异步副作用。
|
||||
|
||||
通用斜杠输入、状态文本与错误不会持久化。成功的 goal 变更使用现有 `Agent.inject()` 路径:持久 inbox 插入项提交原始 goal 快照或 clear 墓碑,后续准入则单独使已排队的上下文对模型可见。因此该命令不会改变会话格式,也不会引入可能与领域事件不一致的第二份审计记录。
|
||||
通用斜杠输入、状态文本与错误不会持久化。成功的 goal 变更会追加领域自有的 `goal/change` 事件,而且不会把模型上下文排队。该命令不会引入可能与领域事件不一致的第二份审计记录。
|
||||
|
||||
### 应用组合
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md
|
||||
2026-07-19-persisted-same-session-goal-domain.md: 7b09676e023b909da8c5a54ae0efa901b1d201e8
|
||||
2026-07-19-persisted-same-session-goal-domain.zh.md: 1bb9aab9d810719d0d0cee3a15d1cc2bc8354db7
|
||||
2026-07-19-persisted-same-session-goal-domain.md: ce93a652f3910912fbf6afc28061de4d13eb2de2
|
||||
2026-07-19-persisted-same-session-goal-domain.zh.md: 15f406281bb284ddb5b54ceabc56db8810c65b77
|
||||
|
||||
@@ -18,11 +18,11 @@ The durable phases are `active`, `paused`, `blocked`, and `complete`. A blocked
|
||||
|
||||
### Durable record and replay
|
||||
|
||||
Every mutation uses `Agent.inject()` to insert one round-zero goal-sourced message containing a versioned full snapshot or, for clear, a revisioned tombstone. The mutation commits in the durable `agent/inbox/spliced` insertion even if its context remains queued and never reaches the model; later removal or discard does not roll it back. If the same message is admitted as `user/message`, replay verifies its id, source, metadata, and rendered `<goal_state>...</goal_state>` content against the insertion without applying the mutation again. This descriptive delimiter follows the repository's existing `<workspace_context>` convention and [Anthropic's published guidance to structure mixed prompt content with consistent descriptive XML tags](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#structure-prompts-with-xml-tags). That is public model-experience prior art, not evidence about any provider's proprietary training corpus. The session log is the only durable source of truth, so persistence and fork inherit goal records without another database or header field.
|
||||
Every mutation appends a versioned `goal/change` session event containing a full snapshot or, for clear, a revisioned tombstone. The session log is the only durable source of truth, so persistence and fork inherit goal records without another database or header field. The [goal-owned durable event decision](../architecture/2026-07-31-goal-owned-durable-events.md) owns the separation from inbox state and model context.
|
||||
|
||||
The replay fold derives mutations only from inbox insertions and validates JSON shape, source attribution, rendered content, message-id consistency on later admission, fresh ids, revision continuity, lifecycle transitions, counters, and monotonic per-goal timestamps. Goal rounds advance only from positive sequential admitted `user/message` source numbers for the current active revision and cannot exceed `maxGoalRounds`; ordinary session turns do not affect the counter. A malformed current-format record fails replay rather than being ignored or repaired.
|
||||
The replay fold derives lifecycle mutations only from `goal/change` and validates JSON shape, fresh ids, revision continuity, lifecycle transitions, counters, and monotonic per-goal timestamps. Goal rounds advance only from positive sequential admitted `user/message` source numbers for the current active revision and cannot exceed `maxGoalRounds`; ordinary session turns do not affect the counter. A malformed current-format record fails replay rather than being ignored or repaired.
|
||||
|
||||
The service reconciles an injected mutation with its synchronous inbox insertion by message id, so reentrant insertion observers apply each accepted change exactly once and a later admission only checks consistency. Incremental replay advances its cursor after each valid event and remains positioned at the first corrupt event, so later reads report the same durable fault. The durable log remains authoritative after restart.
|
||||
Incremental replay advances its cursor after each valid event and remains positioned at the first corrupt event, so later reads report the same durable fault. The durable log remains authoritative after restart.
|
||||
|
||||
### Lifecycle and live activation
|
||||
|
||||
@@ -32,16 +32,16 @@ A cache built from any seed starts disarmed, and every `agent/session-start` edg
|
||||
|
||||
### Service boundary
|
||||
|
||||
The service accepts only the exact live `Agent` object registered under its id. Successful mutation injection emits the scoped `goal/changed` event with contained listener failures. Policy consumers use this service plus the public `Agent` interface and `agent/*` events; the goal domain does not import or modify `dsh-agent-loop`.
|
||||
The service accepts only the exact live `Agent` object registered under its id. A committed mutation emits the scoped `goal/changed` event with contained listener failures. Policy consumers use this service plus the public `Agent` interface and `agent/*` events; the goal domain does not import or modify `dsh-agent-loop`.
|
||||
|
||||
## Testing
|
||||
|
||||
Unit coverage pins creation defaults, exact-live-agent checks, compare-and-set rejection, every lifecycle transition, blocker reason validation and retention, cap enforcement on resume, clear/replacement, seeded replay and `SessionStore.fork()` inheritance, session-start and lifecycle-owner disarming, active-goal rearming, durable insertion folding, reentrant insertion observation, admission consistency, discarded-context persistence, rejected-injection rollback, stable corrupt-event replay, service/listener disposal, listener containment, backward-clock clamping, strict record decoding, lifecycle continuity, and sequential round attribution. A keyless Loader/stdio process test mounts the service and a lifecycle consumer through test-only `cordis.yml`, then reads the persisted JSONL externally to verify the goal record and absence of an unrequested goal round. The package source is held to the repository's per-file 100% coverage gate.
|
||||
Unit coverage pins creation defaults, exact-live-agent checks, compare-and-set rejection, every lifecycle transition, blocker reason validation and retention, cap enforcement on resume, clear/replacement, seeded replay and `SessionStore.fork()` inheritance, session-start and lifecycle-owner disarming, active-goal rearming, durable event folding, inbox independence, stable corrupt-event replay, service/listener disposal, listener containment, backward-clock clamping, strict record decoding, lifecycle continuity, and sequential round attribution. A keyless Loader/stdio process test mounts the service and a lifecycle consumer through test-only `cordis.yml`, then reads the persisted JSONL externally to verify the goal record and absence of an unrequested goal round. The package source is held to the repository's per-file 100% coverage gate.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Store goals in a separate database or session header** — rejected because the session log already supplies ordering, persistence, fork prefixes, and reconstructability; a second store introduces atomicity and lineage questions.
|
||||
- **Use goal-only log events without queued context** — rejected because durable state that changes future model behavior must have a reconstructable path into model input. The inbox insertion supplies durable commit while retaining that model-facing path.
|
||||
- **Couple each durable mutation to queued model context** — rejected by the later [goal-owned durable event decision](../architecture/2026-07-31-goal-owned-durable-events.md): goal tools and scheduled continuation prompts expose state when needed, while domain persistence remains independent from queue outcomes.
|
||||
- **Persist activation and restart automatically** — rejected because opening or resuming a session must wait for human input; durable phase records status, not fresh authority to spend resources.
|
||||
- **Count all session turns as goal rounds** — rejected because one session can contain human clarification, inspection, and unrelated work; only goal-attributed continuation turns consume this budget.
|
||||
- **Add goal state or a generic loop abstraction to `dsh-agent-loop`** — rejected because state and continuation policy can compose through existing plugins, `Agent` verbs, and events without privileging the shipped loop implementation.
|
||||
@@ -50,7 +50,7 @@ Unit coverage pins creation defaults, exact-live-agent checks, compare-and-set r
|
||||
|
||||
- Goal history survives persistence, resume, compaction of unrelated nodes, and session fork as ordinary session data.
|
||||
- Resume and fork expose the same durable phase while remaining operationally inert until an explicit resume mutation arms activation.
|
||||
- Full snapshots simplify inspection and strict replay; when admitted, they repeat the objective and state fields in model history until compaction shadows them.
|
||||
- Full snapshots simplify inspection, strict replay, and last-wins projection without adding mutation-only messages to model history.
|
||||
- Revision and lifecycle validation reject tampered, partially written, or producer-inconsistent goal records early.
|
||||
- Round caps bound continuation count only; policy consumers map round, token, currency, time, and provider limits to blocked reasons when they stop work.
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ Status: implemented
|
||||
|
||||
### 持久记录与回放
|
||||
|
||||
每次变更都使用 `Agent.inject()` 插入一条 Round 为 0、来源为 goal 的消息,其中包含带版本的完整快照;clear 则包含带修订号的墓碑。变更会在持久 `agent/inbox/spliced` 插入项中提交,即使其上下文仍在队列中且从未抵达模型也是如此;后续移除或丢弃不会回滚变更。如果同一消息获准成为 `user/message`,回放会对照插入项验证其 id、来源、元数据和渲染后的 `<goal_state>...</goal_state>` 内容,而不会再次应用变更。这个描述性分隔符沿用了仓库已有的 `<workspace_context>` 约定,也符合 [Anthropic 关于用一致且描述明确的 XML 标签组织混合提示词内容的公开指南](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#structure-prompts-with-xml-tags)。这是公开的模型体验先例,并非对任何提供方专有训练语料的推断。会话日志是唯一的持久真源,因此持久化和 fork 会继承 goal 记录,而无需另设数据库或头字段。
|
||||
每次变更都会追加带版本的 `goal/change` 会话事件,其中包含完整快照;clear 则包含带修订号的墓碑。会话日志是唯一的持久真源,因此持久化和 fork 会继承 goal 记录,而无需另设数据库或头字段。[Goal 自有持久事件决策](../architecture/2026-07-31-goal-owned-durable-events.md)负责 goal 状态与 inbox 状态、模型上下文之间的职责分离。
|
||||
|
||||
回放折叠只从 inbox 插入项派生变更,并校验 JSON 形状、来源归属、渲染内容、后续准入时消息 id 的一致性、新 id、修订连续性、生命周期转换、计数器以及单个 goal 内单调递增的时间戳。只有当前活跃修订上带正数且连续编号、已准入的 `user/message` 来源才会推进 Goal Round,且不能超过 `maxGoalRounds`;普通会话轮次不会影响该计数器。当前格式的畸形记录会使回放失败,而不会被忽略或修复。
|
||||
回放折叠只从 `goal/change` 派生生命周期变更,并校验 JSON 形状、新 id、修订连续性、生命周期转换、计数器以及单个 goal 内单调递增的时间戳。只有当前活跃修订上带正数且连续编号、已准入的 `user/message` 来源才会推进 Goal Round,且不能超过 `maxGoalRounds`;普通会话轮次不会影响该计数器。当前格式的畸形记录会使回放失败,而不会被忽略或修复。
|
||||
|
||||
服务按消息 id 对账注入的变更与其同步 inbox 插入项,因此重入插入观察器对每项已接受变更只应用一次,后续准入只校验一致性。增量回放会在每个有效事件后推进游标,并停留在首个损坏事件处,因此后续读取会报告同一个持久故障。重启后仍以持久日志为准。
|
||||
增量回放会在每个有效事件后推进游标,并停留在首个损坏事件处,因此后续读取会报告同一个持久故障。重启后仍以持久日志为准。
|
||||
|
||||
### 生命周期与实时激活态
|
||||
|
||||
@@ -32,16 +32,16 @@ Status: implemented
|
||||
|
||||
### 服务边界
|
||||
|
||||
服务只接受在对应 id 下注册的同一个实时 `Agent` 对象。成功注入变更后,它会发出带作用域的 `goal/changed` 事件,并隔离监听器失败。策略消费者通过本服务、公共 `Agent` 接口和 `agent/*` 事件工作;目标领域既不导入也不修改 `dsh-agent-loop`。
|
||||
服务只接受在对应 id 下注册的同一个实时 `Agent` 对象。变更提交后,它会发出带作用域的 `goal/changed` 事件,并隔离监听器失败。策略消费者通过本服务、公共 `Agent` 接口和 `agent/*` 事件工作;目标领域既不导入也不修改 `dsh-agent-loop`。
|
||||
|
||||
## 测试
|
||||
|
||||
单元测试固定创建默认值、精确实时 agent 校验、比较并交换拒绝、所有生命周期转换、阻塞原因校验与保留、恢复时的上限执行、清除与替换、种子回放和 `SessionStore.fork()` 继承、会话启动与生命周期所有者解除激活、活跃目标重新激活、持久插入折叠、重入插入观察、准入一致性、丢弃上下文后的持久性、注入拒绝回滚、损坏事件的稳定回放、服务与监听器销毁、监听器隔离、挂钟后退钳制、严格记录解码、生命周期连续性,以及连续 Goal Round 归属。无密钥 Loader/stdio 进程测试通过测试专用 `cordis.yml` 挂载服务与生命周期消费方,再从外部读取持久 JSONL,以验证 goal 记录以及不存在未经请求的 Goal Round。包源码受仓库逐文件 100% 覆盖率门禁约束。
|
||||
单元测试固定创建默认值、精确实时 agent 校验、比较并交换拒绝、所有生命周期转换、阻塞原因校验与保留、恢复时的上限执行、清除与替换、种子回放和 `SessionStore.fork()` 继承、会话启动与生命周期所有者解除激活、活跃目标重新激活、持久事件折叠、inbox 独立性、损坏事件的稳定回放、服务与监听器销毁、监听器隔离、挂钟后退钳制、严格记录解码、生命周期连续性,以及连续 Goal Round 归属。无密钥 Loader/stdio 进程测试通过测试专用 `cordis.yml` 挂载服务与生命周期消费方,再从外部读取持久 JSONL,以验证 goal 记录以及不存在未经请求的 Goal Round。包源码受仓库逐文件 100% 覆盖率门禁约束。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
- **把目标存入独立数据库或会话头**——不予采纳,因为会话日志已经提供顺序、持久化、fork 前缀与可重建性;第二份存储会引入原子性和谱系问题。
|
||||
- **使用不带排队上下文、仅供 goal 使用的日志事件**——不予采纳,因为会改变后续模型行为的持久状态必须拥有可重建的模型输入路径。inbox 插入在提供持久提交的同时保留了这条面向模型的路径。
|
||||
- **把每次持久变更与排队的模型上下文绑定。** 后续的 [Goal 自有持久事件决策](../architecture/2026-07-31-goal-owned-durable-events.md)不采用这一方案:goal 工具与已调度的继续执行提示词会在需要时暴露状态,而领域持久化不依赖队列结果。
|
||||
- **持久化激活态并自动重启**——不予采纳,因为打开或恢复会话时必须等待人类输入;持久阶段记录状态,而不是再次消耗资源的授权。
|
||||
- **把所有会话轮次都计为目标回合**——不予采纳,因为同一会话可以包含人类澄清、检查和无关工作;只有归属于目标的继续执行轮次才消耗该预算。
|
||||
- **向 `dsh-agent-loop` 添加目标状态或通用循环抽象**——不予采纳,因为状态与继续执行策略可以通过现有插件、`Agent` 动词和事件组合,而无需赋予默认循环实现特权。
|
||||
@@ -50,7 +50,7 @@ Status: implemented
|
||||
|
||||
- 目标历史作为普通会话数据,在持久化、恢复、无关节点压缩和会话 fork 后继续保留。
|
||||
- 恢复与 fork 会暴露同一持久阶段,但在显式恢复变更激活目标前不会执行任何操作。
|
||||
- 完整快照便于检查和严格回放;获准后,它们会在模型历史中重复目标描述与状态字段,直到压缩将其遮蔽。
|
||||
- 完整快照便于检查、严格回放与 last-wins 投影,且不会向模型历史添加只用于变更的消息。
|
||||
- 修订号与生命周期校验会尽早拒绝遭篡改、部分写入或生产者不一致的目标记录。
|
||||
- 回合上限只约束继续执行次数;当回合、token、费用、时间或提供方限制停止工作时,策略消费者会把它们映射为不同的阻塞原因。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user