Evaluation context for the Lisp interpreter.
Bundles the parameters that flow through recursive evaluation:
ctx: External data (read-only)user_ns: User namespace (mutable bindings fromdef)env: Lexical environment (variable bindings)tool_exec: Tool executor functionturn_history: Previous turn results for multi-turn loops
Limits
| Field | Default | Hard Cap | Purpose |
|---|---|---|---|
loop_limit | nil | — | Optional per-activation loop/recur bound |
max_print_length | 2,000 | — | Max chars per println call |
max_tool_call_result_bytes | 16,384 | — | Per-entry cap on the :result retained in the in-eval tool ledger |
pmap_max_concurrency | build-time schedulers * 2 | — | Max concurrent pmap/pcalls tasks |
parallel_deadline_cap | nil | — | Absolute ceiling clamping every pmap/pcalls deadline |
Tool-ledger retention
effects.tool_calls records every call's :result and :args for post-eval
telemetry/envelope rendering. To stop a long-running or looping tool use
(e.g. a paginated read fold) from accumulating full payloads in live eval
state, append_tool_call/2 bounds each entry's :result to a preview once
it exceeds max_tool_call_result_bytes, marking the entry with
:result_truncated. Only the LEDGER copy is bounded — the value returned to
the program and any evaluation-local effects.tool_cache entry keep the full
result (they are built separately in record_tool_call). :args is left
intact by default because later effect consumers may need the raw map for
capability identity and canonical argument hashing. A trusted tool may
instead install an explicit ledger-only argument projection for sensitive
boundaries. :child_trace_id/:child_step remain intact.
Summary
Functions
Appends a pmap/pcalls execution record to the context.
Appends a print message to the context.
Appends a tool call record to the context.
Consumes one recur jump against an activation-local iteration counter.
Returns the current evaluator origin, if any.
Returns the user namespace active before the current prelude export, if any.
Returns true when a namespace was directly selected by the session.
Merges new bindings into the environment.
Creates a new evaluation context.
Whether a prelude ref may be resolved from the current origin.
Saves the user namespace active before entering a prelude export.
Pushes a prelude-export origin for private tool authorization.
Pushes a prelude returned-closure origin without granting private tool authority.
Pushes a user-code origin that masks inherited prelude-export authority.
Extracts accumulated side effects that must survive a recur jump.
Returns the namespace part of a qualified prelude ref, or nil.
Atomically reserves one uncached tool invocation against the program-wide tool-call limit.
Restores side effects carried by a recur signal onto the next iteration context.
Whether an origin is session-authored rather than a prelude export.
Returns the direct prelude ids that pulled a transitive namespace in.
Updates the user namespace in the context.
Types
@type parallel_effects() :: recur_effects()
@type pmap_call() :: %{ type: :pmap | :pcalls, count: non_neg_integer(), child_trace_ids: [String.t()], child_steps: [any()], timestamp: DateTime.t(), duration_ms: non_neg_integer(), success_count: non_neg_integer(), error_count: non_neg_integer() }
Parallel map/calls execution record for tracing.
Fields:
type::pmapor:pcallscount: Number of parallel taskschild_trace_ids: Trace IDs supplied by nested tool executionstimestamp: When execution startedduration_ms: Total execution timesuccess_count: Number of successful executionserror_count: Number of failed executions
@type recur_effects() :: PtcRunner.Lisp.Eval.Effects.t()
@type t() :: %PtcRunner.Lisp.Eval.Context{ attached_component_ids: MapSet.t(String.t()), component_catalog: term() | nil, ctx: map(), data_grants: [String.t()] | nil, direct_namespaces: MapSet.t(String.t()), effects: PtcRunner.Lisp.Eval.Effects.t(), env: map(), failure_origin: :capability | nil, inspect_only: boolean(), locals: term(), loop_limit: pos_integer() | nil, max_heap: pos_integer() | nil, max_print_length: pos_integer(), max_tool_call_result_bytes: pos_integer(), max_tool_calls: pos_integer() | nil, missing_data_params_message: String.t() | nil, origin_stack: [map()], parallel_budget: PtcRunner.Lisp.Eval.ParallelBudget.t() | nil, parallel_deadline_cap: integer() | nil, pmap_deadline: integer() | nil, pmap_max_concurrency: pos_integer(), pmap_timeout: pos_integer(), prelude: PtcRunner.Lisp.Prelude.t() | nil, prelude_caller_user_ns_stack: [map()], prelude_export_mask: %{required(String.t()) => MapSet.t(String.t())} | nil, prelude_exports: %{required(String.t()) => {term(), map()}}, private_tool_authority?: boolean(), return_origin: :direct_tool_call | nil, shipped_export_owners: %{required(String.t()) => String.t()} | nil, strict_data: boolean(), strict_transitive_calls: boolean(), tool_activity: :atomics.atomics_ref(), tool_call_budget: :atomics.atomics_ref(), tool_exec: (String.t(), map(), map() | nil -> term()), tool_failure_token: reference() | nil, tools_meta: %{required(String.t()) => %{optional(atom()) => term()}}, transitive_namespace_requirers: %{required(String.t()) => [String.t()]}, turn_history: list(), user_ns: map(), worker_max_heap: pos_integer() | nil }
@type tool_call() :: %{ :name => String.t(), :args => map(), :result => term(), :error => String.t() | nil, :timestamp => DateTime.t(), :duration_ms => non_neg_integer(), optional(:child_trace_id) => String.t(), optional(:child_step) => term(), optional(:cached) => boolean() }
Tool call record for tracing.
Fields:
name: Tool nameargs: Arguments passed to toolresult: Tool resulterror: Error message if tool failedtimestamp: When tool was calledduration_ms: How long tool tookchild_trace_id: Trace ID of a nested tool execution, when supplied
Functions
Appends a pmap/pcalls execution record to the context.
Appends a print message to the context.
Long messages are truncated to max_print_length characters (default: 2000).
Appends a tool call record to the context.
The entry's :result and :args are bounded to a preview when they exceed
max_tool_call_result_bytes, so a looping/large tool use cannot accumulate
full payloads in live eval state. See the "Tool-ledger retention" moduledoc
section. Only the ledger copy is bounded; callers keep the full result for
the program return and cache separately.
@spec consume_loop_iteration(non_neg_integer(), pos_integer() | nil) :: {:ok, non_neg_integer()} | {:error, :loop_limit_exceeded}
Consumes one recur jump against an activation-local iteration counter.
nil disables the bound. A positive limit applies to one loop or
tail-recur function activation, not to the whole evaluation.
Returns the current evaluator origin, if any.
Returns the user namespace active before the current prelude export, if any.
Returns true when a namespace was directly selected by the session.
Merges new bindings into the environment.
@spec new( map(), map(), map(), (String.t(), map(), map() | nil -> term()), list(), keyword() ) :: t()
Creates a new evaluation context.
Options
:loop_limit- Optional positive integer bound on oneloopor tail-recurfunction activation (default: nil = disabled). Sequential loops, nested loops, and separate HOF callback invocations are separate activations.:max_print_length- Max characters perprintlncall (default: 2000):pmap_timeout- Shared absolute deadline in ms for each pmap/pcalls operation, including nested parallel calls (default: 5000). Increase for LLM-backed tools.:parallel_deadline_cap- Absolute monotonic-time ceiling in ms that clamps every parallel deadline regardless of when the operation starts (default: nil = no ceiling). Kernel runs pass their run deadline here.:pmap_max_concurrency- Max concurrent tasks in pmap/pcalls (default: the build-timeSystem.schedulers_online() * 2, frozen into the semantic revision):max_heap- Sandbox per-process heap cap in words (default: nil).:worker_max_heap- FIXEDmax_heap_size(in words) for every pmap/pcalls worker, top-level and nested (default: the:max_heapvalue). Not divided by concurrency. SeePtcRunner.Lisp.Eval.ParallelRunner.:parallel_budget- sharedPtcRunner.Lisp.Eval.ParallelBudgetsemaphore bounding the number of parallel workers alive at once across the whole run (default: nil = uncounted).
Examples
iex> ctx = PtcRunner.Lisp.Eval.Context.new(%{}, %{}, %{}, fn _, _, _ -> nil end, [])
iex> ctx.user_ns
%{}
iex> ctx = PtcRunner.Lisp.Eval.Context.new(%{}, %{}, %{}, fn _, _, _ -> nil end, [], max_print_length: 500)
iex> ctx.max_print_length
500
iex> ctx = PtcRunner.Lisp.Eval.Context.new(%{}, %{}, %{}, fn _, _, _ -> nil end, [], pmap_timeout: 60_000)
iex> ctx.pmap_timeout
60000
Whether a prelude ref may be resolved from the current origin.
Under strict_transitive_calls a session-authored program may name a
namespace it selected directly, or one no direct selection pulled in, but not
one reached only transitively through another prelude's requirements. Calls
made from inside a prelude export are never restricted.
Both the evaluator's resolution guard and PtcRunner.Lisp.Introspection's
visibility filter read this, so what a program can discover and what it can
call cannot drift apart.
Saves the user namespace active before entering a prelude export.
Pushes a prelude-export origin for private tool authorization.
Pushes a prelude returned-closure origin without granting private tool authority.
Pushes a user-code origin that masks inherited prelude-export authority.
@spec recur_effects(t()) :: recur_effects()
Extracts accumulated side effects that must survive a recur jump.
Returns the namespace part of a qualified prelude ref, or nil.
@spec reserve_tool_call(t()) :: :ok | {:error, :tool_call_limit_exceeded}
Atomically reserves one uncached tool invocation against the program-wide tool-call limit.
Returns :ok when unlimited (nil) or when the reservation succeeds,
{:error, :tool_call_limit_exceeded} when the shared limit is exhausted.
@spec restore_recur_effects(t(), recur_effects()) :: t()
Restores side effects carried by a recur signal onto the next iteration context.
Whether an origin is session-authored rather than a prelude export.
Returns the direct prelude ids that pulled a transitive namespace in.
Updates the user namespace in the context.