Skip to content

Add per-model-call wall-clock ceiling + h2 PING keepalives on the default provider client - #124

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/123-per-model-call-budget
Aug 24, 2026
Merged

Add per-model-call wall-clock ceiling + h2 PING keepalives on the default provider client#124
senamakel merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/123-per-model-call-budget

Conversation

@sanil-23

@sanil-23 sanil-23 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Closes #123.

What

1. RunLimits::max_model_call_ms — a per-model-call wall-clock ceiling (default None, today's behavior). The model-call budget becomes min(remaining run budget, per-call ceiling), recomputed for every call and every retry attempt, so each attempt gets its own fresh window while never overshooting the run deadline.

2. Tool calls are deliberately exempt. call_budget() (remaining-only) stays the tool path's budget: a sub-agent delegation is a tool call wrapping an entire child run and must not inherit a model-call-sized cap. Tools remain bounded by their own ToolTimeoutSettings plus the run remainder. Pinned by per_model_call_ceiling_does_not_bound_tool_calls.

3. The timeout message names which ceiling firedper-model-call ceiling vs remaining wall-clock budget — so field triage can tell "this one call wedged" from "the run is out of time".

4. HTTP/2 PING keepalives on the default provider client (http2 reqwest feature + http2_keep_alive_{interval,timeout}(30s) + while_idle). Streaming calls deliberately carry no overall request timeout, so a dead peer during an app-silent stretch (hidden reasoning) was previously indistinguishable from a thinking model until a harness budget fired. PINGs fail the in-flight call in ~1 min when the peer stops acking, with zero false positives on legitimately slow calls. Plaintext HTTP/1.1 endpoints (local Ollama/LM Studio — no ALPN) are unaffected; caller-owned clients (with_client) keep their own transport policy, unchanged.

Why

Observed in the field (OpenCompany workflow, via openhuman's 600s turn ceiling): a model call late in a long, productive run was granted the run's remainder — 56s — and was killed mid-flight, failing the whole run. The single max_wall_clock_ms knob conflates hang detection with runaway-run bounding, forcing hosts to trade "long runs allowed" against "hang-detection latency" 1:1. This decouples them: hosts can raise the run ceiling to a generous runaway guard while a wedged call still dies at the per-call ceiling.

Sizing note: a hidden-reasoning call can legitimately be app-silent for minutes — the ceiling is a backstop for calls that will never return, not a latency target. Host wiring (env knob, defaults) lands separately in openhuman.

Commands run locally

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo test — 1781 lib + all integration suites + doctests, 0 failures

API changes

  • New public field RunLimits::max_model_call_ms + with_max_model_call_ms builder.
  • TinyAgentsError::Timeout message text for model calls bounded by the new ceiling says "exceeded its per-model-call ceiling" (run-bounded calls keep the existing "remaining wall-clock budget" phrasing).
  • reqwest gains the http2 feature (ALPN-negotiated; no behavior change for plaintext endpoints).

Summary by CodeRabbit

  • New Features

    • Added an optional maximum duration for individual model calls.
    • Timeout messages now clarify whether the overall run limit or per-call limit was reached.
    • Enabled HTTP/2 negotiation and keepalive support for default provider connections.
  • Bug Fixes

    • Model-call time limits are recalculated for each retry.
    • Tool calls continue to follow the overall run time limit only.

sanil-23 and others added 3 commits August 24, 2026 21:21
…tinyhumansai#123)

The budget for an individual model call was solely the run's remaining
wall-clock budget, which conflates hang detection with runaway-run
bounding: a generous run ceiling lets a wedged call hold the run for the
whole ceiling, while a tight one kills late calls in long productive
runs (observed in the field: a call granted 56s of a 600s turn after
earlier calls legitimately consumed the rest).

The model-call budget is now min(remaining, max_model_call_ms), computed
fresh per attempt so every retry gets its own window while never
overshooting the run deadline. Tool calls deliberately keep the
remaining-only budget: a sub-agent delegation is a tool call wrapping an
entire child run and must not inherit a model-call-sized cap.

The timeout message now names which ceiling fired (per-model-call
ceiling vs remaining wall-clock budget) so field triage can tell a
wedged call from an exhausted run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… tool exemption (tinyhumansai#123)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ansai#123)

Streaming calls deliberately carry no overall request timeout, so until
now a dead peer during an app-silent stretch (e.g. hidden reasoning) was
indistinguishable from a thinking model until a harness budget fired.
With h2 negotiated via ALPN, PING keepalives fail the in-flight call in
about a minute when the peer stops acking, with zero false positives on
legitimately slow calls. Plaintext HTTP/1.1 endpoints are unaffected;
caller-owned clients (with_client) keep their own transport policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2567287e-4359-4c6f-bf71-accd426120e1

📥 Commits

Reviewing files that changed from the base of the PR and between bbcd0a6 and a783e04.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • Cargo.toml
  • src/harness/agent_loop/model_call.rs
  • src/harness/agent_loop/test.rs
  • src/harness/agent_loop/tools.rs
  • src/harness/limits/mod.rs
  • src/harness/limits/types.rs
  • src/harness/providers/openai/mod.rs
  • src/harness/providers/openai/transport.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change adds an optional per-model-call timeout ceiling, preserves run-budget handling for tools, improves timeout diagnostics, and configures HTTP/2 keepalives for default OpenAI provider clients.

Changes

Model Call Reliability

Layer / File(s) Summary
Per-call limit contract
src/harness/limits/types.rs, src/harness/limits/mod.rs
RunLimits now supports optional max_model_call_ms configuration. The default remains unset.
Budget enforcement and validation
src/harness/agent_loop/model_call.rs, src/harness/agent_loop/tools.rs, src/harness/agent_loop/test.rs
Model calls use the tighter run remainder or per-call ceiling. Tool calls remain run-bound. Timeout messages identify the active bound. Tests cover both limits and tool-call behavior.
Provider HTTP/2 keepalive
Cargo.toml, src/harness/providers/openai/mod.rs, src/harness/providers/openai/transport.rs
The default OpenAI client enables HTTP/2 keepalive settings and is reused by OpenAiModel::new. Custom clients remain unaffected.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to a783e

This PR adds per-call model time limits and HTTP/2 keepalives without any identified merge-blocking correctness or availability risk; it is merge-ready after normal checks and review.

Suggested reviewers: senamakel, m3ga-mind

Poem

A rabbit checks the timeout gate,
While model calls no longer wait too late.
Tools keep their run-bound pace,
HTTP/2 PINGs patrol the space.
“Hop onward,” says the hare,
“The budgets now are clear and fair!” 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes both primary changes: per-model-call wall-clock ceilings and HTTP/2 PING keepalives.
Linked Issues check ✅ Passed The changes implement the per-call budget cap, tool-call exemption, timeout labels, tests, and default-provider HTTP/2 keepalives required by issue #123.
Out of Scope Changes check ✅ Passed All reviewed changes directly support the linked issue objectives and contain no unrelated code changes.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.

Warning

Your free Security trial is over. An organization admin can activate billing to continue.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

tinysweeper found nothing blocking. Approving.

$0.0000 · 0 in / 0 out · 692 embedded · openrouter/openai/text-embedding-3-small

@tinysweeper

tinysweeper Bot commented Aug 24, 2026

Copy link
Copy Markdown

How this change flows

1 changed behaviour across 7 relationships. 4 surrounding behaviours are shown (60 graph nodes walked). 31 further behaviours left out to keep the diagram readable.

flowchart LR
  n0["OpenAiModel<br/>changed"]:::changed
  n1["Send"]:::impacted
  n2["AgentHarness"]:::impacted
  n3["ModelCallBase"]:::impacted
  n4["ChatModel"]:::impacted
  n0 -->|implements| n4
  n2 -->|uses| n1
  n3 -->|uses| n1
  n3 -->|uses| n2
  n3 -->|uses| n4
  n4 -->|uses| n1
  n4 -->|implements| n1
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge.

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Aug 24, 2026
@senamakel
senamakel merged commit eeb7345 into tinyhumansai:main Aug 24, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-call budget is the turn's remainder: late model calls in long turns are killed mid-flight regardless of progress

2 participants