pub struct LimitsConfig {Show 15 fields
pub max_concurrent_inferences: Option<usize>,
pub max_concurrent_tools: usize,
pub default_max_iterations: Option<usize>,
pub exact_token_counting: bool,
pub script_shell_timeout_secs: u64,
pub stall_timeout_secs: u64,
pub dead_cycles_before_relief: u32,
pub finished_retention_secs: u64,
pub mcp_idle_disconnect_secs: u64,
pub wedge_timeout_secs: u64,
pub provider_failures_before_open: u32,
pub provider_circuit_cooldown_secs: u64,
pub interaction_timeout_secs: u64,
pub max_tool_call_write_bytes: Option<u64>,
pub max_run_write_bytes: Option<u64>,
}Expand description
Runtime resource limits with safe defaults baked in.
Both fields default to a bounded value so a fresh install can’t accidentally
run unbounded inference concurrency or an unbounded agent loop. Set a field
explicitly in [limits] to raise or lower it.
Fields§
§max_concurrent_inferences: Option<usize>Global fallback cap on concurrent inference requests for any model
without its own per-model pool entry. Defaults to Some(8); omit or set
a large number to effectively unbound it.
One physical bound sits behind this for script providers: each of their in-flight calls occupies a blocking-pool thread, and the daemon’s runtime provisions 2048 of those. Pools above 2048 only run that wide for HTTP providers, whose calls are fully async.
max_concurrent_tools: usizeSize of the shared tool-execution worker pool - the number of agents whose
tool batches may run concurrently across the whole daemon (the tool-lane
counterpart of max_concurrent_inferences). Defaults to 8. Clamped to at
least 1.
default_max_iterations: Option<usize>Fallback max_iterations applied to a stage that does not set its own,
so an agent can’t loop forever with no completion signal. Defaults to
Some(50). A stage’s explicit max_iterations always wins.
exact_token_counting: boolOpt-in exact pre-inference token budgeting. When true, each agent
inference is preceded by an exact token count of the assembled request
(via the provider’s count_tokens, which uses a remote endpoint for
Anthropic/Gemini and a local heuristic otherwise) and is rejected before
sending if it would exceed the model’s context window. Off by default:
normal budgeting uses cheap local estimates, and this adds a network
round-trip per inference for providers with a remote count endpoint.
script_shell_timeout_secs: u64Wall-clock timeout (seconds) for a Rhai script tool’s shell() host call,
mirroring the built-in shell tool’s own 60-second cap so a script can’t
hang an agent on a runaway command. Defaults to 60.
stall_timeout_secs: u64How long (seconds) a run may sit ready to work but unable to dispatch before it is failed instead of left running.
This only ever fires for something the runtime cannot resolve on its own -
today, a stage whose provider is not configured. Waiting for a busy
model’s inference pool is ordinary backpressure and is never failed, no
matter how long it takes. Defaults to 60; 0 disables the watchdog and
restores the old behaviour of waiting indefinitely.
Read once at daemon start, so a change needs a daemon restart.
dead_cycles_before_relief: u32How many consecutive safety re-drives may find the tool lane full and no run moving before the daemon widens the lane to break the jam.
The daemon re-drives itself every 30 seconds, so the default of 10 is
five minutes of a full lane going nowhere. Relief only ever adds
capacity, never cancels anything, and is capped at one extra lane’s worth
over the daemon’s life, so it cannot run away.
0 turns relief off. Detection and reporting stay on either way, so
lev ps and the metrics still show the streak.
Read once at daemon start, so a change needs a daemon restart.
finished_retention_secs: u64How long (seconds) a run keeps its place in lev ps after the daemon
unloads it from memory.
A terminal run used to leave the listing the moment it was unloaded, which made a run that died on its first inference look exactly like a run that had never been spawned. A scheduler polling the listing could only tell the two apart with a stopwatch, and issue #205 is what that cost: forty minutes of spawning work, timing out, and spawning it again.
Defaults to 300. 0 drops a run as soon as it finishes, which is the
old behaviour. The record lives in memory, so a restart clears it
whatever this is set to.
Read once at daemon start, so a change needs a daemon restart.
mcp_idle_disconnect_secs: u64How long (seconds) a per-agent MCP server may sit with zero live runs
leasing it before the daemon disconnects it (ending a stdio server’s
child process). Long enough that back-to-back runs of a blueprint reuse
the warm connection; the next run that declares the server reconnects
lazily. 0 keeps every server connected for the daemon’s life, which
was the old behaviour. Global [[mcp_servers]] from config.toml are
never disconnected regardless.
Read once at daemon start, so a change needs a daemon restart.
wedge_timeout_secs: u64How long (seconds) a run may sit in a state no part of the engine can reach before it is failed instead of left reported as running.
Not a general “this run looks slow” timeout, and never fires on one. An
agent waiting on the model, on a tool, on its sub-agents, or on a person
is holding the marker that says so, and is exempt however long it takes.
This only catches an agent holding no marker at all, which the engine’s
own invariants say cannot happen and which nothing will ever look at
again. Such a run stays running in meta.json for the life of the
daemon and keeps whatever capacity an external scheduler assigned it,
which is issue #202.
Defaults to 0, which is off: this fails runs, and an upgrade that
starts killing work nobody asked it to kill is worse than the leak. 300
is a reasonable value to set. Turning it on is also a way to find out
whether it is happening to you, since it says so in the log and in the
run’s error.
Read once at daemon start, so a change needs a daemon restart.
provider_failures_before_open: u32How many consecutive provider-fatal failures (out of credits, rejected key) take a provider out of service for every run.
Defaults to 3. One 402 can just be a request asking for more output
tokens than the balance covers; three in a row is the account. While a
provider is out, runs move to their next candidate, and runs with none
left are failed by the stall watchdog rather than left “running”.
0 disables the breaker, leaving per-run failover on its own.
Read once at daemon start, so a change needs a daemon restart.
provider_circuit_cooldown_secs: u64How long a provider stays out of service before one request is let through to see whether it recovered.
Defaults to 300 (five minutes). That probe either succeeds, which puts
the provider straight back into service, or fails and restarts the wait,
so topping up an account brings the factory back with no restart.
Read once at daemon start, so a change needs a daemon restart.
interaction_timeout_secs: u64How long (seconds) a prompt may go unanswered before the daemon resolves it itself and lets the run carry on.
Covers every prompt that waits on a person: an agent’s ask_user_* /
present_for_review call, a tool-approval prompt, a taint gate, and a
blueprint interaction point. Before this existed, a run whose operator
had walked away sat in WaitingInput holding its slot until the daemon
restarted - hours, in the report that prompted it (issue #204).
Expiry resolves the prompt exactly as cancelling it would: a tool
approval and a taint gate deny, an ask_user_* call is told nobody
answered, and an interaction point proceeds with no user text. Nothing is
approved on the strength of a timeout.
Defaults to 3600 (one hour); 0 waits indefinitely.
Read once at daemon start, so a change needs a daemon restart.
max_tool_call_write_bytes: Option<u64>Most bytes one tool call may write to disk. Unset is unlimited.
Unset in code, set by lev setup. How much an agent should write is
a judgement about what you are doing with it, so nothing is imposed on a
user who never opened this file - but a fresh install gets a concrete
number written here, where it is visible and can be deleted outright.
The incident behind it (issue #252) was a single shell call appending in a loop until the 60-second timeout: about 14 GB, from one call that looked ordinary.
A shell redirect is measured after the call, since the bytes go from
the shell to the file without passing through Leviath. So this stops the
call after the one that overran, not the one that did. write_file is
measured before, and is stopped outright.
Running out of disk is checked separately and is never configurable: see
leviath_core::write_limits::MIN_FREE_BYTES.
max_run_write_bytes: Option<u64>Most bytes a whole run may write to disk. Unset is unlimited.
The companion to max_tool_call_write_bytes, and the one that catches
what a per-call ceiling cannot: three calls of 12-14 GB each are
individually plausible and collectively a full disk. Same defaulting -
unset in code, written by lev setup.
Implementations§
Source§impl LimitsConfig
impl LimitsConfig
Sourcepub fn write_limits(&self) -> WriteLimits
pub fn write_limits(&self) -> WriteLimits
The write ceilings in effect, for the engine.
Trait Implementations§
Source§impl Clone for LimitsConfig
impl Clone for LimitsConfig
Source§fn clone(&self) -> LimitsConfig
fn clone(&self) -> LimitsConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for LimitsConfig
impl Debug for LimitsConfig
Source§impl Default for LimitsConfig
impl Default for LimitsConfig
Source§impl<'de> Deserialize<'de> for LimitsConfig
impl<'de> Deserialize<'de> for LimitsConfig
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Auto Trait Implementations§
impl Freeze for LimitsConfig
impl RefUnwindSafe for LimitsConfig
impl Send for LimitsConfig
impl Sync for LimitsConfig
impl Unpin for LimitsConfig
impl UnsafeUnpin for LimitsConfig
impl UnwindSafe for LimitsConfig
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> ConditionalSend for Twhere
T: Send,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> FromTemplate for T
impl<T> FromTemplate for T
Source§impl<T> FromWorld for Twhere
T: Default,
impl<T> FromWorld for Twhere
T: Default,
Source§fn from_world(_world: &mut World) -> T
fn from_world(_world: &mut World) -> T
Creates Self using default().
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
Source§fn with_current_context(self) -> WithContext<Self> ⓘ
fn with_current_context(self) -> WithContext<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoResult<T> for T
impl<T> IntoResult<T> for T
Source§fn into_result(self) -> Result<T, RunSystemError>
fn into_result(self) -> Result<T, RunSystemError>
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> Serialize for T
impl<T> Serialize for T
fn erased_serialize(&self, serializer: &mut dyn Serializer) -> Result<(), Error>
fn do_erased_serialize( &self, serializer: &mut dyn Serializer, ) -> Result<(), ErrorImpl>
Source§impl<T> Template for T
impl<T> Template for T
Source§fn build_template(
&self,
_context: &mut TemplateContext<'_, '_>,
) -> Result<<T as Template>::Output, BevyError>
fn build_template( &self, _context: &mut TemplateContext<'_, '_>, ) -> Result<<T as Template>::Output, BevyError>
entity context to produce a Template::Output.Source§fn clone_template(&self) -> T
fn clone_template(&self) -> T
Clone.