docs(tasks): clarify admission lifecycle

This commit is contained in:
pku-xht
2026-08-11 23:13:35 +08:00
parent e3ac9e85e5
commit 38922cbbb9
14 changed files with 34 additions and 19 deletions

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 .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

View File

@@ -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.

View File

@@ -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`;通用任务控制器添加自身的状态或通知元数据后,再将该上限应用于完整的面向模型输出。省略该值时保持现有控制器行为,因此运行时不会向无关的生产方类别施加隐式默认值。

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 .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

View File

@@ -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.

View File

@@ -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 历史增长;为了保持单一状态权威,并利用足以约束常见实时集合的较小默认值,接受这一代价。终止历史仍可供现有读取与列表使用,但不消耗容量。

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/config-catalog.md
config-catalog.md: f88bb888367e73188e20a1e2fbefdf3c4a926798
config-catalog.zh.md: 5f3cbd274ec8704581642b74e7583249a0db6172
config-catalog.md: 02abc64cfaf677e904ec585c28cbe6053444535c
config-catalog.zh.md: 8542403f68098b9e3939fce6ca033c39b1ecaf27

View File

@@ -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
}
```

View File

@@ -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
}
```

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 packages/tasks/tasks-local/README.md
README.md: b486438eb2f73728361a6140fdec33603a2ace45
README.zh.md: ecc9104e4145add9448d43e1eb2565a3fc7e09c3
README.md: f558676b36bb5462453bde553eac27b458e1268e
README.zh.md: ed8f2b1220692a00c09b6605b18231f5263f00e5

View File

@@ -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.

View File

@@ -31,4 +31,4 @@
## 已知限制与暂缓事项
- **任务只存在于进程本地**:记录会随 harness 进程终止而消失;持久或跨重启执行需要一个单独实现该 seam 的后端。
- **静默无效的取消可能使销毁过程停滞**只有显式抛出异常才能安全地强制标为失败。
- **静默无效的取消可能使销毁过程停滞并持续占用容量**:如果 `cancel` 返回后始终未结算 `done`,注册表就无法将其与缓慢停止区分开;该任务会在服务剩余生命周期内持续占用一个桶名额,只有显式抛出异常才能安全地强制标为失败。

View File

@@ -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
}

View File

@@ -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 = () => {}