Allow the shell tool timeout to be overridden per invocation via a timeout argument #112
Labels
No labels
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Bounty
$100
Bounty
$1000
Bounty
$10000
Bounty
$20
Bounty
$2000
Bounty
$250
Bounty
$50
Bounty
$500
Bounty
$5000
Bounty
$750
MoSCoW
Could have
MoSCoW
Must have
MoSCoW
Should have
Needs Feedback
Points
1
Points
13
Points
2
Points
21
Points
3
Points
34
Points
5
Points
55
Points
8
Points
88
Priority
Backlog
Priority
CI Blocker
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
State
Completed
State
Duplicate
State
In Progress
State
In Review
State
Paused
State
Unverified
State
Verified
State
Wont Do
Type
Automation
Type
Bug
Type
Discussion
Type
Documentation
Type
Epic
Type
Feature
Type
Legendary
Type
Refactor
Type
Support
Type
Task
Type
Testing
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
cleveragents/cleveractors-core#112
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Metadata
Commit Message:
feat(agents): allow per-invocation timeout override on the shell toolBranch:
feature/m1-shell-timeout-overrideBackground and context
The tool agent
timeoutconfig field (Actor Configuration Standard §4.5, default1second) bounds "the maximum execution time in seconds for any single tool invocation." Today it is a static, per-agent value read once at construction (cleveractors.agents.tool.ToolAgent.__init__setsself.timeout = cfg.get("timeout", 1)) and applied to everyshellinvocation viaasyncio.wait_for(process.communicate(), timeout=self.timeout)incleveractors.agents.tool.ToolAgent._execute_shell_command.Issue #82 (
feat(agents): surface timeout budget in tool schemas and errors, closed) made this constraint visible to the LLM: it added timeout language to theshell/http_requestschema descriptions and made the timeoutExecutionErrorname thetimeoutconfig field. It deliberately stopped there — its own scope note states "No change to enforcement mechanics …self.timeoutremains the sole config field."That leaves a gap that actively harms autonomous runs. The guidance #82 surfaces tells the model to "increase the tool agent's
timeoutconfig value", but an LLM driving theshelltool has no runtime affordance to edit its own agent's YAML config — the only channel it has is the tool-call arguments. So when a command legitimately needs more than the default 1 second, the model is told to do something it cannot do, and it flails trying to satisfy the instruction through the only lever it has.A real failure trace (the motivating case):
The second error is the model attempting to "raise the timeout" the only way it can — by threading
timeout 120into the call — which_execute_shell_commandconcatenates onto the command line (cmd_str = command + " " + " ".join(...)), producing a nonsense command. The model has the right intent and no correct mechanism.This issue closes that gap by making the shell timeout overridable per invocation through a first-class
timeouttool argument, turning #82's advice into something the agent can actually act on.Current behavior
cleveractors.agents.tool.ToolAgent._execute_shell_commandalways bounds execution byself.timeout(the single static per-agent value); there is no per-call override.cleveractors.agents.llm_tools._BUILTIN_TOOL_SCHEMAS["shell"]exposes onlycommandandargs(a list of "Additional command-line arguments"). There is notimeoutparameter the model can set, yet the schema description tells the model to "increasetimeoutin the tool agent config."args, a model trying to follow that advice ends up injecting timeout-related tokens intoargs, which are appended verbatim to the shell command string — corrupting the command (see trace above).ExecutionErrorraised by_execute_shell_commanddirects the reader to edit agent config — an action available to a human config author but not to the LLM at runtime.Expected behavior
_BUILTIN_TOOL_SCHEMAS["shell"]exposes an optionaltimeoutparameter (a number, seconds). It is NOT added to the schema'srequiredlist. Its description states it overrides the agent's default timeout for this single invocation and is bounded by a maximum.ToolAgent._execute_shell_commanduses a per-invocationtimeouttaken from the call arguments when present and valid, in place ofself.timeout, for theasyncio.wait_forbound.timeoutis supplied, behavior is byte-for-byte unchanged: the call is bounded byself.timeout(default1s per §4.5).timeoutis a control argument: it is read distinctly from the positionalargslist and MUST NOT be concatenated into the executed command string.timeoutthat is non-numeric,<= 0, or above the allowed ceiling raisesExecutionErrorwith an explanatory message, before any subprocess is spawned.ExecutionErrormessage is rewritten to point the model at the actionable lever — retrying with a largertimeoutargument (up to the ceiling) — rather than instructing it to edit config it cannot reach at runtime.Design points to settle in the ADR / review (not yet fixed by this issue):
timeout. Proposal: a bounded maximum so a model cannot pin a worker indefinitely — either a fixed cap or a newmax_timeoutagent config field. Absent consensus, default to a fixed, documented cap.(0, ceiling]).Out of scope (mirroring #82's scoping, noted for awareness):
cleveractors.agents.tool.ToolAgent._http_request_toolshares the sameself.timeoutand has the same limitation. Extending the per-invocation override tohttp_requestis a parallel follow-up issue, not addressed here, to keep this change atomic.Acceptance criteria
_BUILTIN_TOOL_SCHEMAS["shell"]["parameters"]["properties"]contains an optional numerictimeoutproperty whose description states it overrides the agent default for this invocation (bounded by the ceiling);timeoutis absent from that schema'srequiredarray.timeoutsupplied,ToolAgent._execute_shell_commandboundsasyncio.wait_forby that value rather thanself.timeout(verifiable: a call giventimeoutlarger thanself.timeoutcompletes a command that runs longer thanself.timeout).timeoutsupplied, a command exceedingself.timeoutstill raises the timeoutExecutionError— default behavior is unchanged.timeoutvalue never appears in the executed command string (verifiable: ashellcall with atimeoutargument runs the intended command, not a corrupted one).timeoutthat is non-numeric,<= 0, or above the ceiling raisesExecutionErrorbefore a subprocess is created, with a message naming the accepted range.ExecutionErrormessage instructs retrying with a largertimeoutargument and no longer instructs editing the agent config.nox(all default sessions) is green andnox -s coverage_reportstays ≥ 97%.Supporting information
docs/index.md) — definestimeoutas "Maximum execution time in seconds for any single tool invocation." A per-invocation override is an extension of this field's semantics and therefore requires the ADR-before-code process (see Subtasks): a decision record extendingdocs/adr/ADR-2030-tool-calling-spec-extensions.md, then a §4.5 spec update via the standard spec-revision procedure (bump the document Version and add a §21.1 Revision History row attributing the change to the ADR).docs/adr/ADR-2030-tool-calling-spec-extensions.md— D-1 establishes_BUILTIN_TOOL_SCHEMASas LLM-facing hints; D-3 (file_readmax_chars) establishes the precedent that an optional tool argument may be added because §4.5.1 "allows additional arguments to be handled." A per-invocationtimeoutfollows the same pattern.67972f0:cleveractors.agents.tool.ToolAgent.__init__(setsself.timeout = cfg.get("timeout", 1))cleveractors.agents.tool.ToolAgent._execute_shell_command(appliesself.timeout; buildscmd_str; raises the timeoutExecutionError)cleveractors.agents.tool.ToolAgent._execute_tool(passes the full tool-callargsdict into_execute_shell_command)cleveractors.agents.llm_tools._BUILTIN_TOOL_SCHEMAS(theshellschema)Subtasks
ADR-2030for a per-invocationshelltimeoutoverride (including the ceiling decision); submit for review and get it accepted before writing codetimeoutproperty to_BUILTIN_TOOL_SCHEMAS["shell"]and update its description to describe the override and its boundToolAgent._execute_shell_command, validate and read a per-invocationtimeoutfrom the tool-call args, use it for theasyncio.wait_forbound with fallback toself.timeout, and ensure it is never concatenated intocmd_strExecutionErrormessage to direct the model to retry with a largertimeoutargument (up to the ceiling)timeouthonored (a long command succeeds when overridden, fails at the default); absent → default behavior; invalid values rejected before spawn; thetimeoutargument is not injected into the command; updated error-message wordingshelltool with a per-invocationtimeoutagainst a real subprocessnox -s coverage_reportnox(all default sessions), fix any errorsDefinition of Done
This issue is complete when:
Duplicate o ticket 111