pub struct StepEnv<'a> {
pub plan: &'a PlanGraph,
pub executors: &'a [NodeExecutor],
pub budgets: Budgets,
pub reduce: &'a dyn Fn(&str, Option<&Value>, &Value) -> Value,
pub eval_cond: &'a dyn Fn(&PlanEdge, &Value) -> bool,
pub ask_ttl_sec: Option<i64>,
pub validate_args: &'a dyn Fn(&str, &Value) -> Result<(), String>,
pub llm_reserve_tokens: u64,
pub max_effects_per_attempt: u32,
pub llm_tool_result_chars: Option<usize>,
pub llm_context_tokens: Option<u64>,
pub decide: Option<DecideEnv<'a>>,
}Expand description
The injected pure behavior. Everything here is REQUIRED to be pure — enforced by the journaled decision record + replay assertion, not trust.
Fields§
§plan: &'a PlanGraph§executors: &'a [NodeExecutor]Per node: the resolved executor (the driver’s V7 freeze).
budgets: Budgets§reduce: &'a dyn Fn(&str, Option<&Value>, &Value) -> ValueMerge one result key into the accumulated state:
(key, prev, new) -> merged. The driver’s registry with LWW
fallback; must be pure, deterministic, batching-invariant (§6.5).
eval_cond: &'a dyn Fn(&PlanEdge, &Value) -> boolEvaluate an edge’s condition against the state — the built-in
(cond::eval over the parsed form) or a host evaluator over the raw
string. Must be pure; only consulted for edges that carry a cond.
ask_ttl_sec: Option<i64>TTL for Client asks (seconds); None = asks never expire.
validate_args: &'a dyn Fn(&str, &Value) -> Result<(), String>Validate a model-issued tool call’s arguments against the pinned
Definition’s input_schema (§6.11 — the driver wires
validate_instance over strict definitions; Ok(()) for non-strict).
Must be pure: it runs identically on replay.
llm_reserve_tokens: u64The per-call token reservation for LLM dispatches (§6.7’s
per-dispatch enforcement: spent + reserve must fit before an LLM
effect is emitted). The driver sets this to the request’s mandatory
max_tokens.
max_effects_per_attempt: u32Hard bound on effects per node attempt — an abstract node’s LLM loop
(turns + tool calls + re-prompts) that exceeds it fails the node
with ExecutorError rather than looping forever. Frozen in the run
manifest, so a resume bounds the loop exactly as the start did;
DEFAULT_MAX_EFFECTS_PER_ATTEMPT when the manifest pins none.
llm_tool_result_chars: Option<usize>Bound on ONE tool result’s size in the TRANSCRIPT (characters — there
is no tokenizer here, and calling it tokens would be a lie). None =
unbounded, which is what every run did before the knob existed. The
journal is never bounded: see bound_tool_content.
llm_context_tokens: Option<u64>Ceiling on the WHOLE transcript, in the provider’s own prompt tokens
(AbstractFlow.last_prompt_tokens). None = no ceiling, which is what
every run did before the fold existed. Reaching it emits one journaled
summarizer turn and splices its result over the folded range; failing
to find a foldable range is RUN-E024.
decide: Option<DecideEnv<'a>>The decision backend the manifest pinned, if any (C1/C2). None —
every run before decisions existed, and every host without one —
asks nothing and builds exactly the commands it always did.