feat(adk): let shell backends own the command timeout - #1229
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## alpha/10 #1229 +/- ##
===========================================
Coverage ? 81.90%
===========================================
Files ? 219
Lines ? 37230
Branches ? 0
===========================================
Hits ? 30493
Misses ? 4538
Partials ? 2199 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
filesystem.Backend may be remote, so an agent-generated bash timeout can only predict the command's own runtime, not the network hop or sandbox provisioning in front of it. ExecuteRequest.Timeout hands that limit to the backend, which is the only side that knows when the command actually started. Complete the handover in both directions: - Add ExecuteResponse.TimedOut so a backend can report that it stopped the command on that budget. The note takes precedence over the exit-code note in both the buffered and streaming renderers: a stopped command is usually also signal-killed, and the timeout is the fact the agent can act on. - Document the contract instead of re-taking it. Timeout is the only bound on command execution, and Shell/StreamingShell now describe two parallel stop paths (ctx cancellation, Timeout expiry) rather than the stale ctx-only claim. No framework-side deadline: a ctx timeout equal to Timeout would spend the provisioning budget and fire early, reintroducing the bug this fixes. - Split the tool description per mode. The timeout argument bounds the caller's wait in auto-background mode and the command's own runtime in always-foreground mode; the description is fixed at construction, while the arg schema is derived once from struct tags, so the schema stays mode-neutral and defers to it. - Collapse the three-value run-mode enum into a single bool. The explicit background member was redundant: RunInBackground short-circuits before any foreground wait, so its timeout is ignored either way. Also document the non-positive ForegroundTimeoutMs sentinel where it is read, and that it makes ShouldAutoBackground unreachable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mrh997
approved these changes
Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
filesystem.Backendmay be remote. When an agent generates atimeoutfor a foreground Bash command, it can only predict the command's runtime — not the network hop or the sandbox provisioning in front of it. Using that number as a caller-side wait therefore fires early on a slow link and cancels a command that had barely started.ExecuteRequest.Timeouthands the limit to the backend, which is the only side that knows when the command actually started, and so the only side that can exclude transport and environment setup from it.What changed
The contract (
adk/filesystem/backend.go)ExecuteRequest.Timeout— an optional limit on the command's own runtime. The doc now states the handover plainly: a caller may rely on this as the only bound on command execution, so a backend that ignores it will block its caller untilctxis canceled.ExecuteResponse.TimedOut(new) — the backend reports that it stopped the command on that budget. Scoped deliberately: transport/RPC deadlines,ctxcancellation and environment-setup failures are errors, not timeouts.Outputshould still carry whatever the command produced before it was stopped.Shell/StreamingShellnow describe two parallel stop paths —ctxcancellation andTimeoutexpiry — replacing the claim that a command is stopped solely viactx, which stopped being true onceTimeoutexisted.There is intentionally no framework-side backstop: a
context.WithTimeout(ctx, *req.Timeout)fallback would spend the provisioning budget and fire early, reintroducing exactly the bug this fixes, and no grace period is definable without knowing the same unknowables.ctxis already the true total-wallclock bound.Rendering
TimedOuttakes precedence over the exit-code note in both the buffered and streaming renderers. A stopped command is usually also signal-killed, so it carries a non-zero exit code — the timeout is the fact the agent can act on. The streaming paths accumulate it across chunks, since the terminal chunk may carry no output.Prompts
The
timeoutargument means two different things depending on the mode, so the tool description is now selected per mode, while the arg schema — derived once from struct tags — stays mode-neutral and defers to it.timeoutNeither variant leaks whether the backend is local or remote — that is a deployment detail the model cannot act on, and it would be wrong for a local backend sharing the same code path.
Simplification
The three-value run-mode enum collapsed into a single
backendOwnsCommandTimeoutbool. The explicit-background member was redundant:RunInBackgroundshort-circuits before any foreground wait, so its timeout is ignored either way.Docs
LocalBackgroundConfig.AlwaysForegroundnow says that setting it disables the Manager's foreground timer, leavingctxas the only bound, and thatShouldAutoBackgroundis therefore never consulted.ForegroundTimeoutMssentinel is documented onbackgroundlocal.Config/Input, where it is actually read, and theForegroundTimeoutMs↔ShouldAutoBackgroundcoupling is noted on the managed-tool config.Timeout behavior
run_in_backgroundtimeoutAlwaysForeground=falsefalseForegroundTimeoutMs— the wait before stop or auto-background handoffAlwaysForeground=truefalseExecuteRequest.Timeout; foreground timer disabledtrueCompatibility
ExecuteRequest.Timeout,ExecuteResponse.TimedOutandLocalBackgroundConfig.AlwaysForegroundare all optional; the zero values preserve current behavior.TimedOutrenders exactly as before.AlwaysForegroundis not yet plumbed throughadk/prebuilt/deep'sLocalShellConfig, sodeepcallers cannot reach the new mode.Tests
New coverage:
TimedOutrendering (unit, buffered end-to-end, and streaming where the timeout arrives on an output-less terminal chunk),execTerminalNoteprecedence, thebackendOwnsCommandTimeouttruth table, the explicit-background branch ofmanagedRunInput, and per-mode description selection including the custom-description override.go test -race ./adk/... -count=1go vet ./adk/...gofmt -s -l ./adk/golangci-lint run ./adk/...— 0 issues