refactor(goal): persist state with domain events

This commit is contained in:
_Kerman
2026-07-31 22:52:18 +08:00
parent b6cf9298e3
commit 1a09174987
48 changed files with 354 additions and 650 deletions

View File

@@ -444,13 +444,12 @@ Source: [`packages/fs/fs/src/index.ts:54`](../../packages/fs/fs/src/index.ts)
### `goal/changed` — emit
Goal mutation accepted by one live agent. The matching message has already committed through a durable inbox insertion; later admission or discard does not change that fact. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
Goal mutation accepted by one live agent. The matching `goal/change` session event has already committed. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
```ts cordis-catalog
/**
* Goal mutation accepted by one live agent. The matching message has
* already committed through a durable inbox insertion; later admission or
* discard does not change that fact. Listener failures are contained.
* Goal mutation accepted by one live agent. The matching `goal/change`
* session event has already committed. Listener failures are contained.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @param agent - agent whose session owns the goal.
* @param change - fresh current projection or clear tombstone.

View File

@@ -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 docs/core-data-structures/goal.md
goal.md: bb8b4525f7e12a904b1ba77cdefa4e4e1650ffe8
goal.zh.md: c61bfcf7683b3aa835ee7cca58aba861af847d17
goal.md: fc6a7e63e58fc7cd4bc524be1e66515593680d95
goal.zh.md: e6350a4b6307f117361167a532d558d3be8cb367

View File

@@ -71,10 +71,10 @@ interface GoalView extends GoalSnapshot {
## Durable changes
Every mutation is a round-zero goal-sourced message whose metadata is either a complete snapshot or a clear tombstone. It commits when `agent.inject()` records that message in the `inserted` payload of a durable `agent/inbox/spliced` event. The strict fold and persisted projection derive mutations only from these insertions, so deleting the queued context does not roll back goal state. A later `user/message` with the same id verifies the source, metadata, and verbatim rendered content against the insertion without applying the mutation again.
Every mutation is a durable `goal/change` session event whose payload is either a complete post-mutation snapshot or a clear tombstone. The strict fold and persisted projection derive lifecycle state only from these events; inbox mutations do not affect goal state.
```ts type-equiv
/** Full-snapshot goal mutation committed by an injected inbox message. */
/** Full-snapshot goal mutation committed by a durable `goal/change` event. */
interface GoalSnapshotChangeMeta {
readonly kind: 'goal/change'
readonly version: 1
@@ -97,18 +97,16 @@ interface GoalClearChangeMeta {
}
```
Goal state changes use round `0`. A continuation consumer attributes each admitted user-message turn with a positive, sequential round number and the current revision; only these admitted `user/message` events advance `roundsStarted`. Replay rejects gaps, stale revisions, stopped phases, and cap overflow.
A continuation consumer attributes each admitted user-message turn with a positive, sequential round number and the current revision; only these admitted `user/message` events advance `roundsStarted`. Replay rejects non-positive rounds, gaps, stale revisions, stopped phases, and cap overflow.
```ts type-equiv
/** Message attribution for durable goal state and continuation rounds. */
/** Message attribution for admitted continuation rounds. */
interface GoalMessageSource {
readonly kind: 'goal'
readonly goalId: GoalId
readonly revision: number
/** Zero for state changes; positive for admitted continuation rounds. */
/** Positive admitted continuation round. */
readonly round: number
/** Complete durable mutation carried only by round-zero state-change messages. */
readonly change?: GoalChangeMeta
}
```
@@ -133,7 +131,7 @@ interface EditGoalRequest {
```
```ts type-equiv
/** Live notification after one goal mutation commits through inbox insertion. */
/** Live notification after one durable goal mutation commits. */
interface GoalChanged {
readonly operation: GoalOperation
readonly ref: GoalRef
@@ -144,4 +142,4 @@ interface GoalChanged {
## Service behavior
[`GoalService`](../../packages/goal/goal/src/index.ts) resolves creation defaults, folds strict replay from durable inbox insertions, enforces exact-live-agent identity and compare-and-set mutations, reconciles later admission by message id, and emits contained `goal/changed` notifications. The package [README](../../packages/goal/goal/README.md) owns the callable and model-visible contract.
[`GoalService`](../../packages/goal/goal/src/index.ts) resolves creation defaults, folds strict replay from durable `goal/change` events, enforces exact-live-agent identity and compare-and-set mutations, and emits contained `goal/changed` notifications. The package [README](../../packages/goal/goal/README.md) owns the callable and model-visible contract.

View File

@@ -71,10 +71,10 @@ interface GoalView extends GoalSnapshot {
## 持久变更
每次变更都是 Round 编号为 0、来源为目标的消息其元数据要么是完整快照要么是清除墓碑。当 `agent.inject()` 将该消息记录到持久 `agent/inbox/spliced` 事件的 `inserted` 载荷时,变更即已提交。严格折叠与持久投影只从这些插入项派生变更,因此删除队列中的上下文不会回滚目标状态。随后具有相同 id 的 `user/message` 会对照插入项验证来源、元数据和逐字渲染内容,而不会再次应用变更
每次变更都是持久的 `goal/change` 会话事件其载荷要么是变更后的完整快照要么是清除墓碑。严格折叠与持久投影只从这些事件派生生命周期状态inbox 变更不会影响 goal 状态
```ts type-equiv
/** Full-snapshot goal mutation committed by an injected inbox message. */
/** Full-snapshot goal mutation committed by a durable `goal/change` event. */
interface GoalSnapshotChangeMeta {
readonly kind: 'goal/change'
readonly version: 1
@@ -97,18 +97,16 @@ interface GoalClearChangeMeta {
}
```
目标状态变更使用 Round `0`。续跑消费方会为每个获准的用户消息轮次标注正数且连续的 Round 编号和当前修订号;只有这些获准的 `user/message` 事件会推进 `roundsStarted`。回放会拒绝编号缺口、陈旧修订号、已停止阶段和超出上限。
续跑消费方会为每个获准的用户消息轮次标注正数且连续的 Round 编号和当前修订号;只有这些获准的 `user/message` 事件会推进 `roundsStarted`。回放会拒绝非正数 Round、编号缺口、陈旧修订号、已停止阶段和超出上限。
```ts type-equiv
/** Message attribution for durable goal state and continuation rounds. */
/** Message attribution for admitted continuation rounds. */
interface GoalMessageSource {
readonly kind: 'goal'
readonly goalId: GoalId
readonly revision: number
/** Zero for state changes; positive for admitted continuation rounds. */
/** Positive admitted continuation round. */
readonly round: number
/** Complete durable mutation carried only by round-zero state-change messages. */
readonly change?: GoalChangeMeta
}
```
@@ -133,7 +131,7 @@ interface EditGoalRequest {
```
```ts type-equiv
/** Live notification after one goal mutation commits through inbox insertion. */
/** Live notification after one durable goal mutation commits. */
interface GoalChanged {
readonly operation: GoalOperation
readonly ref: GoalRef
@@ -144,4 +142,4 @@ interface GoalChanged {
## 服务行为
[`GoalService`](../../packages/goal/goal/src/index.ts) 解析创建默认值、从持久 inbox 插入项执行严格回放折叠、校验确切的活跃 agent 身份、以比较并设置方式执行变更、按消息 id 对账后续准入,并发出 `goal/changed` 通知;监听器故障会被隔离。包 [README](../../packages/goal/goal/README.md) 负责记录可调用契约和面向模型的契约。
[`GoalService`](../../packages/goal/goal/src/index.ts) 解析创建默认值、从持久 `goal/change` 事件执行严格回放折叠、校验确切的活跃 agent 身份、以比较并设置方式执行变更,并发出 `goal/changed` 通知;监听器故障会被隔离。包 [README](../../packages/goal/goal/README.md) 负责记录可调用契约和面向模型的契约。

View File

@@ -150,7 +150,7 @@ Source: [`packages/ui/user-approval/src/index.ts:55`](../packages/ui/user-approv
/**
* The session's approval policy was switched — log-only, durable,
* replayable, never in the model transcript (the model learns the policy
* from the prompt section and the narrator's notices). The LAST such
* from the cache-safe runtime-context snapshot). The LAST such
* event is the session's override ({@link effectiveApprovalPolicy}).
* `source: 'delegation'` marks an override seeded into a child; an absent
* source is a runtime switch.
@@ -230,20 +230,27 @@ Source: [`packages/ui/commands/src/index.ts:132`](../packages/ui/commands/src/in
#### `compact/end` — log-only
```ts persistence-catalog
/** Marks the end of a compaction — log-only, releases the lock. `error` set if summarization failed. */
'compact/end': { turn: number; error?: string }
/**
* Marks the end of a compaction — log-only, releases the lock. Its owner
* matches `compact/start`; `error` records an unsuccessful attempt.
*/
'compact/end': { turn: number | null; error?: string }
```
Source: [`packages/compact/compact/src/types.ts:44`](../packages/compact/compact/src/types.ts)
Source: [`packages/compact/compact/src/types.ts:51`](../packages/compact/compact/src/types.ts)
#### `compact/start` — log-only
```ts persistence-catalog
/** Marks the start of a compaction — log-only, holds the lock until `compact/end`. */
'compact/start': { turn: number }
/**
* Marks the start of a compaction — log-only, holds the lock until
* `compact/end`. A numbered owner is strictly enclosed by that open turn;
* `null` identifies a standalone manual transaction between turns.
*/
'compact/start': { turn: number | null }
```
Source: [`packages/compact/compact/src/types.ts:15`](../packages/compact/compact/src/types.ts)
Source: [`packages/compact/compact/src/types.ts:19`](../packages/compact/compact/src/types.ts)
#### `compact/summary` — log-only
@@ -279,7 +286,20 @@ Source: [`packages/compact/compact/src/types.ts:15`](../packages/compact/compact
Types: [ContentBlock](core-data-structures/core.md) · [TokenUsage](core-data-structures/llm-streaming.md)
Source: [`packages/compact/compact/src/types.ts:22`](../packages/compact/compact/src/types.ts)
Source: [`packages/compact/compact/src/types.ts:26`](../packages/compact/compact/src/types.ts)
### `goal/*`
#### `goal/change` — log-only
```ts persistence-catalog
/**
* Complete post-mutation goal state or clear tombstone.
*/
'goal/change': GoalChangeMeta
```
Source: [`packages/goal/goal/src/domain.ts:81`](../packages/goal/goal/src/domain.ts)
### `hook/*`
@@ -371,7 +391,7 @@ Source: [`packages/llm/llm-retry/src/index.ts:17`](../packages/llm/llm-retry/src
'permission/preset': { preset: string }
```
Source: [`packages/ui/permission/src/index.ts:49`](../packages/ui/permission/src/index.ts)
Source: [`packages/ui/permission/src/index.ts:50`](../packages/ui/permission/src/index.ts)
### `plan/*`
@@ -661,3 +681,14 @@ Source: [`packages/core/session/src/types.ts:174`](../packages/core/session/src/
```
Source: [`packages/core/session/src/types.ts:193`](../packages/core/session/src/types.ts)
### `web/*`
#### `web/deepseek-search-llm-request` — log-only
```ts persistence-catalog
/** Secret-free auxiliary DeepSeek search request recorded before dispatch. */
'web/deepseek-search-llm-request': DeepSeekSearchLlmRequest
```
Source: [`packages/web/web-search-deepseek/src/provider.ts:83`](../packages/web/web-search-deepseek/src/provider.ts)

View File

@@ -25,7 +25,7 @@ This table connects model-visible tool names to the plugin package and service s
| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after successful file operations`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The tool schemas above are identical with or without the policy plugin. |
| `@deepseek-ai/dsh-tool-fs-search` | `glob`, `grep` | `ctx.tools`, `ctx.bash`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | glob and grep are conditional bash-backed discovery tools: they register only when ctx.bash can find `rg`, then run fixed ripgrep commands through ctx.bash as ordinary foreground calls (never background tasks). The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments. |
| `@deepseek-ai/dsh-tool-pty` | `terminal_close`, `terminal_list`, `terminal_open`, `terminal_read`, `terminal_send`, `terminal_signal` | `ctx.tools`, `ctx.pty`, `ctx.systemPrompt`, `ctx.tasks at call time for run_in_background` | `tool/call`, `tool/result` | - | The six terminal tools are opt-in and complement one-shot bash/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.tasks`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema. |
| `@deepseek-ai/dsh-tool-goal` | `create_goal`, `get_goal`, `update_goal` | `ctx.tools`, `ctx.agents`, `ctx.goals`, `ctx.systemPrompt`, `a calling Agent in an authorized open turn` | `tool/call`, `agent/inbox/spliced goal snapshot for mutations`, `tool/result` | - | create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds. |
| `@deepseek-ai/dsh-tool-goal` | `create_goal`, `get_goal`, `update_goal` | `ctx.tools`, `ctx.agents`, `ctx.goals`, `ctx.systemPrompt`, `a calling Agent in an authorized open turn` | `tool/call`, `goal/change for mutations`, `tool/result` | - | create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds. |
| `@deepseek-ai/dsh-tool-lsp` | `lsp` | `ctx.tools`, `ctx.lsp`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-local`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema. |
| `@deepseek-ai/dsh-tool-ralph` | `ralph` | `ctx.tools`, `ctx.workflows`, `ctx.subagents`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents every fresh round)` | `tool/call`, `tool/result`, `workflow and child session events during execution` | - | A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap. |
| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.tools`, `ctx.agents`, `ctx.skills` | `tool/call`, `tool/result`, `user/message replacement catalogs via agent.inject()` | - | - |