PtcRunner.Kernel.RunState (PtcRunner v0.14.0)

Copy Markdown View Source

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.

t()

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

environment()

@type environment() :: :workflow | :mission

provider_tracker()

@type provider_tracker() :: %{__struct__: module(), pid: pid(), token: reference()}

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.

t()

@type t() :: %PtcRunner.Kernel.RunState{
  pid: pid(),
  provider_tracker: provider_tracker(),
  token: reference()
}

Functions

attach_provider(state, provider)

@spec attach_provider(t(), pid()) :: :ok | {:error, :closed | :provider_down}

Attaches the caller's live provider process to its capability reservation.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

clear_last_evaluator_failure(state)

@spec clear_last_evaluator_failure(t()) :: :ok

close(state)

@spec close(t()) :: :ok

Closes the run against further reservations and result commits.

commit_evaluation(state, lease, memory, history)

@spec commit_evaluation(t(), reference(), map(), [term()]) :: :ok | {:error, atom()}

Atomically commits one bounded native memory/history continuation candidate.

evaluation_memory_summary(state)

@spec evaluation_memory_summary(t()) :: map()

Returns bounded definition, history, and retained continuation byte counts.

fail(state, kind, reason)

@spec fail(t(), atom(), atom()) :: :ok | {:error, :closed}

Records the first terminal failure and closes the run.

finish_provider(state, replay_request_hash \\ nil, llm_provider_error \\ nil)

@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.

last_evaluator_failure(state)

@spec last_evaluator_failure(t()) :: {:ok, map()} | :error

limits(state)

@spec limits(t()) :: PtcRunner.Kernel.Limits.t()

Returns the normalized limits owned by this run.

mission_lease_current?(state, lease)

@spec mission_lease_current?(t(), reference() | nil) :: boolean()

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.

open?(state)

@spec open?(t()) :: boolean()

Returns whether the run is open and its deadline has not elapsed.

protocol_error(state)

@spec protocol_error(t()) :: :ok | {:error, :protocol_error_limit}

Records one protocol error and closes the run when its ceiling is exceeded.

protocol_error(state, environment, lease)

@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.

record_agent_protocol_error(state)

@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.

record_capability_refusal(state, key)

@spec record_capability_refusal(t(), binary()) :: :ok

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.

record_last_evaluator_failure(state, evidence)

@spec record_last_evaluator_failure(t(), map()) :: :ok

release_evaluation(state, lease)

@spec release_evaluation(t(), reference()) :: :ok | {:error, atom()}

Releases the caller's evaluation lease without changing committed memory.

release_provider_slot(state)

@spec release_provider_slot(t()) :: :ok | {:error, :closed}

Releases the caller's live provider slot without accepting a result.

remaining_ms(state)

@spec remaining_ms(t()) :: non_neg_integer()

Returns non-negative wall time remaining before the run deadline.

reserve_capability(state, environment, name)

@spec reserve_capability(t(), environment(), binary()) :: :ok | {:error, atom()}

Atomically reserves environment, per-name, and live-provider budgets.

reserve_capability(state, environment, name, lease, route \\ nil)

@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.

reserve_evaluation(state, mission_name, atom)

@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.

start(limits, opts \\ [])

@spec start(
  PtcRunner.Kernel.Limits.t(),
  keyword()
) :: {:ok, t()} | {:error, term()}

Starts run state, preserving a supplied active-run deadline when present.

stop(state)

@spec stop(t()) :: :ok

Stops the owner process after the run has closed.

terminal_failure(state)

@spec terminal_failure(t()) ::
  nil
  | %{kind: atom(), reason: atom(), details: map()}
  | %{kind: atom(), reason: atom()}

Returns the first terminal failure, if any.

usage(state)

@spec usage(t()) :: map()

Returns a read-only bounded usage snapshot.