fix(workflow-run): infer interruption only from crash or fork closers

Replace the persisted background marker with a client-only rule. A
foreground run writes run-end before its Tool Step ends and a background
run outlives that Step, so only a Turn closed after a crash or at a fork
boundary shows a run without run-end as interrupted.
This commit is contained in:
Turtle committed 2026-10-03 10:16:11 +08:00
1 parent 34aef8c140
commit d3c2b3e642
19 files changed
+129 -351

No files matched your search

+22 -22
View File
@@ -9,8 +9,8 @@
en: e7c80d1de4d328b4
zh: d9f368c27fd9f998
/session-persistence-event-catalog/persistence-type-fingerprints:
en: 8501cdd466c329a3
zh: 1fecdaa5349ce427
en: 33dd28b1c768cd8b
zh: 7256442baf2faed1
/session-persistence-event-catalog/event-envelope:
en: 7ebb1a2e0dd1f916
zh: 8946f415dda5c1b8
@@ -252,17 +252,17 @@
en: 1eac7deac6af15f3
zh: 1eac7deac6af15f3
/session-persistence-event-catalog/events/tool-workflow/tool-workflow-agent-end-log-only:
en: 24d01a19ee065fdf
zh: c433d504c2b5d830
en: 77bc3650fec776c4
zh: 423c11a52cb11e1a
/session-persistence-event-catalog/events/tool-workflow/tool-workflow-agent-start-log-only:
en: a96412f6e5621c77
zh: 9bd265eb39e18428
en: 8e131e9c8210d334
zh: 65059ec0c2f5dc95
/session-persistence-event-catalog/events/tool-workflow/tool-workflow-run-end-log-only:
en: 1d6bcf016a6af399
zh: 710a56368a33c717
en: 7d4f9675b83814d3
zh: 5bfd2fdd82b2f85d
/session-persistence-event-catalog/events/tool-workflow/tool-workflow-run-start-log-only:
en: b510118291f55b51
zh: 26e9b29ed0c0114b
en: b96c096b5ff3e03c
zh: ec6b06357cbc3398
/session-persistence-event-catalog/events/turn:
en: d7e8c999a0851a0c
zh: d7e8c999a0851a0c
@@ -1236,17 +1236,17 @@
en: 12c14727bb7834e2
zh: b0f3c877873c36db
/session-persistence-event-catalog/resolved-persistence-types/toolworkflowagentenddata:
en: 8f327cc1f9f2474c
zh: 812e74404c278beb
en: ac394442db02e075
zh: 3782191860f095f1
/session-persistence-event-catalog/resolved-persistence-types/toolworkflowagentstartdata:
en: debf65304f3ef434
zh: a3e3d46bdbbf5bca
en: b30d7b45639c4d48
zh: 97cca691e6952c88
/session-persistence-event-catalog/resolved-persistence-types/toolworkflowrunenddata:
en: 586c57bf172e73d1
zh: 5d02fcf3992a7011
en: 4905fd20255fab76
zh: 46ebc17868537607
/session-persistence-event-catalog/resolved-persistence-types/toolworkflowrunstartdata:
en: 727900992b451c68
zh: 3c977315869a0a51
en: 54090e169899c573
zh: 530ea8d0d0f8cf68
/session-persistence-event-catalog/resolved-persistence-types/turnendcancelcause:
en: 544292eddeff5faa
zh: 4e36e575a5030fb2
@@ -2019,11 +2019,11 @@
en: 557cbf925d0d118a
zh: cdea625fa2ad1c33
/session-persistence-event-catalog/resolved-persistence-types/type-tool-workflow-run-end:
en: 5ed112e5ea7334c4
zh: 15d0b1188a96fdda
en: bb5da0ee7d289b9c
zh: 2e6703d11142f065
/session-persistence-event-catalog/resolved-persistence-types/type-tool-workflow-run-start:
en: dbaeaf21e1d3cd05
zh: e7c6da48921f3dad
en: 939c100f8a590003
zh: b372827fffc17b1c
/session-persistence-event-catalog/resolved-persistence-types/type-tool-call:
en: 2dc168f9f2bd25d6
zh: d75bd24319468403
+14 -15
View File
@@ -69,7 +69,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover
| `event:tool-workflow/agent-end` | event | `babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7` | [`{ type: "tool-workflow/agent-end" }`](#persistence-type-sha256-babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7) |
| `event:tool-workflow/agent-start` | event | `5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627` | [`{ type: "tool-workflow/agent-start" }`](#persistence-type-sha256-5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627) |
| `event:tool-workflow/run-end` | event | `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f` | [`{ type: "tool-workflow/run-end" }`](#persistence-type-sha256-42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f) |
| `event:tool-workflow/run-start` | event | `0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc` | [`{ type: "tool-workflow/run-start" }`](#persistence-type-sha256-0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc) |
| `event:tool-workflow/run-start` | event | `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058` | [`{ type: "tool-workflow/run-start" }`](#persistence-type-sha256-c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058) |
| `event:tool/call` | event | `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` | [`{ type: "tool/call" }`](#persistence-type-sha256-3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48) |
| `event:tool/ptc-dispatch` | event | `100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163` | [`{ type: "tool/ptc-dispatch" }`](#persistence-type-sha256-100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163) |
| `event:tool/ptc-dispatch-start` | event | `ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93` | [`{ type: "tool/ptc-dispatch-start" }`](#persistence-type-sha256-ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93) |
@@ -1150,7 +1150,7 @@ Source: [`packages/core/session/src/types.ts:375`](../packages/core/session/src/
'tool-workflow/agent-end': ToolWorkflowAgentEndData
```
Source: [`packages/workflow/tool-workflow/src/types.ts:59`](../packages/workflow/tool-workflow/src/types.ts)
Source: [`packages/workflow/tool-workflow/src/types.ts:57`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowagent-start--log-only"></a>
@@ -1164,7 +1164,7 @@ Source: [`packages/workflow/tool-workflow/src/types.ts:59`](../packages/workflow
'tool-workflow/agent-start': ToolWorkflowAgentStartData
```
Source: [`packages/workflow/tool-workflow/src/types.ts:54`](../packages/workflow/tool-workflow/src/types.ts)
Source: [`packages/workflow/tool-workflow/src/types.ts:52`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowrun-end--log-only"></a>
@@ -1178,7 +1178,7 @@ Source: [`packages/workflow/tool-workflow/src/types.ts:54`](../packages/workflow
'tool-workflow/run-end': ToolWorkflowRunEndData
```
Source: [`packages/workflow/tool-workflow/src/types.ts:64`](../packages/workflow/tool-workflow/src/types.ts)
Source: [`packages/workflow/tool-workflow/src/types.ts:62`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowrun-start--log-only"></a>
@@ -1187,12 +1187,12 @@ Source: [`packages/workflow/tool-workflow/src/types.ts:64`](../packages/workflow
```ts persistence-catalog
/**
* Opens one top-level workflow record.
* @param data - stable run identity, display name, and background marker.
* @param data - stable run identity and display name.
*/
'tool-workflow/run-start': ToolWorkflowRunStartData
```
Source: [`packages/workflow/tool-workflow/src/types.ts:49`](../packages/workflow/tool-workflow/src/types.ts)
Source: [`packages/workflow/tool-workflow/src/types.ts:47`](../packages/workflow/tool-workflow/src/types.ts)
### `turn/*`
@@ -5014,7 +5014,7 @@ Array of [`ToolSchema`](#persistence-type-sha256-66ba70f6f068da443f969a0b947a013
SHA-256: `c0ea97bc89dfc46fa94eab956f5c3ee6af2f964aa7db6327cb18b451f207bef7`
Sources: [`packages/workflow/tool-workflow/src/types.ts:31`](../packages/workflow/tool-workflow/src/types.ts)
Sources: [`packages/workflow/tool-workflow/src/types.ts:29`](../packages/workflow/tool-workflow/src/types.ts)
| Property | Presence | Type |
|---|---|---|
@@ -5032,7 +5032,7 @@ Sources: [`packages/workflow/tool-workflow/src/types.ts:31`](../packages/workflo
SHA-256: `9a410ddf56792b2b7cd4a224a2e54dc4dee30a8e8e6b1c2b9a1d19992b217adc`
Sources: [`packages/workflow/tool-workflow/src/types.ts:22`](../packages/workflow/tool-workflow/src/types.ts)
Sources: [`packages/workflow/tool-workflow/src/types.ts:20`](../packages/workflow/tool-workflow/src/types.ts)
| Property | Presence | Type |
|---|---|---|
@@ -5052,14 +5052,14 @@ Sources: [`packages/workflow/tool-workflow/src/types.ts:22`](../packages/workflo
SHA-256: `7f9365bae9fcb73e1e34061a1acfe0422c1a5fd351848cace9bebbe806f28462`
Sources: [`packages/workflow/tool-workflow/src/types.ts:38`](../packages/workflow/tool-workflow/src/types.ts)
Sources: [`packages/workflow/tool-workflow/src/types.ts:36`](../packages/workflow/tool-workflow/src/types.ts)
| Property | Presence | Type |
|---|---|---|
| `runId` | required | `string` |
| `stopReason` | required | [`WorkflowStopReason`](#persistence-type-sha256-8465ece5e9d9007c038b357015d849c15262cb0bc25abc59e47692d87ee01a15) |
<a id="persistence-type-sha256-3cc643a957d3f0b1ecb0db5dcb5326939cdb35afe6a98b3261415b7d3c72e376"></a>
<a id="persistence-type-sha256-a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c"></a>
<a id="persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowrunstartdata"></a>
@@ -5067,13 +5067,12 @@ Sources: [`packages/workflow/tool-workflow/src/types.ts:38`](../packages/workflo
### `ToolWorkflowRunStartData`
SHA-256: `3cc643a957d3f0b1ecb0db5dcb5326939cdb35afe6a98b3261415b7d3c72e376`
SHA-256: `a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c`
Sources: [`packages/workflow/tool-workflow/src/types.ts:14`](../packages/workflow/tool-workflow/src/types.ts)
| Property | Presence | Type |
|---|---|---|
| `background` | optional | `true` |
| `name` | required | `string` |
| `runId` | required | `string` |
@@ -8567,17 +8566,17 @@ SHA-256: `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f`
| `time` | required | `number` |
| `type` | required | `"tool-workflow/run-end"` |
<a id="persistence-type-sha256-0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc"></a>
<a id="persistence-type-sha256-c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058"></a>
<a id="persistence-type-eventtool-workflowrun-start"></a>
### `{ type: "tool-workflow/run-start" }`
SHA-256: `0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc`
SHA-256: `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058`
| Property | Presence | Type |
|---|---|---|
| `data` | required | [`ToolWorkflowRunStartData`](#persistence-type-sha256-3cc643a957d3f0b1ecb0db5dcb5326939cdb35afe6a98b3261415b7d3c72e376) |
| `data` | required | [`ToolWorkflowRunStartData`](#persistence-type-sha256-a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c) |
| `ignorable` | optional | `true` |
| `seq` | required | `number` |
| `time` | required | `number` |
+14 -15
View File
@@ -71,7 +71,7 @@
| `event:tool-workflow/agent-end` | event | `babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7` | [`{ type: "tool-workflow/agent-end" }`](#persistence-type-sha256-babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7) |
| `event:tool-workflow/agent-start` | event | `5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627` | [`{ type: "tool-workflow/agent-start" }`](#persistence-type-sha256-5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627) |
| `event:tool-workflow/run-end` | event | `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f` | [`{ type: "tool-workflow/run-end" }`](#persistence-type-sha256-42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f) |
| `event:tool-workflow/run-start` | event | `0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc` | [`{ type: "tool-workflow/run-start" }`](#persistence-type-sha256-0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc) |
| `event:tool-workflow/run-start` | event | `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058` | [`{ type: "tool-workflow/run-start" }`](#persistence-type-sha256-c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058) |
| `event:tool/call` | event | `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` | [`{ type: "tool/call" }`](#persistence-type-sha256-3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48) |
| `event:tool/ptc-dispatch` | event | `100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163` | [`{ type: "tool/ptc-dispatch" }`](#persistence-type-sha256-100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163) |
| `event:tool/ptc-dispatch-start` | event | `ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93` | [`{ type: "tool/ptc-dispatch-start" }`](#persistence-type-sha256-ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93) |
@@ -1152,7 +1152,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
'tool-workflow/agent-end': ToolWorkflowAgentEndData
```
来源:[`packages/workflow/tool-workflow/src/types.ts:59`](../packages/workflow/tool-workflow/src/types.ts)
来源:[`packages/workflow/tool-workflow/src/types.ts:57`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowagent-start--log-only"></a>
@@ -1166,7 +1166,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
'tool-workflow/agent-start': ToolWorkflowAgentStartData
```
来源:[`packages/workflow/tool-workflow/src/types.ts:54`](../packages/workflow/tool-workflow/src/types.ts)
来源:[`packages/workflow/tool-workflow/src/types.ts:52`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowrun-end--log-only"></a>
@@ -1180,7 +1180,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
'tool-workflow/run-end': ToolWorkflowRunEndData
```
来源:[`packages/workflow/tool-workflow/src/types.ts:64`](../packages/workflow/tool-workflow/src/types.ts)
来源:[`packages/workflow/tool-workflow/src/types.ts:62`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowrun-start--log-only"></a>
@@ -1189,12 +1189,12 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
```ts persistence-catalog
/**
* Opens one top-level workflow record.
* @param data - stable run identity, display name, and background marker.
* @param data - stable run identity and display name.
*/
'tool-workflow/run-start': ToolWorkflowRunStartData
```
来源:[`packages/workflow/tool-workflow/src/types.ts:49`](../packages/workflow/tool-workflow/src/types.ts)
来源:[`packages/workflow/tool-workflow/src/types.ts:47`](../packages/workflow/tool-workflow/src/types.ts)
### `turn/*`
@@ -5016,7 +5016,7 @@ SHA-256: `a0baff42eb900561f91032b6950a3925cf19c5c8d9ba33948ac69e4ac7dee573`
SHA-256: `c0ea97bc89dfc46fa94eab956f5c3ee6af2f964aa7db6327cb18b451f207bef7`
来源:[`packages/workflow/tool-workflow/src/types.ts:31`](../packages/workflow/tool-workflow/src/types.ts)
来源:[`packages/workflow/tool-workflow/src/types.ts:29`](../packages/workflow/tool-workflow/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
@@ -5034,7 +5034,7 @@ SHA-256: `c0ea97bc89dfc46fa94eab956f5c3ee6af2f964aa7db6327cb18b451f207bef7`
SHA-256: `9a410ddf56792b2b7cd4a224a2e54dc4dee30a8e8e6b1c2b9a1d19992b217adc`
来源:[`packages/workflow/tool-workflow/src/types.ts:22`](../packages/workflow/tool-workflow/src/types.ts)
来源:[`packages/workflow/tool-workflow/src/types.ts:20`](../packages/workflow/tool-workflow/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
@@ -5054,14 +5054,14 @@ SHA-256: `9a410ddf56792b2b7cd4a224a2e54dc4dee30a8e8e6b1c2b9a1d19992b217adc`
SHA-256: `7f9365bae9fcb73e1e34061a1acfe0422c1a5fd351848cace9bebbe806f28462`
来源:[`packages/workflow/tool-workflow/src/types.ts:38`](../packages/workflow/tool-workflow/src/types.ts)
来源:[`packages/workflow/tool-workflow/src/types.ts:36`](../packages/workflow/tool-workflow/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
| `runId` | 必需 | `string` |
| `stopReason` | 必需 | [`WorkflowStopReason`](#persistence-type-sha256-8465ece5e9d9007c038b357015d849c15262cb0bc25abc59e47692d87ee01a15) |
<a id="persistence-type-sha256-3cc643a957d3f0b1ecb0db5dcb5326939cdb35afe6a98b3261415b7d3c72e376"></a>
<a id="persistence-type-sha256-a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c"></a>
<a id="persistence-type-packagesworkflowtool-workflowsrctypeststoolworkflowrunstartdata"></a>
@@ -5069,13 +5069,12 @@ SHA-256: `7f9365bae9fcb73e1e34061a1acfe0422c1a5fd351848cace9bebbe806f28462`
### `ToolWorkflowRunStartData`
SHA-256: `3cc643a957d3f0b1ecb0db5dcb5326939cdb35afe6a98b3261415b7d3c72e376`
SHA-256: `a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c`
来源:[`packages/workflow/tool-workflow/src/types.ts:14`](../packages/workflow/tool-workflow/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
| `background` | 可选 | `true` |
| `name` | 必需 | `string` |
| `runId` | 必需 | `string` |
@@ -8569,17 +8568,17 @@ SHA-256: `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f`
| `time` | 必需 | `number` |
| `type` | 必需 | `"tool-workflow/run-end"` |
<a id="persistence-type-sha256-0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc"></a>
<a id="persistence-type-sha256-c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058"></a>
<a id="persistence-type-eventtool-workflowrun-start"></a>
### `{ type: "tool-workflow/run-start" }`
SHA-256: `0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc`
SHA-256: `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058`
| 属性 | 存在性 | 类型 |
|---|---|---|
| `data` | 必需 | [`ToolWorkflowRunStartData`](#persistence-type-sha256-3cc643a957d3f0b1ecb0db5dcb5326939cdb35afe6a98b3261415b7d3c72e376) |
| `data` | 必需 | [`ToolWorkflowRunStartData`](#persistence-type-sha256-a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c) |
| `ignorable` | 可选 | `true` |
| `seq` | 必需 | `number` |
| `time` | 必需 | `number` |
@@ -1,28 +0,0 @@
# Bilingual-pair consistency record for 2026-10-03-workflow-run-background.md (docs/i18n/README.md): per heading
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-10-03-workflow-run-background.md
/:
en: 3d5a424ae7d7e46b
zh: c22a8e4a62d8cc1a
/2026-10-03-workflow-run-background:
en: 990aebebf4796f71
zh: 9ae52c345e6c5537
/2026-10-03-workflow-run-background/summary:
en: bffd445af628f905
zh: 6207efcc06c2768d
/2026-10-03-workflow-run-background/table-of-contents:
en: 6cf314a069ceb451
zh: 9adff678e9ecab3b
/2026-10-03-workflow-run-background/declaration:
en: 6907136f28e48c90
zh: 02f8298485f3d8e7
/2026-10-03-workflow-run-background/compatibility:
en: cdcfa251bd45ee28
zh: 593c290c4ee9b868
/2026-10-03-workflow-run-background/verification:
en: 0539b2ac90f058bf
zh: 6ead6a707c3b3121
/2026-10-03-workflow-run-background/dev-note:
en: b75c8458928b72b9
zh: 003ba4e17b4b5269
@@ -1,48 +0,0 @@
---
description: "Records a persistence type transition and its compatibility acknowledgement."
kind: persistence-change
---
# 2026-10-03-workflow-run-background
English | [中文](2026-10-03-workflow-run-background.zh.md)
## Summary
Adds an optional background marker to tool-workflow/run-start.
## Table of Contents
- [Declaration](#declaration)
- [Compatibility](#compatibility)
- [Verification](#verification)
- [Dev Note](#dev-note)
<a id="declaration"></a>
## Declaration
```yaml persistence-change
schemaVersion: 1
id: 2026-10-03-workflow-run-background
baseline: false
changes:
- root: "event:tool-workflow/run-start"
previous: "2026-09-11-initial"
after: "0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc"
decision: same-version
```
<a id="compatibility"></a>
## Compatibility
Existing records remain valid. Writers add the marker only from this change onward: in new records, absence means a foreground run, while background runs recorded earlier also lack it and keep their interrupted presentation after the Tool Step closes. The Web workflow node uses the marker to stop inferring interruption from the closed Tool Step of a run that belongs to a background job.
<a id="verification"></a>
## Verification
pnpm exec vitest run packages/workflow/tool-workflow/tests packages/client/ui-workflow-run/tests: 70 tests passed.
<a id="dev-note"></a>
## Dev Note
None.
@@ -1,86 +0,0 @@
{
"formatVersion": 2,
"roots": [
{
"key": "event:tool-workflow/run-start",
"kind": "event",
"event": "tool-workflow/run-start",
"surface": false,
"digest": "0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc",
"schema": {
"root": 0,
"nodes": [
{
"kind": "object",
"properties": [
{
"name": "data",
"type": 1,
"optional": false
},
{
"name": "ignorable",
"type": 2,
"optional": true
},
{
"name": "seq",
"type": 4,
"optional": false
},
{
"name": "time",
"type": 4,
"optional": false
},
{
"name": "type",
"type": 5,
"optional": false
}
],
"indices": []
},
{
"kind": "object",
"properties": [
{
"name": "background",
"type": 2,
"optional": true
},
{
"name": "name",
"type": 3,
"optional": false
},
{
"name": "runId",
"type": 3,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": true
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "primitive",
"type": "number"
},
{
"kind": "literal",
"value": "tool-workflow/run-start"
}
]
}
}
],
"types": []
}
@@ -1,48 +0,0 @@
---
description: "记录持久化类型更改及其兼容性确认。"
kind: persistence-change
---
# 2026-10-03-workflow-run-background
[English](2026-10-03-workflow-run-background.md) | 中文
## 概述
为 tool-workflow/run-start 增加可选的后台标记。
## 目录
- [声明](#declaration)
- [兼容性](#compatibility)
- [验证](#verification)
- [开发备注](#dev-note)
<a id="declaration"></a>
## 声明
```yaml persistence-change
schemaVersion: 1
id: 2026-10-03-workflow-run-background
baseline: false
changes:
- root: "event:tool-workflow/run-start"
previous: "2026-09-11-initial"
after: "0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc"
decision: same-version
```
<a id="compatibility"></a>
## 兼容性
已有记录仍然有效。写入方从本变更起才写入该标记:新记录中缺失表示前台运行;此前记录的后台运行同样缺少该标记,工具 Step 关闭后仍显示为已中断。Web 工作流节点使用该标记,不再根据已关闭的工具 Step 推断后台作业所属运行已中断。
<a id="verification"></a>
## 验证
pnpm exec vitest run packages/workflow/tool-workflow/tests packages/client/ui-workflow-run/tests:70 个测试通过。
<a id="dev-note"></a>
## 开发备注
无。
+25 -30
View File
@@ -17123,7 +17123,7 @@
"kind": "event",
"event": "tool-workflow/run-start",
"surface": false,
"digest": "0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc",
"digest": "c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058",
"schema": {
"root": 0,
"nodes": [
@@ -17137,7 +17137,7 @@
},
{
"name": "ignorable",
"type": 2,
"type": 3,
"optional": true
},
{
@@ -17161,32 +17161,27 @@
{
"kind": "object",
"properties": [
{
"name": "background",
"type": 2,
"optional": true
},
{
"name": "name",
"type": 3,
"type": 2,
"optional": false
},
{
"name": "runId",
"type": 3,
"type": 2,
"optional": false
}
],
"indices": []
},
{
"kind": "literal",
"value": true
},
{
"kind": "primitive",
"type": "string"
},
{
"kind": "literal",
"value": true
},
{
"kind": "primitive",
"type": "number"
@@ -21959,11 +21954,6 @@
"packages/subagent/tool-subagent/src/model-selection-state.ts:17"
]
},
{
"digest": "0dea366b90e202fdbb1a8d79aef14ffc6716b31000e5116309af6ae65d67cecc",
"names": [],
"sources": []
},
{
"digest": "0e10d7c096b47e61defe372a5fc8bd37d835199410eac44367f95c45082498bd",
"names": [],
@@ -22712,15 +22702,6 @@
"names": [],
"sources": []
},
{
"digest": "3cc643a957d3f0b1ecb0db5dcb5326939cdb35afe6a98b3261415b7d3c72e376",
"names": [
"packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData"
],
"sources": [
"packages/workflow/tool-workflow/src/types.ts:14"
]
},
{
"digest": "3cfc3a56502da1f8c6153c2c56657bab4b749056968634dd0991d325ec1c919f",
"names": [
@@ -23669,7 +23650,7 @@
"packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunEndData"
],
"sources": [
"packages/workflow/tool-workflow/src/types.ts:38"
"packages/workflow/tool-workflow/src/types.ts:36"
]
},
{
@@ -24070,7 +24051,7 @@
"packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentStartData"
],
"sources": [
"packages/workflow/tool-workflow/src/types.ts:22"
"packages/workflow/tool-workflow/src/types.ts:20"
]
},
{
@@ -24232,6 +24213,15 @@
"packages/hooks/hook-protocol/src/types.ts:48"
]
},
{
"digest": "a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c",
"names": [
"packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData"
],
"sources": [
"packages/workflow/tool-workflow/src/types.ts:14"
]
},
{
"digest": "a720757f704a2512e9d2f20da4a093e582472176236a75ab8b8eb7aaff17d85a",
"names": [],
@@ -24679,7 +24669,7 @@
"packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentEndData"
],
"sources": [
"packages/workflow/tool-workflow/src/types.ts:31"
"packages/workflow/tool-workflow/src/types.ts:29"
]
},
{
@@ -24700,6 +24690,11 @@
"packages/llm/llm/src/types.ts:105"
]
},
{
"digest": "c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058",
"names": [],
"sources": []
},
{
"digest": "c219efbdfd53b836786202ab4532913d555798f0a8615a4e9629736e3799fb70",
"names": [],
@@ -21,8 +21,8 @@
en: 99b67c86b6cf2031
zh: b3c5e998600525cb
/deepseek-ai-dsh-client-ui-workflow-run/use-this-package/state-and-completion:
en: d7b7c401ea53e2ca
zh: 8d3cdbff168d796d
en: de2dbff7df2294ed
zh: 97a03787caf02e2b
/deepseek-ai-dsh-client-ui-workflow-run/understand-the-implementation:
en: d41b996f3fb6727b
zh: 6b40fdf1db894fff
@@ -42,8 +42,8 @@
en: ca75c51c89c2c9b0
zh: de54f3467ef3b9be
/deepseek-ai-dsh-client-ui-workflow-run/known-limitations-and-deferred-work:
en: c7346285846dd51a
zh: 56d8171fcc6b1d80
en: d89a3727cfdc9db9
zh: 42ad9b22bb0c356e
/deepseek-ai-dsh-client-ui-workflow-run/known-limitations-and-deferred-work/dev-note:
en: e67f633dc6fe7773
zh: 3bb0c20c93f16f98
+2 -3
View File
@@ -33,7 +33,7 @@ Run and phase headers keep 6px gaps between their title, count, and status field
### State and completion
Completion updates the visible status immediately but delays its automatic close while focus remains inside the content. For a foreground run, a closed Turn or Step with missing terminal events presents the affected run or members as interrupted without changing the tool result. A background run outlives its Tool Step, so it stays running until its terminal events arrive.
Completion updates the visible status immediately but delays its automatic close while focus remains inside the content. A run or member without terminal events stays running after its Turn or Step ends normally, because a background run outlives its Tool Step. It is presented as interrupted, without changing the tool result, once its Turn is closed after a crash or at a fork boundary.
-----
@@ -86,8 +86,7 @@ None; this package neither assembles nor sends a provider request.
These limits define which runs produce records and what the node exposes; they are current package constraints.
- **Only top-level calls through `dsh-tool-workflow` produce these records** — nested PTC mode calls and direct `WorkflowEngine` consumers do not.
- **A background run lost without terminal events stays running** — its Step closure carries no liveness fact, and the session log records no job loss after a process crash or after a failed append disables recording for that run.
- **Background runs recorded before the background marker still show as interrupted** — their `tool-workflow/run-start` lacks the marker, so the node treats them as foreground runs.
- **A run lost without terminal events in a normally ended Turn stays running** — for example a background run whose process crashed during a later Turn, or a run whose recording stopped after a failed append; the session log carries no fact that marks it stopped.
- **Navigation is intentionally live-only** — terminal members remain visible for review but never expose a cold-session opener from this node.
- **The node shows run, phase, member identity, and status only** — scripts, outputs, errors, logs, usage, static topology, and controls remain outside this surface.
+2 -3
View File
@@ -33,7 +33,7 @@ kind: "package-reference"
### 状态与完成
完成状态会立即更新,但只要焦点仍位于展开内容内,自动折叠就会等待焦点离开。对前台运行,若所属轮次或步骤已关闭但终点事件缺失,界面把相应运行或成员显示为已中断,而不改写工具结果。后台运行的生命周期长于其工具 Step,因此在终点事件到达前保持运行中。
完成状态会立即更新,但只要焦点仍位于展开内容内,自动折叠就会等待焦点离开。缺少终点事件的运行或成员在所属轮次或步骤正常结束后仍显示为运行中,因为后台运行的生命周期长于其工具 Step。只有当所属轮次在崩溃后或于分叉边界被关闭时,界面才把它们显示为已中断,且不改写工具结果。
-----
@@ -86,8 +86,7 @@ kind: "package-reference"
这些限制定义哪些运行会产生记录、节点暴露什么;它们是当前包约束。
- **只有经 `dsh-tool-workflow` 发起的顶层调用会生成这些记录**:嵌套 PTC mode 调用和直接 `WorkflowEngine` 消费方不会生成。
- **未写终点事件就丢失的后台运行保持运行中**:其 Step 关闭不表示存活状态,进程崩溃或追加失败停止该运行的记录后,会话日志也不记录作业丢失。
- **后台标记出现之前记录的后台运行仍显示为已中断**:其 `tool-workflow/run-start` 缺少该标记,节点把它们视为前台运行。
- **在正常结束的轮次中未写终点事件就丢失的运行保持运行中**:例如进程在之后的轮次中崩溃的后台运行,或追加失败后停止记录的运行;会话日志中没有标记其已停止的事实。
- **导航刻意只面向实时运行**:终态成员继续保留供复盘,但本节点永不为其提供冷会话入口。
- **节点只显示运行、阶段、成员身份与状态**:脚本、输出、错误、日志、用量、静态拓扑与控制操作都不属于本界面。
@@ -47,8 +47,6 @@ interface WorkflowMemberState extends Omit<ToolWorkflowAgentStartData, 'runId'>
interface WorkflowState {
readonly name: string
/** A background run outlives its Tool Step, so a closed Location does not imply interruption. */
readonly background: boolean
readonly stopReason?: WorkflowStopReason
readonly members: readonly WorkflowMemberState[]
}
@@ -82,11 +80,16 @@ function statusFromOutcome(outcome: WorkflowAgentOutcome): WorkflowRunStatus {
}
}
function locationClosed(location: ConversationLocation): boolean {
if (location.kind === 'step') {
return location.step.status === 'closed' || location.turn.status === 'closed'
}
return location.kind === 'turn' && location.turn.status === 'closed'
/**
* A foreground run writes its run-end before its Tool Step ends, and a
* background run outlives that Step, so an ordinary Step or Turn closure says
* nothing about liveness. Only a Turn closed after a crash or at a fork
* boundary proves that a run without run-end stopped.
*/
function turnAbandoned(location: ConversationLocation): boolean {
if (location.kind !== 'step' && location.kind !== 'turn') return false
const reason = location.turn.end?.data.reason.kind
return reason === 'interrupted' || reason === 'forked'
}
function projectWorkflow(
@@ -95,8 +98,7 @@ function projectWorkflow(
): WorkflowRunChatData {
const state = context.state as WorkflowState
const interrupted = state.stopReason === undefined
&& !state.background
&& locationClosed(location)
&& turnAbandoned(location)
const phases = new Map<string, { phase: string | null; members: WorkflowRunMemberData[] }>()
for (const member of state.members) {
const phase = member.phase === undefined ? null : member.phase
@@ -165,7 +167,7 @@ export const workflowRunDefinition: ConversationNodeDefinition<WorkflowState> =
if (match.event.type !== 'tool-workflow/run-start') {
throw new Error('workflow-run start requires tool-workflow/run-start')
}
return { name: match.event.data.name, background: match.event.data.background === true, members: [] }
return { name: match.event.data.name, members: [] }
},
update: (context, match) => {
if (match.event.type === 'tool-workflow/agent-start') {
@@ -174,31 +174,13 @@ describe('workflow-run Conversation Definition', () => {
expect(workflowData(value)).toEqual(workflowData(assembler(events)))
})
it('shows missing terminal facts as interrupted only after the owning Location closes', () => {
it('keeps a background run running after its Tool Step and Turn end normally', () => {
const value = assembler([
at(1, 'turn/start', { turn: 1 }),
at(2, 'step/start', { turn: 1, step: 1 }),
at(3, 'tool-workflow/run-start', { runId: 'run-1', name: 'audit' }),
at(4, 'tool-workflow/agent-start', {
runId: 'run-1', seq: 1, label: 'worker', childId: 'child-1',
}),
])
expect(workflowData(value)?.status).toBe('running')
value.append(at(5, 'step/end', { turn: 1, step: 1 }))
value.flush()
expect(workflowData(value)).toMatchObject({
status: 'interrupted',
phases: [{ members: [{ status: 'interrupted' }] }],
})
})
it('keeps a background run running after its Tool Step and Turn close', () => {
const value = assembler([
at(1, 'turn/start', { turn: 1 }),
at(2, 'step/start', { turn: 1, step: 1 }),
at(3, 'tool-workflow/run-start', { runId: 'run-1', name: 'audit', background: true }),
at(4, 'step/end', { turn: 1, step: 1 }),
at(5, 'turn/end', { turn: 1, reason: { kind: 'completed' } }),
at(5, 'turn/end', { turn: 1, reason: { kind: 'aborted', reason: { kind: 'user' } } }),
at(6, 'tool-workflow/agent-start', {
runId: 'run-1', seq: 1, label: 'worker', childId: 'child-1',
}),
@@ -213,6 +195,27 @@ describe('workflow-run Conversation Definition', () => {
expect(workflowData(value)?.status).toBe('completed')
})
it.each(['interrupted', 'forked'] as const)(
'shows missing terminal facts as interrupted once a %s closer ends the owning Turn',
(kind) => {
const value = assembler([
at(1, 'turn/start', { turn: 1 }),
at(2, 'step/start', { turn: 1, step: 1 }),
at(3, 'tool-workflow/run-start', { runId: 'run-1', name: 'audit' }),
at(4, 'tool-workflow/agent-start', {
runId: 'run-1', seq: 1, label: 'worker', childId: 'child-1',
}),
])
expect(workflowData(value)?.status).toBe('running')
value.append(at(5, 'turn/end', { turn: 1, reason: { kind } }))
value.flush()
expect(workflowData(value)).toMatchObject({
status: 'interrupted',
phases: [{ members: [{ status: 'interrupted' }] }],
})
},
)
it('retains a zero-member run as its own completed node', () => {
const value = assembler([
at(1, 'turn/start', { turn: 1 }),
@@ -250,7 +253,7 @@ describe('workflow-run Conversation Definition', () => {
at(3, 'tool-workflow/agent-start', {
runId: 'turn', seq: 1, label: 'open', childId: 'child-1',
}),
at(4, 'turn/end', { turn: 1, reason: { kind: 'completed' } }),
at(4, 'turn/end', { turn: 1, reason: { kind: 'interrupted' } }),
])
expect(workflowData(interruptedTurn)?.status).toBe('interrupted')
})
@@ -42,8 +42,8 @@
en: 35950b2015800ec5
zh: 5ae6f5a5e53dc585
/deepseek-ai-dsh-tool-workflow/understand-the-implementation/durable-session-records:
en: f067ab6fc9a052cd
zh: a5087dc3cd44aae9
en: e5bbbb7788d6dcdf
zh: aa035f074f7ac021
/deepseek-ai-dsh-tool-workflow/understand-the-implementation/render-intent:
en: 389b3740ec1f5ac2
zh: 78430014f5e270e2
+1 -1
View File
@@ -75,7 +75,7 @@ A background call registers the run through `jobs.start` inside the job starter,
### Durable session records
For a root transport execution (`exec.parent` absent), the tool projects the run into the calling Agent's Session with four log-only events: run-start after `start()` returns, carrying `background: true` for a background run, member starts and endings filtered by `run.id`, then run-end only after the result is available and disposal reaches quiescence. Nested transport calls execute normally but write no record. The first failed Session append disables later recording for that run with one warning, leaving either no record or a legal continuous prefix without changing the tool result or cleanup.
For a root transport execution (`exec.parent` absent), the tool projects the run into the calling Agent's Session with four log-only events: run-start after `start()` returns, member starts and endings filtered by `run.id`, then run-end only after the result is available and disposal reaches quiescence. Nested transport calls execute normally but write no record. The first failed Session append disables later recording for that run with one warning, leaving either no record or a legal continuous prefix without changing the tool result or cleanup.
The engine's `workflow/phase` and `workflow/log` events have no per-line durable surface from this tool: the session log deliberately records run and member lifecycle only, and the Web transcript derives from those records. A background run's lines reach a human through the job observation record instead, which is transient by design.
+1 -1
View File
@@ -75,7 +75,7 @@ kind: "package-reference"
### 持久会话记录
对于根 transport 执行(`exec.parent` 缺省),工具会用四个 log-only 事件把运行投影到调用方 agent 的会话:`start()` 返回后写 run-start(后台运行带 `background: true`),只记录 `run.id` 匹配的成员开始与结束,并且只在结果可用且 dispose 完全停稳后写 run-end。嵌套 transport 调用照常执行,但不写任何记录。会话追加操作首次失败后,本运行会停止后续记录并只告警一次,留下空记录或合法连续前缀,同时不改变工具结果和清理。
对于根 transport 执行(`exec.parent` 缺省),工具会用四个 log-only 事件把运行投影到调用方 agent 的会话:`start()` 返回后写 run-start,只记录 `run.id` 匹配的成员开始与结束,并且只在结果可用且 dispose 完全停稳后写 run-end。嵌套 transport 调用照常执行,但不写任何记录。会话追加操作首次失败后,本运行会停止后续记录并只告警一次,留下空记录或合法连续前缀,同时不改变工具结果和清理。
引擎的 `workflow/phase` 与 `workflow/log` 事件在本工具没有逐行的持久面:会话日志刻意只记录 run 与成员生命周期,Web transcript 由这些记录派生。后台运行的这些行改经任务观察 record 抵达人类,而 record 的瞬态是设计使然。
+5 -10
View File
@@ -65,7 +65,7 @@ export const Config: z<Config> = z.object({
type ResolvedConfig = Required<Config>
interface WorkflowRecorder {
start(session: Session, run: WorkflowRun, background: boolean): void
start(session: Session, run: WorkflowRun): void
finish(runId: WorkflowRunId, stopReason: WorkflowStopReason): void
abandon(runId: WorkflowRunId): void
}
@@ -136,13 +136,8 @@ function createWorkflowRecorder(ctx: Context): WorkflowRecorder {
})
return {
start(session, run, background) {
const data: ToolWorkflowRunStartData = {
runId: run.id,
name: run.meta.name,
...background ? { background: true } : {},
}
if (append(session, 'tool-workflow/run-start', data)) {
start(session, run) {
if (append(session, 'tool-workflow/run-start', { runId: run.id, name: run.meta.name })) {
active.set(run.id, session)
}
},
@@ -291,7 +286,7 @@ function startBackgroundRun(
parent,
})
deps.mirror.start(run.id, job)
if (recordsRun) deps.recorder.start(parent.session, run, true)
if (recordsRun) deps.recorder.start(parent.session, run)
const done = run.result.then(async (result): Promise<JobOutcome> => {
try {
// Keep member listeners alive through disposal: an engine may
@@ -444,7 +439,7 @@ export function apply(ctx: Context, config: Config): void {
})
const recordsRun = exec.parent === undefined
// The engine publishes member events after start() returns and this run record is active.
if (recordsRun) recorder.start(parent.session, run, false)
if (recordsRun) recorder.start(parent.session, run)
// Bridge the tool's abort signal to the run: if the parent step is aborted while the
// script is in flight, cancel the whole run. The signal also enters the engine directly, but
+1 -3
View File
@@ -14,8 +14,6 @@ import type {
export interface ToolWorkflowRunStartData {
readonly runId: WorkflowRunId
readonly name: string
/** Present when the run belongs to a background job and outlives its Tool Step. */
readonly background?: true
}
/** Records one workflow member after its child Session is published. */
@@ -44,7 +42,7 @@ declare module '@deepseek-ai/dsh-session/types' {
interface SessionEventMap {
/**
* Opens one top-level workflow record.
* @param data - stable run identity, display name, and background marker.
* @param data - stable run identity and display name.
*/
'tool-workflow/run-start': ToolWorkflowRunStartData
/**
@@ -517,7 +517,6 @@ describe('dsh-tool-workflow', () => {
expect(session.snapshotEvents().map(event => event.type)).toEqual([
'tool-workflow/run-start', 'tool-workflow/agent-start', 'tool-workflow/agent-end', 'tool-workflow/run-end',
])
expect(session.snapshotEvents()[0]!.data).toEqual({ runId: 'run-1', name: 'audit', background: true })
// A straggling event after settlement finds no tracked run and is dropped.
engine.phase(runId, 'Late')
expect(retained(ctx, job.id, parent)).not.toContain('Late')