Files
deepseek-harness/packages/tasks/tasks-local
Yichen Jiang 15d452d7ea fix(tasks): correct the change-feed contract and its documentation
Review found the `onTasksChanged` teardown reasoning inverted. The comment
claimed every registration is an effect on the registry's own fiber, so
listeners would be gone before service disposal empties the store — but the
traceable proxy rebinds `this.ctx` to the CALLER, which this package's own
HMR-safety test already proves. The only shipped consumer registers from the
api-proxy mux stream, so it was still listening and simply kept the rows it
last received. Service disposal now announces the emptied set, and teardown
announces its stopping transition immediately instead of leaving an observer
on `running` for however long a slow producer takes to release.

Two documentation claims were false in the opposite direction: the Agent Note
and the ui-task README both said an unowned task is invisible in the header,
while `list(caller)` returns unowned tasks to every caller, the carrier fans
their changes out to every subscribed session, and this PR's own test asserts
exactly that. The note even contradicted itself two sections earlier. Both
sides now state the real asymmetries — another session's tasks, and the
process-local registry emptying on restart.

The "no Web path calls the consuming `ctx.tasks.read()`" invariant claimed a
test that did not exist; the carrier suite's producer had no `readOutput` at
all, so a stray read would have failed nothing. Its producer now counts cursor
consumption and the lifecycle and baseline paths both assert zero.

Also: a session created after the mux opened now receives the task baseline it
missed, the popover samples its clock when it opens rather than at mount, and
a failed task's unbounded producer detail elides instead of widening the row.
2026-08-10 13:37:18 +08:00
..

@deepseek-ai/dsh-tasks-local

English | 中文

Process-local implementation of the @deepseek-ai/dsh-tasks registry contract: LocalTaskService keeps every record in memory, issues per-kind <kind>-N ids, and hands out fresh snapshots, never live state. It has no config; load it as a plugin and it registers as ctx.tasks.

Lifecycle

Tasks belong to their owner and backend, not the producer tool fiber, so producer and surface reloads do not stop them. The first task for an owner attaches one awaited effect to the exact Agent scope. Owner disposal cancels that object's tasks, awaits producer quiescence, and removes their snapshots; reused agent or session ids cannot redirect an old cleanup.

Service disposal closes listeners, cancels all live tasks, awaits their records, and detaches effects from surviving owner scopes. If teardown cancellation throws, the service force-fails the record and warns that work may be orphaned instead of deadlocking. A cancellation that returns but never settles done remains indistinguishable from a slow stop and can stall teardown.

Settlement is first-wins: the earliest terminal outcome — producer settlement, a rejected done contained as failed, or a teardown force-failure — records once, notifies listeners once with per-listener containment, and releases waiters. Pending waits mark the task reported before listeners run so completion surfaces do not duplicate notices.

Model Experience

Indirectly, through producer plugins and dsh-tool-tasks, which render task ids, output, status, cancellation, and completion notices.

KV Cache effect

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.