From 38922cbbb92646a88bb6141f3e5d72c6673db41a Mon Sep 17 00:00:00 2001 From: pku-xht Date: Tue, 11 Aug 2026 23:13:35 +0800 Subject: [PATCH] docs(tasks): clarify admission lifecycle --- .../2026-06-20-generic-long-running-tool-runtime.i18n.yaml | 4 ++-- .../2026-06-20-generic-long-running-tool-runtime.md | 2 +- .../2026-06-20-generic-long-running-tool-runtime.zh.md | 2 +- .../2026-08-11-bounded-background-task-admission.i18n.yaml | 4 ++-- .../bug-fix/2026-08-11-bounded-background-task-admission.md | 4 +++- .../2026-08-11-bounded-background-task-admission.zh.md | 4 +++- docs/config-catalog.i18n.yaml | 4 ++-- docs/config-catalog.md | 5 ++++- docs/config-catalog.zh.md | 5 ++++- packages/tasks/tasks-local/README.i18n.yaml | 4 ++-- packages/tasks/tasks-local/README.md | 2 +- packages/tasks/tasks-local/README.zh.md | 2 +- packages/tasks/tasks-local/src/index.ts | 5 ++++- packages/tasks/tasks-local/src/invariant.ts | 6 ++++-- 14 files changed, 34 insertions(+), 19 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml index e2a4a7d7b2..3964e59f77 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml @@ -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/architecture/2026-06-20-generic-long-running-tool-runtime.md -2026-06-20-generic-long-running-tool-runtime.md: 12e9b794e7e31c0dcbb45fac2f1f3d6852573b5a -2026-06-20-generic-long-running-tool-runtime.zh.md: 2d85b9ba824f9f1620a31c181e5d9a573c52875f +2026-06-20-generic-long-running-tool-runtime.md: 1edc3422c253e06178a5c8ebf68dfd4ef1289e31 +2026-06-20-generic-long-running-tool-runtime.zh.md: 8cfc2b2ec7ecce1a358b0be63ca52e9319bf362a diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md index 12e9b794e7..1edc3422c2 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md @@ -25,7 +25,7 @@ Long-running tools are producers. `dsh-tool-bash` adapts a `BashProcess` into in The literal types live on the [tasks subsystem page](../../../../docs/subsystems/tasks.md). A producer calls `ctx.tasks.start()` with a kind, label, optional owning `Agent`, optional positive `outputLimitBytes`, and a `run()` function. The runtime completes all failable preflight work before calling `run()` and invokes it once. After `run()` returns hooks, registration commits without another failable step; a producer cannot start work that lacks a collectable task id. -The process-local provider also owns bounded admission. Its positive-safe-integer `maxConcurrentTasksPerOwner` config defaults to `10`; `start()` derives each exact `Agent` object's active count from `running` and `stopping` records, while every unowned task shares one service bucket. Capacity rejection occurs before `run()` and id allocation, and producer `done` settlement is the only event that releases a stopping task's place. The provider does not queue, preempt, or retain a second mutable count. +The process-local provider also owns bounded admission, whose rationale is recorded in the [bounded background task admission decision](../bug-fix/2026-08-11-bounded-background-task-admission.md). Its positive-safe-integer `maxConcurrentTasksPerOwner` config defaults to `10`; `start()` derives each exact `Agent` object's active count from `running` and `stopping` records, while every unowned task shares one service bucket. Capacity rejection occurs before `run()` and id allocation, and producer `done` settlement is the only event that releases a stopping task's place. The provider does not queue, preempt, or retain a second mutable count. `outputLimitBytes` is producer-owned presentation policy, not a registry buffer. The registry validates and projects it unchanged into `TaskSnapshot`; generic control APIs apply the cap to complete model-facing output after adding their own status or notice metadata. Omitting it preserves the existing controller behavior, so the runtime does not impose a hidden default on unrelated producer families. diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md index 2d85b9ba82..8cfc2b2ec7 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md @@ -25,7 +25,7 @@ Status: implemented 字面类型见[任务子系统页面](../../../../docs/subsystems/tasks.md)。生产方调用 `ctx.tasks.start()`,传入 kind、label、可选的所属 `Agent`、可选的正数 `outputLimitBytes` 与一个 `run()` 函数。运行时会在调用 `run()` 前完成所有可能失败的预检工作,并且只调用一次。`run()` 返回钩子后,注册过程不会再执行可能失败的步骤而直接提交;生产方无法启动没有可收集 task id 的工作。 -进程内 Service provider 还拥有有界准入。它的 `maxConcurrentTasksPerOwner` 配置必须是正的安全整数,默认值为 `10`;`start()` 从 `running` 与 `stopping` 记录派生每个确切 `Agent` 对象的活动数量,而全部无 owner 任务共享一个服务级桶。容量拒绝发生在 `run()` 与 id 分配之前,处于 stopping 的任务只有在生产方 `done` 结算时才释放名额。Service provider 不排队或抢占任务,也不保留第二份可变计数。 +进程内 Service provider 还拥有有界准入,其理由记录在[有界后台任务准入决策](../bug-fix/2026-08-11-bounded-background-task-admission.md)中。它的 `maxConcurrentTasksPerOwner` 配置必须是正的安全整数,默认值为 `10`;`start()` 从 `running` 与 `stopping` 记录派生每个确切 `Agent` 对象的活动数量,而全部无 owner 任务共享一个服务级桶。容量拒绝发生在 `run()` 与 id 分配之前,处于 stopping 的任务只有在生产方 `done` 结算时才释放名额。Service provider 不排队或抢占任务,也不保留第二份可变计数。 `outputLimitBytes` 是生产方拥有的呈现策略,而非注册表缓冲区。注册表校验该值,并将其原样投影到 `TaskSnapshot`;通用任务控制器添加自身的状态或通知元数据后,再将该上限应用于完整的面向模型输出。省略该值时保持现有控制器行为,因此运行时不会向无关的生产方类别施加隐式默认值。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.i18n.yaml index 682b53e94e..42ea8361e2 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.i18n.yaml @@ -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/bug-fix/2026-08-11-bounded-background-task-admission.md -2026-08-11-bounded-background-task-admission.md: a7df21e5e981d80635220af4d50fa596967659bd -2026-08-11-bounded-background-task-admission.zh.md: 9d0041af05899a164b79b83c77d35fc8f25edff4 +2026-08-11-bounded-background-task-admission.md: 24512a87f554cd2d775fe76c5a6e5a700a51f2e4 +2026-08-11-bounded-background-task-admission.zh.md: dc3abaf4a64a4dc5fe5cacaabd3c29e278874646 diff --git a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.md b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.md index a7df21e5e9..24512a87f5 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.md +++ b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.md @@ -14,6 +14,8 @@ The process-local task registry already owns the exact task owner and the author `LocalTaskService` owns a `maxConcurrentTasksPerOwner` configuration field. It accepts positive safe integers, defaults to `10`, and is available through the provider's Cordis schema, the typed `agent-spine-demo` bundle, and the ACP app configuration. The bundle transports the value; the process-local provider owns its meaning. +The [generic task runtime decision](../architecture/2026-06-20-generic-long-running-tool-runtime.md) owns the shared Task lifecycle and control API; this note owns the process-local admission policy. + `start()` performs admission after the existing task-controller, task-field, and live-owner checks and before `TaskStart.run()`. It derives the active count from the registry's current records instead of storing another counter: | Record | Occupies capacity | Release fact | @@ -48,6 +50,6 @@ The task-provider suite covers the default and explicit limits, producer-before ## Consequences -One exact owner cannot keep creating Task-backed live resources indefinitely, and unrelated owners retain independent allowances. A slow stop can temporarily keep a bucket full, which is deliberate: the configured number bounds work that may still own resources, not cancellation requests. +One exact owner cannot keep creating Task-backed live resources indefinitely, and unrelated owners retain independent allowances. A slow stop keeps a bucket full until `done` settles, which is deliberate: the configured number bounds work that may still own resources, not cancellation requests. A producer whose `cancel` returns but whose `done` never settles holds one slot for the rest of the service lifetime and can stall teardown because the registry cannot safely infer resource release. Admission scans the process-local registry on each start. The cost grows with retained Task history, accepted in exchange for one state authority and a default limit small enough to bound the common live set. Terminal history remains available to existing reads and listings without consuming capacity. diff --git a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.zh.md b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.zh.md index 9d0041af05..dc3abaf4a6 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-11-bounded-background-task-admission.zh.md @@ -14,6 +14,8 @@ Status: implemented `LocalTaskService` 拥有 `maxConcurrentTasksPerOwner` 配置字段。它只接受正的安全整数,默认值为 `10`,并通过 Service provider 的 Cordis schema、typed `agent-spine-demo` 组合包与 ACP 应用配置提供。组合包只传输该值;其含义归进程内 Service provider 所有。 +[通用任务运行时决策](../architecture/2026-06-20-generic-long-running-tool-runtime.md)拥有共享 Task 生命周期与控制 API;本记录只拥有进程内准入策略。 + `start()` 在现有任务控制器、任务字段与存活 owner 检查之后、`TaskStart.run()` 之前执行准入。它从注册表当前记录派生活动数量,而不保存另一份计数: | 记录 | 占用容量 | 释放事实 | @@ -48,6 +50,6 @@ owner 与服务释放保留现有顺序:请求取消,在生产方释放资 ## 后果 -单个确切 owner 无法再无限创建由 Task 承载的实时资源,无关 owner 则保留独立额度。缓慢停止可能暂时让桶保持满载,这是有意行为:配置值限制的是仍可能拥有资源的工作,而不是取消请求。 +单个确切 owner 无法再无限创建由 Task 承载的实时资源,无关 owner 则保留独立额度。缓慢停止会让桶保持满载直到 `done` 结算,这是有意行为:配置值限制的是仍可能拥有资源的工作,而不是取消请求。如果生产方的 `cancel` 返回后始终不结算 `done`,它会在服务剩余生命周期内持续占用一个名额并阻塞销毁,因为注册表无法安全推断资源已经释放。 每次启动都会扫描进程内注册表。成本随保留的 Task 历史增长;为了保持单一状态权威,并利用足以约束常见实时集合的较小默认值,接受这一代价。终止历史仍可供现有读取与列表使用,但不消耗容量。 diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index 52a8c284be..154e14cecb 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.i18n.yaml @@ -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/config-catalog.md -config-catalog.md: f88bb888367e73188e20a1e2fbefdf3c4a926798 -config-catalog.zh.md: 5f3cbd274ec8704581642b74e7583249a0db6172 +config-catalog.md: 02abc64cfaf677e904ec585c28cbe6053444535c +config-catalog.zh.md: 8542403f68098b9e3939fce6ca033c39b1ecaf27 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index f88bb88836..02abc64cfa 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -2039,7 +2039,10 @@ Source: [`packages/core/system-prompt/src/index.ts:186`](../packages/core/system ```ts config-catalog /** Configuration for the process-local task registry. */ export interface Config { - /** Maximum `running` plus `stopping` tasks per exact owner; omission defaults to 10. */ + /** + * Maximum `running` plus `stopping` tasks per exact owner or in the shared unowned bucket; + * omission defaults to 10. + */ maxConcurrentTasksPerOwner?: number } ``` diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 5f3cbd274e..8542403f68 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -2041,7 +2041,10 @@ export interface Config { ```ts config-catalog /** Configuration for the process-local task registry. */ export interface Config { - /** Maximum `running` plus `stopping` tasks per exact owner; omission defaults to 10. */ + /** + * Maximum `running` plus `stopping` tasks per exact owner or in the shared unowned bucket; + * omission defaults to 10. + */ maxConcurrentTasksPerOwner?: number } ``` diff --git a/packages/tasks/tasks-local/README.i18n.yaml b/packages/tasks/tasks-local/README.i18n.yaml index ee3158db1c..7901d983b7 100644 --- a/packages/tasks/tasks-local/README.i18n.yaml +++ b/packages/tasks/tasks-local/README.i18n.yaml @@ -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 packages/tasks/tasks-local/README.md -README.md: b486438eb2f73728361a6140fdec33603a2ace45 -README.zh.md: ecc9104e4145add9448d43e1eb2565a3fc7e09c3 +README.md: f558676b36bb5462453bde553eac27b458e1268e +README.zh.md: ed8f2b1220692a00c09b6605b18231f5263f00e5 diff --git a/packages/tasks/tasks-local/README.md b/packages/tasks/tasks-local/README.md index b486438eb2..f558676b36 100644 --- a/packages/tasks/tasks-local/README.md +++ b/packages/tasks/tasks-local/README.md @@ -31,4 +31,4 @@ No direct invalidation; the named consumer owns any request-prefix changes. ## Known Limitations and Deferred Work - **Tasks are process-local** — records die with the harness process; durable or cross-restart execution needs a separate backend implementing the seam. -- **A silently ineffective cancel can stall teardown** — only an explicit throw can be force-failed safely. +- **A silently ineffective cancel can stall teardown and hold capacity** — if `cancel` returns without settling `done`, the registry cannot distinguish it from a slow stop; the task keeps one bucket slot for the rest of the service lifetime, and only an explicit throw can be force-failed safely. diff --git a/packages/tasks/tasks-local/README.zh.md b/packages/tasks/tasks-local/README.zh.md index ecc9104e41..ed8f2b1220 100644 --- a/packages/tasks/tasks-local/README.zh.md +++ b/packages/tasks/tasks-local/README.zh.md @@ -31,4 +31,4 @@ ## 已知限制与暂缓事项 - **任务只存在于进程本地**:记录会随 harness 进程终止而消失;持久或跨重启执行需要一个单独实现该 seam 的后端。 -- **静默无效的取消可能使销毁过程停滞**:只有显式抛出异常才能安全地强制标为失败。 +- **静默无效的取消可能使销毁过程停滞并持续占用容量**:如果 `cancel` 返回后始终未结算 `done`,注册表就无法将其与缓慢停止区分开;该任务会在服务剩余生命周期内持续占用一个桶名额,只有显式抛出异常才能安全地强制标为失败。 diff --git a/packages/tasks/tasks-local/src/index.ts b/packages/tasks/tasks-local/src/index.ts index 53ef9b2450..3d75f8dca4 100644 --- a/packages/tasks/tasks-local/src/index.ts +++ b/packages/tasks/tasks-local/src/index.ts @@ -29,7 +29,10 @@ const DEFAULT_MAX_CONCURRENT_TASKS_PER_OWNER = 10 /** Configuration for the process-local task registry. */ export interface Config { - /** Maximum `running` plus `stopping` tasks per exact owner; omission defaults to 10. */ + /** + * Maximum `running` plus `stopping` tasks per exact owner or in the shared unowned bucket; + * omission defaults to 10. + */ maxConcurrentTasksPerOwner?: number } diff --git a/packages/tasks/tasks-local/src/invariant.ts b/packages/tasks/tasks-local/src/invariant.ts index 21d00ed155..2c96c5338a 100644 --- a/packages/tasks/tasks-local/src/invariant.ts +++ b/packages/tasks/tasks-local/src/invariant.ts @@ -15,8 +15,10 @@ export const name = 'tasks-local-invariant' export const inject = ['invariants'] /** - * No runtime invariant: the Service Definition companion in `@deepseek-ai/dsh-tasks` already - * validates every registry snapshot this implementation publishes. + * No runtime invariant: the Service Definition companion in `@deepseek-ai/dsh-tasks` validates + * every published snapshot. `LocalTaskService.start()` enforces this provider's configured + * admission limit synchronously before producer execution; repeating that aggregate check here + * would expose provider-private configuration solely to this companion. */ const install: InvariantInstaller = () => {}