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 | 1,000 | 10,000 | Max loop/recur jumps |
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.
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.
Increments the iteration count and checks against the limit.
Copies the attached prelude tables and shared run-scoped resources from
source onto context.
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{ 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, iteration_count: integer(), locals: term(), loop_limit: integer(), 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, 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.
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.
Increments the iteration count and checks against the limit.
Copies the attached prelude tables and shared run-scoped resources from
source onto context.
Sub-contexts built with new/6 for closure/thunk evaluation start with empty
prelude tables; this re-installs them so a qualified prelude call made from
inside a user closure still resolves.
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
: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.