Ephemeral tool-call ToolAgent instances do not inherit tools_max_timeout/shell_max_timeout from the parent LLM agent #140

Open
opened 2026-08-21 14:15:16 +00:00 by CoreRasurae · 0 comments
Member

Metadata

  • Commit Message: feat(agents): inherit tool timeout ceilings from parent LLM agent
  • Branch: feature/m1-inherit-tool-timeout-ceilings

Background and context

The Actor Configuration Standard (docs/index.md §4.5) defines three tool-agent
config fields that bound tool execution: timeout (default 1s), and — added by
ADR-2030 D-9 — the generic tools_max_timeout (default 120s, the ceiling a
per-invocation timeout tool-call argument may request) and the shell-only
shell_max_timeout refinement. §4.4 (LLM agents) defines no equivalent fields at
all — timeout/tools_max_timeout/shell_max_timeout simply do not appear in
the type: llm config table.

Despite that, cleveractors.agents.llm.LLMAgent dispatches every built-in tool
call (shell, http_request, etc.) by constructing an ephemeral
cleveractors.agents.tool.ToolAgent per call — three near-identical call sites
inside LLMAgent._execute_tool_loop at commit 0880b02 (the normal per-round
path, the budget-exhaustion synthesis retry, and the post-loop stuck-model
synthesis retry). Each site builds a small config mapping for that ephemeral
agent (tool_cfg / tool_config / tool_config_s), including
"timeout": self.config.get("timeout", 1) — i.e. it already reads timeout
off the parent LLM agent's own config, even though §4.4 never documents
that key as meaningful there.

None of the three sites do the same for tools_max_timeout or
shell_max_timeout, and this issue is scoped to those two fields only
timeout already inherits correctly and is out of scope here, referenced
below only as context/precedent. cleveractors.agents.tool.ToolAgent.__init__
always falls back to its own hardcoded defaults (120.0 / None) for the
ephemeral agent, regardless of what the parent type: llm agent's config
contains under those keys. Since the ephemeral agent's config is synthesized
internally and never authored directly by an end user, the parent LLM agent's
own config is the only place an actor author could realistically express "let
this agent's internally-dispatched shell/http_request calls request a higher
per-call timeout override" — and today there is no way to do that: every
LLM-agent-dispatched tool call is capped at the built-in 120s ceiling no
matter what.

This is the same code area, and the same "ephemeral ToolAgent config is
implicitly, only partially derived from the parent LLM agent's config" defect
class, as #115/#116 (dead unsafe_mode config key, fixed by deriving
_resolve_parent_unsafe from the real safe_mode/context signals) and #111
(the timeout tool-call-argument override mechanism this issue's ceiling
fields bound). Extending docs/adr/ADR-2030-tool-calling-spec-extensions.md
(D-9) is the natural home for the design decision, following the same
ADR-before-code process #111 already used for the sibling timeout-argument
feature.

Current behavior

  1. LLMAgent._execute_tool_loop's three ephemeral-ToolAgent-construction
    sites already read self.config.get("timeout", 1) into the ephemeral
    agent's config, so a timeout value on the parent type: llm agent's
    config reaches the ephemeral agent as intended — no change needed here,
    included only as context for how the ceiling fields below should behave
    the same way
    .
  2. The same three sites never read tools_max_timeout or shell_max_timeout
    off the parent config, so the ephemeral ToolAgent always constructs with
    the class defaults (120.0 / None) no matter what the parent type: llm
    agent's config contains.
  3. Net effect: an actor author has no working lever to raise the per-call
    timeout tool-call-argument ceiling (ADR-2030 D-9) for tool calls a
    type: llm agent dispatches internally.

Expected behavior

  1. All three ephemeral-ToolAgent-construction sites in
    LLMAgent._execute_tool_loop build their config mapping with
    tools_max_timeout and shell_max_timeout read from the parent LLM
    agent's own config (self.config.get("tools_max_timeout", 120.0),
    self.config.get("shell_max_timeout", None)), exactly mirroring how
    timeout is already threaded through today. timeout itself is not
    touched by this change.
  2. The existing per-invocation timeout tool-call argument (ADR-2030 D-9,
    cleveractors.agents.timeout_policy.TimeoutPolicy) is completely
    unaffected by this change.
    When the model itself supplies a timeout
    argument inside a specific tool call's args (e.g.
    {"tool": "shell", "args": {"command": "...", "timeout": 30}}), that
    per-call value still takes precedence over the (now-inherited) default for
    that one call, bounded by the (now-inherited) ceiling — inheritance only
    changes where the ephemeral agent's default/ceiling originate from; it
    must never be confused with, shadow, or bypass the model's own per-call
    override argument.
  3. Omitting tools_max_timeout/shell_max_timeout on the parent type: llm
    agent's config reproduces today's behavior byte-for-byte (fallback to
    120.0 / None, identical to ToolAgent.__init__'s own defaults).
  4. A misconfigured inherited ceiling (non-numeric, zero, negative, or
    non-finite) fails fast at ephemeral-ToolAgent-construction time with the
    existing AgentCreationError, exactly as it already does for a
    directly-configured type: tool agent.

Acceptance criteria

  • Setting tools_max_timeout: 300 on a type: llm agent's config raises the
    per-call timeout tool-argument ceiling to 300 for shell/http_request
    calls that agent dispatches (verifiable via the ephemeral agent's resolved
    TimeoutPolicy), for all three dispatch sites (normal round, budget-
    exhaustion synthesis, post-loop synthesis).
  • Setting shell_max_timeout on a type: llm agent's config overrides the
    shell-only ceiling for tool calls it dispatches, without changing the
    http_request ceiling (still governed by tools_max_timeout).
  • Omitting both fields on a type: llm agent's config reproduces current
    default behavior exactly: ephemeral agent ceilings are 120.0 / unset.
  • A shell/http_request tool call carrying its own per-invocation timeout
    argument still uses that value (bounded by the inherited ceiling) in
    preference to the inherited default — verified both when the parent config
    sets tools_max_timeout/shell_max_timeout and when it omits them.
  • A non-numeric, zero, negative, or non-finite tools_max_timeout or
    shell_max_timeout on a type: llm agent's config raises an
    AgentCreationError at ephemeral-tool-agent construction, not silently or
    on first tool call.
  • nox (all default sessions) is green and nox -s coverage_report stays
    ≥ 97%.

Supporting information

  • docs/adr/ADR-2030-tool-calling-spec-extensions.md D-9 — introduces
    tools_max_timeout/shell_max_timeout and enumerates where a ToolAgent
    reads them ("an agent-package config, a type: tool node in an actor
    graph, and the streaming/interactive runtime"); this enumeration predates,
    and does not mention, the ephemeral tool-call-dispatch ToolAgent this
    issue covers, and needs a decision extending it.
  • docs/index.md §4.4 (LLM agents) / §4.5 (tool agents,
    tools_max_timeout/shell_max_timeout field definitions).
  • #111 — added the per-invocation timeout tool-call-argument override this
    issue's ceiling fields bound; used the same ADR-before-code process this
    issue should follow.
  • #115 / #116 — same code area and defect class (an ephemeral ToolAgent's
    config being only partially/incorrectly derived from the parent type: llm
    agent's own config/context).
  • Relevant symbols at commit 0880b02:
    • cleveractors.agents.llm.LLMAgent._execute_tool_loop (all three
      ephemeral-ToolAgent-construction sites)
    • cleveractors.agents.tool.ToolAgent.__init__ (reads
      tools_max_timeout/shell_max_timeout, validates them eagerly)
    • cleveractors.agents.timeout_policy.TimeoutPolicy (per-call override
      resolution — must remain untouched by this change)

Subtasks

  • ADR: add a decision record extending ADR-2030 documenting that
    ephemeral tool-call ToolAgent instances constructed by a type: llm
    agent inherit tools_max_timeout/shell_max_timeout from the parent's
    own config (same fallback defaults as today when absent, mirroring how
    timeout already inherits), and that a model-supplied per-call
    timeout tool argument continues to take precedence exactly as it does
    for a directly-configured type: tool agent; submit for review and get
    it accepted before writing code
  • Spec: if the accepted ADR determines a normative addition to
    docs/index.md is warranted, update it via the spec-revision procedure
    (bump Version, add a §21.1 Revision History row attributing the change
    to the ADR) — do not edit spec prose inline without it
  • Update the config construction at all three ephemeral-ToolAgent
    sites in LLMAgent._execute_tool_loop
    (src/cleveractors/agents/llm.py) to read tools_max_timeout and
    shell_max_timeout from self.config, falling back to ToolAgent's
    own defaults when absent
  • Tests (Behave): inherited tools_max_timeout raises the per-call
    ceiling; inherited shell_max_timeout raises the shell-only ceiling
    without affecting http_request; omitting both preserves current
    defaults; a per-call timeout tool-call argument still takes
    precedence over the inherited default in both cases; an invalid
    inherited ceiling raises AgentCreationError at ephemeral-agent
    construction; coverage across all three dispatch sites
  • Tests (Robot): integration scenario exercising a type: llm agent
    configured with tools_max_timeout/shell_max_timeout, dispatching a
    real shell tool call that needs more than the default ceiling
  • Benchmarks (ASV): assess whether any perf-sensitive path changed
    (expected N/A — this only threads config values through); document the
    assessment
  • Verify coverage >= 97% via nox -s coverage_report
  • Run nox (all default sessions), fix any errors

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • The ADR extending ADR-2030 is accepted (and docs/index.md updated per the
    spec-revision procedure, if the ADR calls for it) before implementation code
    is merged.
  • A Git commit is created where the first line matches the Commit Message in
    Metadata exactly.
  • The commit is pushed to the branch matching the Branch in Metadata exactly.
  • The commit is submitted as a PR to master, reviewed, and merged.
## Metadata - **Commit Message:** `feat(agents): inherit tool timeout ceilings from parent LLM agent` - **Branch:** `feature/m1-inherit-tool-timeout-ceilings` ## Background and context The Actor Configuration Standard (`docs/index.md` §4.5) defines three tool-agent config fields that bound tool execution: `timeout` (default `1`s), and — added by ADR-2030 D-9 — the generic `tools_max_timeout` (default `120`s, the ceiling a per-invocation `timeout` **tool-call argument** may request) and the shell-only `shell_max_timeout` refinement. §4.4 (LLM agents) defines no equivalent fields at all — `timeout`/`tools_max_timeout`/`shell_max_timeout` simply do not appear in the `type: llm` config table. Despite that, `cleveractors.agents.llm.LLMAgent` dispatches every built-in tool call (`shell`, `http_request`, etc.) by constructing an **ephemeral** `cleveractors.agents.tool.ToolAgent` per call — three near-identical call sites inside `LLMAgent._execute_tool_loop` at commit `0880b02` (the normal per-round path, the budget-exhaustion synthesis retry, and the post-loop stuck-model synthesis retry). Each site builds a small `config` mapping for that ephemeral agent (`tool_cfg` / `tool_config` / `tool_config_s`), including `"timeout": self.config.get("timeout", 1)` — i.e. it already reads `timeout` off the **parent** LLM agent's own config, even though §4.4 never documents that key as meaningful there. None of the three sites do the same for `tools_max_timeout` or `shell_max_timeout`, and **this issue is scoped to those two fields only** — `timeout` already inherits correctly and is out of scope here, referenced below only as context/precedent. `cleveractors.agents.tool.ToolAgent.__init__` always falls back to its own hardcoded defaults (`120.0` / `None`) for the ephemeral agent, regardless of what the parent `type: llm` agent's config contains under those keys. Since the ephemeral agent's config is synthesized internally and never authored directly by an end user, the parent LLM agent's own config is the only place an actor author could realistically express "let this agent's internally-dispatched shell/http_request calls request a higher per-call `timeout` override" — and today there is no way to do that: every LLM-agent-dispatched tool call is capped at the built-in 120s ceiling no matter what. This is the same code area, and the same "ephemeral `ToolAgent` config is implicitly, only partially derived from the parent LLM agent's config" defect class, as #115/#116 (dead `unsafe_mode` config key, fixed by deriving `_resolve_parent_unsafe` from the real `safe_mode`/`context` signals) and #111 (the `timeout` tool-call-argument override mechanism this issue's ceiling fields bound). Extending `docs/adr/ADR-2030-tool-calling-spec-extensions.md` (D-9) is the natural home for the design decision, following the same ADR-before-code process #111 already used for the sibling `timeout`-argument feature. ## Current behavior 1. `LLMAgent._execute_tool_loop`'s three ephemeral-`ToolAgent`-construction sites already read `self.config.get("timeout", 1)` into the ephemeral agent's config, so a `timeout` value on the parent `type: llm` agent's config reaches the ephemeral agent as intended — **no change needed here, included only as context for how the ceiling fields below should behave the same way**. 2. The same three sites never read `tools_max_timeout` or `shell_max_timeout` off the parent config, so the ephemeral `ToolAgent` always constructs with the class defaults (`120.0` / `None`) no matter what the parent `type: llm` agent's config contains. 3. Net effect: an actor author has no working lever to raise the per-call `timeout` **tool-call-argument** ceiling (ADR-2030 D-9) for tool calls a `type: llm` agent dispatches internally. ## Expected behavior 1. All three ephemeral-`ToolAgent`-construction sites in `LLMAgent._execute_tool_loop` build their `config` mapping with `tools_max_timeout` and `shell_max_timeout` read from the parent LLM agent's own config (`self.config.get("tools_max_timeout", 120.0)`, `self.config.get("shell_max_timeout", None)`), exactly mirroring how `timeout` is already threaded through today. **`timeout` itself is not touched by this change.** 2. **The existing per-invocation `timeout` tool-call argument (ADR-2030 D-9, `cleveractors.agents.timeout_policy.TimeoutPolicy`) is completely unaffected by this change.** When the model itself supplies a `timeout` argument inside a specific tool call's `args` (e.g. `{"tool": "shell", "args": {"command": "...", "timeout": 30}}`), that per-call value still takes precedence over the (now-inherited) default for that one call, bounded by the (now-inherited) ceiling — inheritance only changes where the ephemeral agent's *default*/*ceiling* originate from; it must never be confused with, shadow, or bypass the model's own per-call override argument. 3. Omitting `tools_max_timeout`/`shell_max_timeout` on the parent `type: llm` agent's config reproduces today's behavior byte-for-byte (fallback to `120.0` / `None`, identical to `ToolAgent.__init__`'s own defaults). 4. A misconfigured inherited ceiling (non-numeric, zero, negative, or non-finite) fails fast at ephemeral-`ToolAgent`-construction time with the existing `AgentCreationError`, exactly as it already does for a directly-configured `type: tool` agent. ## Acceptance criteria - Setting `tools_max_timeout: 300` on a `type: llm` agent's config raises the per-call `timeout` tool-argument ceiling to `300` for `shell`/`http_request` calls that agent dispatches (verifiable via the ephemeral agent's resolved `TimeoutPolicy`), for all three dispatch sites (normal round, budget- exhaustion synthesis, post-loop synthesis). - Setting `shell_max_timeout` on a `type: llm` agent's config overrides the `shell`-only ceiling for tool calls it dispatches, without changing the `http_request` ceiling (still governed by `tools_max_timeout`). - Omitting both fields on a `type: llm` agent's config reproduces current default behavior exactly: ephemeral agent ceilings are `120.0` / unset. - A `shell`/`http_request` tool call carrying its own per-invocation `timeout` argument still uses that value (bounded by the inherited ceiling) in preference to the inherited default — verified both when the parent config sets `tools_max_timeout`/`shell_max_timeout` and when it omits them. - A non-numeric, zero, negative, or non-finite `tools_max_timeout` or `shell_max_timeout` on a `type: llm` agent's config raises an `AgentCreationError` at ephemeral-tool-agent construction, not silently or on first tool call. - `nox` (all default sessions) is green and `nox -s coverage_report` stays ≥ 97%. ## Supporting information - `docs/adr/ADR-2030-tool-calling-spec-extensions.md` D-9 — introduces `tools_max_timeout`/`shell_max_timeout` and enumerates where a `ToolAgent` reads them ("an agent-package config, a `type: tool` node in an actor graph, and the streaming/interactive runtime"); this enumeration predates, and does not mention, the ephemeral tool-call-dispatch `ToolAgent` this issue covers, and needs a decision extending it. - `docs/index.md` §4.4 (LLM agents) / §4.5 (tool agents, `tools_max_timeout`/`shell_max_timeout` field definitions). - #111 — added the per-invocation `timeout` tool-call-argument override this issue's ceiling fields bound; used the same ADR-before-code process this issue should follow. - #115 / #116 — same code area and defect class (an ephemeral `ToolAgent`'s config being only partially/incorrectly derived from the parent `type: llm` agent's own config/context). - Relevant symbols at commit `0880b02`: - `cleveractors.agents.llm.LLMAgent._execute_tool_loop` (all three ephemeral-`ToolAgent`-construction sites) - `cleveractors.agents.tool.ToolAgent.__init__` (reads `tools_max_timeout`/`shell_max_timeout`, validates them eagerly) - `cleveractors.agents.timeout_policy.TimeoutPolicy` (per-call override resolution — must remain untouched by this change) ## Subtasks - [ ] ADR: add a decision record extending `ADR-2030` documenting that ephemeral tool-call `ToolAgent` instances constructed by a `type: llm` agent inherit `tools_max_timeout`/`shell_max_timeout` from the parent's own config (same fallback defaults as today when absent, mirroring how `timeout` already inherits), and that a model-supplied per-call `timeout` tool argument continues to take precedence exactly as it does for a directly-configured `type: tool` agent; submit for review and get it accepted before writing code - [ ] Spec: if the accepted ADR determines a normative addition to `docs/index.md` is warranted, update it via the spec-revision procedure (bump Version, add a §21.1 Revision History row attributing the change to the ADR) — do not edit spec prose inline without it - [ ] Update the `config` construction at all three ephemeral-`ToolAgent` sites in `LLMAgent._execute_tool_loop` (`src/cleveractors/agents/llm.py`) to read `tools_max_timeout` and `shell_max_timeout` from `self.config`, falling back to `ToolAgent`'s own defaults when absent - [ ] Tests (Behave): inherited `tools_max_timeout` raises the per-call ceiling; inherited `shell_max_timeout` raises the shell-only ceiling without affecting `http_request`; omitting both preserves current defaults; a per-call `timeout` tool-call argument still takes precedence over the inherited default in both cases; an invalid inherited ceiling raises `AgentCreationError` at ephemeral-agent construction; coverage across all three dispatch sites - [ ] Tests (Robot): integration scenario exercising a `type: llm` agent configured with `tools_max_timeout`/`shell_max_timeout`, dispatching a real `shell` tool call that needs more than the default ceiling - [ ] Benchmarks (ASV): assess whether any perf-sensitive path changed (expected N/A — this only threads config values through); document the assessment - [ ] Verify coverage >= 97% via `nox -s coverage_report` - [ ] Run `nox` (all default sessions), fix any errors ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - The ADR extending ADR-2030 is accepted (and `docs/index.md` updated per the spec-revision procedure, if the ADR calls for it) before implementation code is merged. - A Git commit is created where the first line matches the Commit Message in Metadata exactly. - The commit is pushed to the branch matching the Branch in Metadata exactly. - The commit is submitted as a PR to master, reviewed, and merged.
CoreRasurae added this to the v2.1.0 milestone 2026-08-21 14:15:16 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveractors-core#140
No description provided.