Class: SolidLoop::Configuration
- Inherits:
-
Object
- Object
- SolidLoop::Configuration
- Defined in:
- lib/solid_loop/configuration.rb
Overview
Durable-lease + reaper tunables. See docs/decisions/durable_attempt_lease.md.
The load-bearing invariant is: an LLM turn's lease MUST outlive the HTTP
client's read timeout, so a healthy in-flight turn can never be reclaimed by
the reaper (its provider client raises first). The lease duration is derived
PER CLAIM from the agent's actual read_timeout plus lease_margin, so the
invariant holds by construction rather than by a fragile global-TTL constant.
Instance Attribute Summary collapse
-
#default_max_duration ⇒ Object
Fallback
max_duration(seconds) used to size the leak ceiling when a registration is created without one (an out-of-band caller ofLeaseRenewer#register). -
#default_read_timeout ⇒ Object
Fallback read_timeout (seconds) used to derive a lease when the agent's
llm_providerhash carries no:read_timeout. -
#default_tool_timeout ⇒ Object
Fallback tool client timeout (seconds) used to derive a per-tool_call lease.
-
#lease_leak_grace ⇒ Object
Grace period (seconds) added on top of the OWNING AGENT'S
max_durationto form the lease renewer's per-registration leak ceiling. -
#lease_margin ⇒ Object
Seconds added on top of the agent's HTTP read_timeout when deriving a per-claim LLM lease.
-
#queued_reap_threshold ⇒ Object
A loop sitting in
queuedfor longer than this (seconds) is treated by the reaper (case 4) as having lost its enqueued job, and is re-enqueued.
Instance Method Summary collapse
-
#initialize ⇒ Configuration
constructor
A new instance of Configuration.
-
#llm_lease_duration(agent) ⇒ Object
Derive an LLM lease duration (seconds) from an agent's configured HTTP read timeout plus the safety margin.
-
#tool_lease_duration(agent = nil) ⇒ Object
Derive a per-tool_call lease duration (seconds) for
agent.
Constructor Details
#initialize ⇒ Configuration
Returns a new instance of Configuration.
61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/solid_loop/configuration.rb', line 61 def initialize @lease_margin = 60 @queued_reap_threshold = 300 @default_read_timeout = SolidLoop::LlmCompletionJob::READ_TIMEOUT @default_tool_timeout = 600 # 5 minutes past the agent's own legal turn bound: comfortably absorbs clock # skew / a straggling final chunk, yet a genuine leak self-expires soon after # `max_duration` rather than never. @lease_leak_grace = 300 @default_max_duration = 2.hours.to_i end |
Instance Attribute Details
#default_max_duration ⇒ Object
Fallback max_duration (seconds) used to size the leak ceiling when a
registration is created without one (an out-of-band caller of
LeaseRenewer#register). Mirrors SolidLoop::Base#max_duration's default (2h).
59 60 61 |
# File 'lib/solid_loop/configuration.rb', line 59 def default_max_duration @default_max_duration end |
#default_read_timeout ⇒ Object
Fallback read_timeout (seconds) used to derive a lease when the agent's
llm_provider hash carries no :read_timeout. Kept in sync with the
adapter default (LlmCompletionJob::READ_TIMEOUT).
27 28 29 |
# File 'lib/solid_loop/configuration.rb', line 27 def default_read_timeout @default_read_timeout end |
#default_tool_timeout ⇒ Object
Fallback tool client timeout (seconds) used to derive a per-tool_call lease.
Tools do not expose a single uniform read_timeout the way the LLM provider
does (a loop can mix in-process toolsets and remote MCP servers, each with
its own Faraday timeout — default 60s in Mcp::Client), so the tool lease is
derived from THIS configurable ceiling on the slowest tool a healthy worker
could still be blocked in. The lease (this + lease_margin) must strictly
outlive that ceiling so a live, long-running tool is never reclaimed.
36 37 38 |
# File 'lib/solid_loop/configuration.rb', line 36 def default_tool_timeout @default_tool_timeout end |
#lease_leak_grace ⇒ Object
Grace period (seconds) added on top of the OWNING AGENT'S max_duration to
form the lease renewer's per-registration leak ceiling. Once a registration has
been renewed for longer than max_duration + lease_leak_grace — only possible
if a turn NEVER deregistered (a job that skipped its ensure, an unreachable
death path) — the renewer DROPS it and stops renewing, so its lease lapses and
the reaper's case-1 reclaim reclaims the loop; renewer liveness no longer
depends on ensure ever running.
This replaces the old lease_renew_ceiling_multiplier (a MULTIPLE of the lease
duration), which was WRONG: a healthy turn stays registered for its WHOLE life,
so any legitimate stream longer than K lease-widths (e.g. a small read_timeout
with steady chunks) was dropped and reaper-reclaimed mid-flight, and no long
turn could ever complete. Tying the ceiling to the agent's OWN legal turn bound
(max_duration, its own contract) means a legitimate turn always finishes
before the ceiling and is never dropped, while a genuine leak still self-expires
shortly after max_duration. Must be > 0.
54 55 56 |
# File 'lib/solid_loop/configuration.rb', line 54 def lease_leak_grace @lease_leak_grace end |
#lease_margin ⇒ Object
Seconds added on top of the agent's HTTP read_timeout when deriving a per-claim LLM lease. Must be > 0 so the lease strictly outlives the client timeout. Also acts as the floor when an agent exposes no read_timeout.
15 16 17 |
# File 'lib/solid_loop/configuration.rb', line 15 def lease_margin @lease_margin end |
#queued_reap_threshold ⇒ Object
A loop sitting in queued for longer than this (seconds) is treated by the
reaper (case 4) as having lost its enqueued job, and is re-enqueued. Must
comfortably exceed normal queue latency so a merely-backlogged loop is not
redundantly re-enqueued (the claim CAS dedupes duplicates harmlessly, so a
low value is safe-but-wasteful, not incorrect).
22 23 24 |
# File 'lib/solid_loop/configuration.rb', line 22 def queued_reap_threshold @queued_reap_threshold end |
Instance Method Details
#llm_lease_duration(agent) ⇒ Object
Derive an LLM lease duration (seconds) from an agent's configured HTTP read
timeout plus the safety margin. The margin GUARANTEES lease > read_timeout,
so a live worker never holds an expired lease. A missing/invalid read
timeout falls back to default_read_timeout.
119 120 121 122 123 124 125 126 127 128 |
# File 'lib/solid_loop/configuration.rb', line 119 def llm_lease_duration(agent) read_timeout = begin Integer(agent.llm_provider[:read_timeout]) rescue StandardError, TypeError nil end read_timeout = default_read_timeout unless read_timeout&.positive? read_timeout + lease_margin end |
#tool_lease_duration(agent = nil) ⇒ Object
Derive a per-tool_call lease duration (seconds) for agent. Mirrors the LLM
lease so the invariant holds BY CONSTRUCTION: the lease strictly outlives the
SLOWEST client an in-flight tool could be blocked in, so a healthy
long-running tool can never be reclaimed by the reaper (case 2).
max(default_tool_timeout, slowest resolved HTTP MCP client timeout) + margin
HTTP MCP tools use mcp_config[:timeout] || 60 (Mcp::ClientFactory) — an
operator can set this well above default_tool_timeout, so it must be folded
in. In-process/custom tools expose NO client timeout to derive from, so
default_tool_timeout is their ceiling. lease_margin (validated > 0)
guarantees lease > the resolved timeout.
Residual (documented): an in-process/custom tool that legitimately runs
longer than default_tool_timeout MAY be reclaimed → a duplicate invocation.
That is harmless — the DB fence yields exactly one canonical result and tools
carry the stable solid_loop:tool_call:#{id} idempotency key — but operators
who run a slow in-process tool must raise default_tool_timeout above it.
148 149 150 151 |
# File 'lib/solid_loop/configuration.rb', line 148 def tool_lease_duration(agent = nil) max_http = max_http_tool_timeout(agent) [ default_tool_timeout, max_http ].compact.max + lease_margin end |