Skip to content

refactor(tools): collapse the opaque-tool watchdog to a standard timeout (#1472 step 5)#1487

Merged
Aaronontheweb merged 3 commits into
devfrom
refactor/collapse-opaque-tool-watchdog
Jun 25, 2026
Merged

refactor(tools): collapse the opaque-tool watchdog to a standard timeout (#1472 step 5)#1487
Aaronontheweb merged 3 commits into
devfrom
refactor/collapse-opaque-tool-watchdog

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Collaborator

What & why

#1472 step 5 — "addition through subtraction" of the tool-liveness machinery, at the opaque-tool boundary.

After #1481, the off-thread, volatile-polling StreamingToolWatchdog was used for exactly one thing: bounding opaque tool calls with a flat wall-clock budget. In WallClock mode its two shared Volatile fields (lastActivity, budgetTicks) are written once and never change, so the 1-second polling timer only ever computes "has ToolExecutionTimeout elapsed since start?" — which is exactly what a CancellationTokenSource timeout token does. This was the "why is this volatile instead of actor state?" smell.

Change

Opaque tools are now bounded by a TimeProvider-driven timeout token (new CancellationTokenSource(timeout, timeProvider)) linked to the caller token, drained through the same DrainToCompletionAsync helper self-monitoring tools already use:

Deletes StreamingToolWatchdog, ToolWatchdogBudget, ToolWatchdogResetMode.

Direction was chosen deliberately over a literal "actor-owned timer": the work runs off the actor thread, so an actor-owned timer would add per-call cancellation-token plumbing across that boundary — more moving parts, against the goal. Collapsing to a standard timeout removes the volatile rather than relocating it.

Folds in three deferred #1481 review follow-ups

No behavior change

Opaque tools still time out at the same wall-clock budget, same message, per-tool isolation. Self-monitoring tools untouched. The spec-of-record (Per-call liveness by tool class in the streaming-tool-call-execution change) already describes opaque tools as "one wall-clock budget" — only the mechanism changed, so no spec edit.

Verification

  • Full Netclaw.Actors.Tests green (2474). Existing pipeline tests already cover opaque wall-clock timeout (FakeTimeProvider) and concurrent independence; added one for the stream-without-a-completion-item contract. Gutted the watchdog unit tests, keeping the INetclawTool default-adapter test.
  • dotnet slopwatch analyze clean; copyright headers present.
  • Net −273 LOC (94 insertions / 367 deletions).

Part of #1472.

…out (#1472 step 5)

After #1481 the off-thread StreamingToolWatchdog was used for exactly one thing:
bounding opaque tool calls with a flat wall-clock budget. In that mode its two
shared Volatile fields were written once and never changed, so the 1s polling timer
only ever computed "has the budget elapsed since start?" — which is exactly what a
CancellationTokenSource timeout token does. (The "why is this volatile instead of
actor state?" smell.)

Replace it with a TimeProvider-driven timeout token — new CancellationTokenSource(
timeout, timeProvider) linked to the caller token — draining through the same
DrainToCompletionAsync helper self-monitoring tools already use. A budget trip is
converted to the same TimeoutException message; caller (turn/user) cancellation is
rethrown unchanged (preserves #1481). Per call, so a slow tool times out without
affecting siblings, and TimeProvider-virtualizable so tests stay deterministic.

- Delete StreamingToolWatchdog, ToolWatchdogBudget, ToolWatchdogResetMode — removes
  the volatile, the polling timer, and the dead ResetOnItem/FirstItem/InterItem
  machinery (review follow-up #13).
- Unify the two drain paths on DrainToCompletionAsync (review follow-up #11).
- Point SpawnAgentStreamingTests at the production drain instead of the watchdog
  (review follow-up #12).
- Opaque wall-clock timeout + concurrent independence are already covered at the
  pipeline level (FakeTimeProvider); add one test for the stream-without-a-completion-
  item contract; gut the watchdog unit tests, keeping the INetclawTool default-adapter
  test.

No behavior change: opaque tools still time out at the same wall-clock budget with the
same message and per-tool isolation; self-monitoring tools are untouched. The spec-of-
record ("Per-call liveness by tool class") already says opaque tools get one wall-clock
budget — only the mechanism changed.

Net: roughly -150 LOC. Part of #1472.
@Aaronontheweb Aaronontheweb added tools Issues related to agent tools: file_read, web_search, shell_execute, image processing, etc. reliability Retries, resilience, graceful degradation labels Jun 25, 2026

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Aaronontheweb Aaronontheweb enabled auto-merge (squash) June 25, 2026 17:01
@Aaronontheweb Aaronontheweb merged commit 06fa951 into dev Jun 25, 2026
21 checks passed
@Aaronontheweb Aaronontheweb deleted the refactor/collapse-opaque-tool-watchdog branch June 25, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reliability Retries, resilience, graceful degradation tools Issues related to agent tools: file_read, web_search, shell_execute, image processing, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant