Internal single owner of mutable per-run resource state.
One GenServer owns the deadline, open/closed status, workflow and mission
capability counters, optional per-route call counters keyed by
{public name, route key}, live provider-task count, protocol errors,
payload-free capability-refusal counts, subordinate
evaluation and source-check counts, terminal failure,
evaluation-continuation lease and revision, and committed native evaluation
memory/history.
Reservations and commits are deliberately atomic owner operations. Callers
must not recreate them as separate read and update steps. A routed LLM alias
may carry max_calls; that per-alias cap is checked inside the same reserve,
after the public total and per-name quotas, so a spent alias is named only
when those shared buckets still have room. The opaque token
prevents messages that did not originate through the returned handle from
mutating state. The process monitors the run owner and automatically exits
with it. Owner checks compare the actual GenServer.call/3 caller inside this
process. Ordinary and standalone-REPL state cannot transfer ownership; only
co-hosted session construction receives a one-shot transfer.
Each capability reservation monitors the dispatching process. Every attached
provider is also registered with the run's one provider-task owner, an
internal process external to both this process and the provider session. It
monitors both, so it untrappably kills every attached callback when either
lifecycle disappears — including a session terminated at its cleanup
deadline, where terminate/2 cannot run. If the
dispatching process dies mid-call (heap kill, timeout kill), the reservation
is reclaimed only after the attached provider process has been killed and
its :DOWN observed. Thus connector cleanup cannot begin while a callback
from that run remains live. Shutdown — owner death or explicit stop —
likewise kills and drains every still-attached provider before state
terminates. A process holds at most one reservation at a time: dispatch is
sequential per process, so a second reserve while one is active is a protocol
violation and is rejected rather than silently replacing the tracked
reservation.
A dispatching process routinely races that shutdown, so the calls it makes
here report closure rather than propagating the owner's exit, each answering
with the value that fails closed for its own caller. usage/1 and limits/1
are the deliberate exceptions: invented limits would widen the ceilings a
caller is about to enforce and an invented usage snapshot would misreport the
budget, so exiting with the owner is the safe outcome and they stay unguarded.
What is reported is the same for any exit, including a call timeout; a reply
the owner actually sent is always passed through, so a mismatched token still
reaches the caller unchanged. Provider and pre-dispatch validation processes
atomically record terminal classifications against the active evaluation
before publishing their results; those evaluation-scoped bits therefore
survive a later sandbox timeout or heap kill and are cleared with the lease.
Summary
Types
Opaque handle to the one process that owns this run's provider tasks.
Functions
Attaches the caller's live provider process to its capability reservation.
Returns a specification to start this module under a supervisor.
Closes the run against further reservations and result commits.
Atomically commits one bounded native memory/history continuation candidate.
Returns bounded definition, history, and retained continuation byte counts.
Records the first terminal failure and closes the run.
Atomically releases a provider slot, accepts completion, and records trusted evidence.
Returns the normalized limits owned by this run.
Checks whether a mission lease is still the current evaluation lease.
Returns whether the run is open and its deadline has not elapsed.
Records one protocol error and closes the run when its ceiling is exceeded.
Records one protocol error after atomically authenticating the mission lease.
Records one agent-loop protocol error without consuming a limit or closing the run.
Records one capability-callback error under its closed rejection class.
Releases the caller's evaluation lease without changing committed memory.
Releases the caller's live provider slot without accepting a result.
Returns non-negative wall time remaining before the run deadline.
Atomically reserves environment, per-name, and live-provider budgets.
Reserves a capability slot, authenticating mission calls by lease.
Reserves the evaluation lease with the chosen admission mode.
Starts run state, preserving a supplied active-run deadline when present.
Stops the owner process after the run has closed.
Returns the first terminal failure, if any.
Returns a read-only bounded usage snapshot.
Types
@type environment() :: :workflow | :mission
Opaque handle to the one process that owns this run's provider tasks.
Its shape is an implementation detail: callers receive it inside t/0 and
hand it back to the Kernel rather than inspecting it. Declaring it here keeps
the public struct from depending on an internal module's documentation, and
pid() would be inaccurate because the handle also carries an ownership
token.
@type t() :: %PtcRunner.Kernel.RunState{ pid: pid(), provider_tracker: provider_tracker(), token: reference() }
Functions
Attaches the caller's live provider process to its capability reservation.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec clear_last_evaluator_failure(t()) :: :ok
@spec close(t()) :: :ok
Closes the run against further reservations and result commits.
Atomically commits one bounded native memory/history continuation candidate.
Returns bounded definition, history, and retained continuation byte counts.
Records the first terminal failure and closes the run.
@spec finish_provider(t(), binary() | nil, PtcRunner.Kernel.ProviderError.t() | nil) :: :ok | {:error, :run_closed | :invalid_provider_evidence}
Atomically releases a provider slot, accepts completion, and records trusted evidence.
@spec limits(t()) :: PtcRunner.Kernel.Limits.t()
Returns the normalized limits owned by this run.
Checks whether a mission lease is still the current evaluation lease.
Advisory pre-authentication for dispatch: a stale evaluation's call is
turned away before it can run argument validators or spend the shared
protocol-error budget. The atomic recheck inside reserve_capability/4
remains authoritative.
Returns whether the run is open and its deadline has not elapsed.
@spec protocol_error(t()) :: :ok | {:error, :protocol_error_limit}
Records one protocol error and closes the run when its ceiling is exceeded.
@spec protocol_error(t(), environment(), reference() | nil) :: :ok | {:error, :protocol_error_limit | :stale_evaluation}
Records one protocol error after atomically authenticating the mission lease.
Authentication and accounting are one owner operation: an advisory check
followed by a separate protocol_error/1 would let an evaluation that died
mid-validation spend the next evaluation's shared protocol-error budget.
A stale mission lease answers {:error, :stale_evaluation} and records
nothing. Workflow calls carry no lease and are always recorded.
@spec record_agent_protocol_error(t()) :: :ok | {:error, :closed}
Records one agent-loop protocol error without consuming a limit or closing the run.
This is not protocol_error/1: that counter is the Kernel's recoverable
capability-protocol budget and closes the run at its ceiling. The loop's
count is attested through a private trusted tool and is observational.
Records one capability-callback error under its closed rejection class.
The count is observational: it does not consume a budget or close the run.
Keys are produced by SafeMetadata.capability_refusal_key/2. Distinct class
keys are capped at SafeMetadata.capability_refusal_map_limit/0; further
classes increment $overflow. Recording after the run has closed is
intentional — run_closed refusals happen then.
Releases the caller's evaluation lease without changing committed memory.
@spec release_provider_slot(t()) :: :ok | {:error, :closed}
Releases the caller's live provider slot without accepting a result.
@spec remaining_ms(t()) :: non_neg_integer()
Returns non-negative wall time remaining before the run deadline.
@spec reserve_capability(t(), environment(), binary()) :: :ok | {:error, atom()}
Atomically reserves environment, per-name, and live-provider budgets.
@spec reserve_capability(t(), environment(), binary(), reference() | nil, map() | nil) :: :ok | {:error, atom()}
Reserves a capability slot, authenticating mission calls by lease.
A mission reservation must present the lease of the evaluation whose tool
grant issued the call; a lease that is no longer current answers
{:error, :stale_evaluation}. This closes the window where a dead
evaluation's lingering sandbox reserves after the next evaluation was
admitted and has its late call attributed to the new lease. Workflow
reservations carry no lease. When route is
%{route_key: alias, max_calls: n} and n is stricter than the public
per-name quota, the owner refuses that alias once it has spent n calls
({:error, :route_call_limit}) after the public total and per-name quotas.
An alias cap at or above the per-name budget is ignored so omitted defaults
keep today's public quota. A named max_calls diagnostic is authenticated
only after this owner has actually refused that alias. Public total and
per-name quota refusals are authenticated the same way, keyed by the limit,
capability name, and configured value that this owner actually refused.
@spec reserve_evaluation(t(), binary(), :fail_fast | :block) :: {:ok, map(), [term()], reference()} | {:error, atom()}
Reserves the evaluation lease with the chosen admission mode.
:fail_fast answers {:error, :busy} while another evaluation holds the
lease (or its provider reservations are still draining). :block parks the
caller in a FIFO admission queue instead; the reply arrives when the lease
frees, or as {:error, :admission_timeout} /
{:error, :deadline_expired} when the bounded wait ends first. The wait is
bounded server-side by evaluation_admission_timeout_ms and the run
deadline, so the blocking call itself uses an infinite client timeout.
@spec start( PtcRunner.Kernel.Limits.t(), keyword() ) :: {:ok, t()} | {:error, term()}
Starts run state, preserving a supplied active-run deadline when present.
@spec stop(t()) :: :ok
Stops the owner process after the run has closed.
@spec terminal_failure(t()) :: nil | %{kind: atom(), reason: atom(), details: map()} | %{kind: atom(), reason: atom()}
Returns the first terminal failure, if any.
Returns a read-only bounded usage snapshot.