Execution resilience and deterministic replay¶
Treelang keeps its historical execution behavior by default: tool calls run once,
the first failure is raised, cancellation propagates, and successful programs
return their values directly. ExecutionPolicy enables retry and partial-result
behavior explicitly without changing serialized ASTs.
Safe retries¶
Retries are disabled unless both conditions are met:
max_attemptsis greater than one; and- the tool name is listed in
idempotent_tools.
from treelang import AST, ExecutionPolicy, RetryPolicy
policy = ExecutionPolicy(
retry=RetryPolicy(
max_attempts=3,
delay_seconds=0.1,
idempotent_tools=frozenset({"exchange_rate"}),
)
)
result = await AST.eval(program, provider, policy=policy)
Treelang retries ToolExecutionError and TimeoutError by default. Applications
may replace retryable_exceptions, but should only include failures known to be
transient. Arguments are evaluated once and reused unchanged. Every physical
attempt consumes the tool-call budget. Cancellation is never retried and also
interrupts retry backoff.
A tool must not be declared idempotent unless repeating the same call has the same externally observable effect. Mutating tools should normally remain single-shot.
Parallel failures¶
The default parallel_failures="raise" is fail-fast. When one parallel branch
fails, Treelang cancels and awaits unfinished siblings before raising the original
error. This prevents background tool activity after execution has returned.
Set parallel_failures="collect" to return one ordered BranchOutcome per branch:
from treelang import ExecutionPolicy
outcomes = await AST.eval(
parallel_program,
provider,
policy=ExecutionPolicy(parallel_failures="collect"),
)
Each outcome contains either success=True and value, or success=False plus
the exception type and message. Collection is only valid for programs whose mode
is parallel; using it with single mode raises ValueError. External
cancellation always propagates rather than becoming an outcome.
Offline replay¶
ToolReplayProvider and ModelReplayTransport consume ordered fixtures and
validate each tool argument or model request. Unexpected, reordered, changed, or
unconsumed entries raise ReplayMismatchError.
from treelang import ToolReplayEntry, ToolReplayProvider
provider = ToolReplayProvider(
tools=[tool_definition],
entries=[
ToolReplayEntry(
name="exchange_rate",
arguments={"base": "USD", "quote": "JPY"},
output=150.0,
)
],
)
result = await AST.eval(program, provider)
provider.assert_consumed()
Replay fixtures contain complete arguments, model requests, and outputs. Treat them as potentially sensitive: redact secrets and personal data before committing fixtures, just as for evaluation datasets.