Class: SolidLoop::Configuration

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

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_durationObject

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_timeoutObject

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_timeoutObject

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_graceObject

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_marginObject

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_thresholdObject

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