Audience: people changing PtcRunner itself. Nothing here is needed to build an application on it.
This guide is the architectural map for the implemented Kernel. It explains
ownership and responsibility boundaries without duplicating field-level API
reference. Exact options, return values, limits, state transitions, and error
atoms belong in the owning PtcRunner.Kernel.* or PtcRunner.Lisp.* module
documentation.
For application authoring, start with the Quickstart, Getting started, the manifest guide, Host configuration, Building agents, and the Kernel REPL guide. PTC-Lisp semantics live in the language specification; canonical trace storage/query semantics live in the TraceLog contract; the admitted Java surface is generated in Java interop.
The diagram orients new maintainers; the sections below are authoritative. It carries two structural invariants worth stating on their own.
Filesystem paths stop at one line. Frontends resolve every path — the
application, host configuration, input, and destinations — into sealed values
before execution begins. PtcRunner.Kernel.RunCoordinator.prepare/2 accepts
only a sealed RunRequest and an inert InstallationCatalog, so no layer
below it can reopen a file, widen a destination, or leak a path into a
diagnostic. Preparation is also pure: it compiles bundles, resolves the
dependency graph, normalizes provider selections, narrows limits, and derives
identity without resolving a credential, starting a process, or touching the
network.
Every effectful resource has one owner. ExecutionSessionOwner holds the
canonical EventSink, the optional InspectionSink, and — only for
provider-bearing runs — one provider session, which is the sole owner of
active provider work. Acquired resources register their close operation on one
LIFO stack through PtcRunner.Kernel.ResourceRegistrar as they are created.
The execution owner separately records every resource it still holds and
removes that ownership on worker close or successful handoff. Normal close,
timeout, caller death, and termination re-entry all consult that set while
following the same bounded reverse-order cleanup under a single absolute
deadline, so no path can orphan a provider process or release a resource twice.
A REPL takes the same owner handle and runs repeated evaluations against it
rather than opening a session per evaluation.
Reading the layers top to bottom: frontends enter through a shared command
surface (1) and seal their inputs (2); RunCoordinator splits pure preparation
from effectful execution (3); the session owner bounds all provider lifetime
(4); a run-bound registry exposes only selected provider aliases as
capabilities plus an idempotent close (5); the Kernel core evaluates PTC-Lisp
against separately assembled workflow and mission environments (6); and
authorized artifacts are published last, after provider cleanup (7). Two
contracts cut across every layer: one absolute-monotonic Deadline that nested
work may narrow but never reset, and a closed diagnostic catalog whose
envelopes carry no path, credential, or arbitrary term.
The runtime-included release's bin/ptc entrypoint and generic mix ptc
task are the shipped command frontends. They share the entry parser, human
renderer, file-envelope publisher, and standalone-or-Mix runtime adapters. The
command engine's phase-6 boundary and both frontends use the same retained
publication authority.
Private-result recovery is implemented by that authority and the compact
ArtifactPublisher state machine described below.
The Kernel is a bounded runtime, not an agent framework.
BEAM code owns:
- authority assembly and immutable capability grants;
- owner-process state, deadlines, quotas, and cancellation;
- process containment and provider lifecycle;
- public/private value projection; and
- unavoidable canonical events and private-capture boundaries.
PTC-Lisp owns replaceable workflow policy:
- model messages and tool protocol;
- planning, retries, feedback, and completion policy;
- application-specific composition of capabilities; and
- reusable prompt and agent behavior in shipped or local components.
Frontends own presentation and host choices. They must enter through
PtcRunner.Kernel.ApplicationPackage and a sealed
PtcRunner.Kernel.RunRequest. The closed command pipeline uses
PtcRunner.Kernel.CommandEntry to generate the run reference, parse once, and
reject resolved envelope collisions before bootstrap. CommandRouter then
sends parsed one-shot requests through PtcRunner.Kernel.CommandEngine and
parsed REPL requests through the long-lived session frontend. Mix and release
adapters supply their own runtime bootstrap policy and share presentation.
One-shot runs execute through a
dedicated execution-session owner,
whether or not they select providers, and a provider-bearing invocation opens
ProviderActiveSession inside that owner's subordinate worker rather than in
the adapter. doctor --connect is a distinct active operation with its own
closed result. Manifest REPL startup enters through ManifestRepl: it uses the
same inert catalog, preparation, phase-7 checks, lifecycle marker, and active
provider-session prefix as a one-shot run, but transfers the resulting opening
handle and run state to ReplSessionOwner for repeated evaluations.
Embedding frontends execute a sealed request through
PtcRunner.Kernel.RunBuilder.build/3. For a provider-free request they may
instead call the path-free PtcRunner.Kernel.RunCoordinator and pass its
sealed PreparedRun to PtcRunner.Kernel.RunBuilder.build_prepared/3.
Provider-bearing command preparation and its later active continuation are
sealed by CommandEngine. CommandEngine.dispatch/1 completes phase-6
authorization, the one execution-owner lifecycle, publication from immutable
execution evidence, and closed run-outcome projection. Callers using the staged
prepare/1 boundary must still close an unused CommandPreparation rather than
pass its embedded run to build_prepared/3.
Frontends must not create another manifest parser, provider registry, event
model, or evaluator; once integrated, command frontends must also share the
engine's argv parser and diagnostic vocabulary.
When placing new behavior, prefer the lowest layer that must own it:
- containment or authority belongs in the Kernel;
- replaceable orchestration belongs in PTC-Lisp;
- external effects become explicit capabilities;
- frontend-only interaction remains above
RunBuilder.
The normal path is:
directory or memory documents + host-owned installation catalog
|
v
ApplicationSource -> Manifest -> ApplicationPackage
+ ExecutionInput
+ ExecutionPolicy
|
v
sealed RunRequest
|
v
RunCoordinator.prepare (path-free phases 4-5)
|
v
one-shot ExecutionSessionOwner
|
+-> sealed ProviderExecution -> ProviderActiveSession
|
+-> RunBuilder -> immutable RunConfig
|
v
Kernel.run -> Runner -> RunState + Dispatcher + Evaluation
|
+-> Result | Error
+-> canonical EventSink batch
+-> optional private inspection records
|
v
close providers and persist artifacts
PtcRunner.Kernel.ApplicationSource is the bounded, caching byte-acquisition
boundary. Directory and memory sources feed the same
PtcRunner.Kernel.Manifest decoder. The manifest decoder never creates
executable host callbacks. PtcRunner.Kernel.ApplicationPackage retains the
captured path-free source closure and semantic application identity;
PtcRunner.Kernel.ExecutionInput separately retains the selected input and
its authority class. A destination-free PtcRunner.Kernel.ExecutionPolicy
fixes event identities, inspection capture, and result projection before the
three values are sealed as one PtcRunner.Kernel.RunRequest.
PtcRunner.Kernel.InstallationCatalog maps selected names to sealed
ProviderDescriptor values and keeps trusted implementations inaccessible to
phase 5. Host catalog construction is process-free: it starts no installation
owner, retains no credential resolver, and seals each implementation recipe
with only its alias and opaque host binding rather than any installation
payload.
Opening a host-backed registry invokes separately sealed
ProviderRuntimeServices, starts and transfers one private installation owner,
and only then creates an OAuth context or claims authority. OAuth context
creation is lazy and disabled by default. Every context, claim, transfer, or
registry-construction failure releases the new owner. The catalog and runtime
services independently seal the same keyed host-document binding; a mismatch
is rejected before activation, context creation, store access, credential
resolution, or a direct connectivity probe. Host runtime services retain the
host document only as an authenticated, process-local encrypted payload; their
inspectable callback environments expose neither paths nor credential values.
Generic runtime-service construction cannot mint a host binding, and a
host-bound catalog requires activation to return a valid host authority.
That activation is a deliberate exception to the operation deadline rather than
cancellable work. Because only host-sealed runtime services carry a binding,
the branch runs one code-owned step — decrypt the sealed host payload, then
start the private owner and its credential lease — with no embedder-supplied
callback, file, socket, or network reachable inside it. Its input is bounded by
the confined read ceiling both HostConfig loaders share, so every host
document a command or embedding acquires through them stays within it; an
embedding that builds a HostConfig by other means owns that bound itself.
The deadline is checked immediately before the step and rechecked after it, so
an expired operation releases the authority instead of yielding a registry; a
pathological activation delays the command rather than being cancelled.
PtcRunner.Kernel.RunCoordinator.prepare/2 compiles the captured workflow and
mission component graphs, validates the public workflow entry, and performs
provider-inert declaration checks without accepting a path, looking up an
implementation, or invoking a builder, validator, credential resolver, OAuth
context/store, preflight, or probe. SelectionRules is the only selection
normalizer at this boundary: a sealed data-only IR with exact fields, scalar or
unique-list types, defaults, finite sets, ranges, and the closed cross-rules
subset_of, required_when_set_nonempty, and
ceiling_of_context_limit. A custom descriptor that needs executable
validation records active_required; validate reports
provider_declaration/selection_unverifiable without running it.
RunCoordinator.local_checks/3 is the only entry to phase 7 and the only place
an audited_local callback runs. Every active command crosses it before the
active lifecycle begins: run, doctor --connect, and manifest REPL opening
from ProviderExecution immediately before the session opens, and default
doctor directly, because it opens no session. Applicability is derived from the sealed
prepared/catalog/services trio rather than supplied, the coordinator anchors one
local_preflight_timeout_ms deadline that every applicable occurrence spends,
and the result is only :ok or one catalogued diagnostic. There is no
per-occurrence report, because the closed result contract has no failing
provider row: a failed check fails the whole command, and doctor settles its
audited-local rows only after the step as a whole succeeded.
unverified callbacks are the other half and never run there.
LocalPreflight.run_unverified/5 is their only entry, reached from the shared
operation prefix after the phase-8 marker and bounded by the operation deadline
rather than by local_preflight_timeout_ms. Run and doctor --connect both
cross it; default doctor does not, and reports
active_check_required instead. The two steps derive applicability separately,
so neither can reach the other's declarations, and they share the reason
translation with one deliberate difference: after the marker,
provider_declaration is unreachable — it is a pre-classification phase pinned
to provider_activity: false — so a declaration-class reason reports
active_preflight/selection_rejected, keeping its :selection subject and
occurrence.
A provider's prepare, preflight, or acquire callback answers {:error, reason}
with an atom and nothing else. Every provider_acquisition code requires a
subject bearing an occurrence, so once that reason leaves the loop that knows
which occurrence produced it there is nothing to attribute it to, and the
command boundary fails closed as internal_error — reporting an unreachable MCP
server as a defect in this program. AcquisitionReason therefore classifies at
the three sites in ProviderAcquisition that still hold the occurrence.
Only an active command is classified. Direct embedding keeps the bare reason: it has no envelope to render a diagnostic into, and its vocabulary — the MCP source alone distinguishes a timeout from an authentication failure from an oversized catalog — is far richer than three closed codes can carry. The two are told apart by whether the session carries an operation deadline, the same question phase-8 credential resolution asks.
Acquisition's own codes follow one rule rather than a judgement per reason:
provider_unavailable when the provider could not be reached or started,
provider_protocol_error when it answered and the answer was unusable, and
provider_policy_changed when a preparation contradicted its sealed
declaration. Three groups keep a phase of their own instead, because what they
describe is not acquisition failing — a rejected credential, a declaration or
selection reason, and a local-environment reason each report the phase they
belong to, reusing phase 7's groupings so one condition is not named two ways
depending on which step observed it.
Anything else fails closed as an internal error, and translations are added with their producers: a reason nothing can currently return has no branch. That rule is load-bearing rather than decorative — the first draft of this table carried six reasons the stdio and discovery paths normalize away before a builder can return them, and omitted one that a snapshot-identity build genuinely produces.
Every active command resolves its ordinary credentials once, at phase-8 step 5,
through ProviderCredentials. The step runs after the registry and OAuth
context exist and before any provider prepare, preflight, acquisition, or
execution callback below it, so a missing credential fails while every provider
is still inert instead of partway through a preparation or an acquisition.
Active selection checks, unverified local checks, command-owned provider
application startup, or an explicit OAuth exchange may already have run. The
execution branch therefore supplies that evidence to the resolver: an ordinary
failure before any such work reports provider_activity: false, while a failure
after one of those boundaries preserves true.
The required set is the union of credential_names over the sealed descriptor
of every selected declaration — never what a prepare callback reported, on the
same rule that decides the acquisition closure. Deriving it from callback reports
would make the authority to read a credential depend on invoking the code that
reads it. Because the union is whole-selection rather than whole-closure,
doctor --connect can answer for an occurrence that declares
connectivity_mode: :none: nothing acquires or probes it, but its credentials
row still exists and a connect success requires it to pass.
Consumers receive that map and take a subset of it, and neither resolves again.
Every subset is per occurrence, and what makes it so is that each consumer has a
sealed declaration to compare against. ConnectivityProbe subsets straight from
the sealed descriptor, so a probe sees only its own credential.
ProviderAcquisition.acquire/6 requires each preparation to report exactly its
sealed declaration before preflight, so a preparation that reports a name a
different selected provider declared is refused rather than served that
provider's value — and it is refused before the map is consulted at all. What
remains for the map itself is that it covers every declared name, which is a
claim about the resolution rather than about a callback; a preparation naming
something outside the sealed union is drift that fails closed with
provider_declaration_mismatch.
Failure attribution is per alias and never per occurrence:
subject_occurrence_policy/3 forbids an occurrence on
active_preflight/credential_unavailable, and the resolver answers for the
whole batch rather than naming which credential failed. The alias reported is
the first in manifest order that declares a credential — deterministic, and
independent of both resolver behaviour and the order providers are prepared in.
The lifecycle marker is deliberately not used as the diagnostic answer here.
It also admits active-only boundaries, so it is already set when a host-owned
provider application is found missing or an ordinary credential resolver fails
before any provider-facing work. active_preflight consequently accepts either
activity value, and each producer states what actually ran. In particular, an
inert application-availability rejection reports false: that includes a
missing host-owned application and a command VM refusing an already-running
target. A command-owned startup failure reports true because the gate attempted
OTP application startup. An OAuth selection refused before interaction reports
only any preceding command-owned startup; an actual authorization exchange,
dispatched selection validation or connectivity callback, and post-authorization
credential failure report true.
Direct embedding is the one caller that still resolves inside acquisition. It has no sealed declarations to derive a union from before preparation and no operation deadline to bound one, so it keeps the registry's synchronous semantics; an active command reaching that branch is refused rather than served, which is what stops a second credential pipeline from re-growing.
Shipped live-LLM and stdio MCP descriptors declare audited_local callbacks.
Those callbacks use the same model/adapter and executable/launcher checks as
runtime provider preflight, without resolving credentials or contacting a
provider. audited_local is a trust declaration rather than a capability flag,
and two constructors bound who may make it: ProviderDescriptor refuses it from
a :custom source, and InstallationCatalog refuses it from a catalog without
a host runtime binding. A custom local check declares unverified and runs as
active work after the phase-8 marker.
Those rules bound what may be declared. They do not attest that an admitted
callback came from a shipped recipe: whoever assembles a catalog in-process
supplies its implementations, and an embedder holding sealed host services can
bind one it assembled itself. That code is already trusted — hostile same-VM
containment is an explicit non-goal — and the guarantee that matters holds
regardless: manifest input selects installed aliases and never registers an
implementation, so nothing an application declares can introduce a callback
into phase 7. A live-LLM descriptor also supplies a bounded completion probe for
the active doctor --connect path. It consumes the credential the command
resolved at phase-8 step 5 rather than resolving one of its own, disables
adapter and HTTP retries and redirects, forces a one-token response ceiling, and
makes exactly one request under the sealed doctor timeout and provider heap
limit.
After every selection is normalized, the coordinator derives aggregate data
class, flow, and event privacy, then builds
effective_application_digest as SHA-256 over
"ptc.effective-application.v2\\0" || u64(n) || TJCS(projection). The literal
projection contains the final workflow hash, a sorted map of named mission
hashes, environment-qualified component records, contract behavior hashes,
entry, per-mission data and provider grants, identity-participating limits,
provider arrays in manifest order, input authority, derived event policy,
inspection-capture choice, result projection, and semantic revision. Each
provider record contains exactly alias, source, required installation revision,
data class, accepted data classes, authorization mode, and normalized config.
Input values/forms, paths, event IDs, credentials, raw selectors, and private
OAuth authority/fingerprint are excluded. The final digest and derived classes
are then added to each occurrence's path-free execution context.
The returned PreparedRun owns its monotonic lifecycle-marker process;
construction atomically claims a fresh false marker, so an active or previously
claimed marker cannot be shared by another prepared run. Its creating process
must retain the marker link through construction. Consuming the prepared run
atomically transfers that link and the marker's creator monitor to the
consuming process; cross-process construction and unlinked markers are
rejected. The current owner must call PreparedRun.close/1, which is
idempotent; a former owner receives {:error, :not_owner} after a transfer,
an unavailable bounded close is reported rather than silently accepted, and
owner death closes the marker even after a normal exit. Marker
calls carry a five-second server-clock deadline and a short reply grace: a
backlogged call fails boundedly, and processing it after the deadline cannot
apply the queued transition. An expired close still checks the caller against
the current controller, so a delayed creator cannot terminate the marker after
ownership transfers.
PreparedRun revalidates the exact normalized declarations and recomputes the
effective projection/digest at construction and on every seal check, so
provider-bearing preparation cannot bypass declaration processing by calling
the constructor directly. Such provider-bearing values are presently
continuation state for the staged command pipeline. RunBuilder.build_prepared/3
rejects them; the execution-session owner consumes one when it opens its
sinks, ProviderActiveSession then marks the active lifecycle and opens the
session. During application admission, a command-owned VM configures ReqLLM's
default HTTP/1 Finch pool before starting it: one shard sized from the sealed
catalog's installed live_provider_tasks ceiling. It deliberately does not use
the prepared run's manifest-narrowed effective value, because ReqLLM constructs
one VM-lifetime Finch tree at application startup. Explicit full Finch pools or
a non-HTTP/1 protocol configuration keep ReqLLM's own precedence. Host-owned
mode changes neither application configuration nor lifecycle. A later same-VM
command therefore does not resize an already-running pool; matching that pool
to changed host ceilings or concurrent runs belongs to the embedding host's
aggregate-capacity contract. That marker proves consumption and
lifecycle position, not by itself
that provider-facing work was attempted. Active producers carry cumulative
attempted-work evidence separately. The runtime registry, lifecycle value, and
that same session are passed to
RunBuilder. A run does that inside the execution-session owner's subordinate
worker, which calls build_active_owned/7 with the owner-opened sinks, the
catalog acquisition plans from, and the phase-8 step-5 credentials, then
completes through execute_built/1. Manifest REPL opening consumes the same
sealed preparation, opens its sinks before the active lifecycle, and retains the
acquired runtime registry and provider session behind one ManifestReplOpening
handle.
It atomically adopts the completed RunConfig, run state, trace grant, and
opening handle into ReplSessionOwner before exposing a process-affine
session. On an untyped owner failure, the opening owner attests the lifecycle
marker before its terminal cleanup closes the preparation; that is conservative
evidence that the active boundary was entered, while typed diagnostics carry
the exact attempted-work value. The public failure projection consumes the
captured evidence instead of racing owner teardown.
Provider-free manifests take the same opening and REPL-owner path but omit only
the provider session. After application admission, an active session
anchors one absolute run deadline shared by active selection, construction,
and Kernel execution. The active build atomically claims the session sealed to
the exact prepared run; swapping sessions or replaying the same prepared/session
pair is rejected before acquisition.
Construction binds each frozen bundle's component IDs, source hashes,
dependency edges, mission presence, and exported entry back to the sealed
request. RunBuilder.build_prepared/3 atomically consumes the prepared run
before assembly; sequential or concurrent reuse is rejected. Keyword shape,
duplicate and unknown keys, pure option types, and mutually exclusive option
pairs are rejected before acquisition, artifact anchoring, or that consumption,
so a caller may correct a side-effect-free option error and use the same
prepared run. Input, component-override, and result-projection options belong
only to document acquisition; build/3 and build_prepared/3 reject them
instead of silently competing with the sealed request. They consume the sealed
request, entry expression, and correlated frozen bundles without reconstructing
coordinator-owned fields. The provider-free
RunBuilder.build/3 convenience path crosses that same boundary and closes its
temporary prepared run after assembly.
CommandInitializer owns the shared init command's bounded filesystem state
machine. Its fixed main.clj and ptc.json documents pass through
ApplicationPackage.request_memory/3 and RunCoordinator.prepare/2 before
target filesystem access, so the scaffold uses the same manifest, compilation,
and sealed-preparation boundary as ordinary applications without opening a
provider session.
The initializer creates one mode-0700 sibling staging directory and records
the directory and known-child identities. Pre-publication failures remove only
children whose identities still match and then use non-recursive rmdir; a
replaced or otherwise uncertain staging entry is left untouched. The native
companion performs only the final no-replace directory rename, using
renameat2(RENAME_NOREPLACE) on Linux or renamex_np(RENAME_EXCL) on macOS.
That rename is the commit point. No later branch deletes or rolls back the
published target, and failures remain path-free. Target collisions project
publication/initialization_target_exists; preflight distinguishes
initialization_parent_missing from initialization_parent_unusable; and
failures without a safe public cause use publication/initialization_failed.
PtcRunner.Kernel.RunBuilder remains the shared environment assembly and
cleanup boundary.
One-shot Mix execution consumes the prepared run and constructs both sinks
inside one execution-session owner. Kernel evaluation runs in a monitored
subordinate process so caller death can abort it, finalize the canonical event
batch, and stop both sinks without waiting for evaluation to return. Normal
one-shot execution freezes the Kernel result, fail-closed disclosure class,
result-contract decision, terminal canonical events, and optional inspection
records in a sealed, path-free ExecutionOutcome. It then stops both sinks
before publication consumes only that immutable evidence and the bound, sealed
PublicationAuthority; callers cannot replace the anchored destinations after
preflight.
A provider-bearing one-shot uses the same owner. Runtime setup crosses that
boundary as a sealed ProviderExecution, which carries the inert catalog,
sealed runtime services, and requested authorization targets but never the raw
host configuration or the authorization-URL notifier. The owner remains the
fixed lifecycle owner for the provider session, registry, OAuth store,
loopback listener, prepared run, and sinks, while an authorized subordinate
worker performs provider setup, authorization, and Kernel work and reuses the
owner's already-opened sealed sinks. Aborting closes the provider session
first, because its committed closers still belong to the runtime that acquired
them, and only then unwinds that runtime in reverse acquisition order —
listener, registry, store. Ownership matches that guarantee: the OAuth store
and the host-bound registry authority both belong to the lifecycle owner rather
than to the worker, so terminating a worker blocked in provider work does not
destroy the store or revoke the authority that a session closer still needs.
Tearing those two down is bounded by its own outer-runtime bound rather than by
the session's anchored cleanup deadline: that deadline belongs to the registered
closers on the session's stack and is meant to be spendable in full, so
inheriting its remainder would force-kill the store immediately whenever a
session used its whole budget, skipping the cooperative stop that terminates its
registered managers. A failed session close outranks the result it would
otherwise hide. The owner rejects an
execution that is not bound to its exact preparation before consuming that
preparation, so a mismatched catalog or an authorization target the run never
selected leaves the prepared run reusable.
PtcRunner.Kernel.CommandEntry allocates a command reference before strict
argv parsing and resolves envelope distinctness before bootstrap. The
PtcRunner.Kernel.CommandEngine consumes the already-parsed request through
acquisition adapters and projects failures into
PtcRunner.Kernel.CommandOutcome. Mix.Tasks.Ptc is a thin adapter over that
boundary. Its Mix-owned runtime owns bootstrap, the interactive authorization
extension, and runtime hooks. Bootstrap exceptions and application
start failures are projected as the same closed run outcome as other internal
failures; neither their reason nor argv paths reach the envelope. The task
renders only the sealed outcome and raises a Mix error for a nonzero status
without halting the VM. The standalone release entrypoint turns the same
presentation status into a process exit.
Successful validate is terminal: it projects the five-field digest result,
closes its prepared run, and returns a sealed CommandOutcome. Both doctor
modes are terminal too, and doctor --connect is the one command the engine
completes that performs provider work: it derives the connect-mode plan while
the preparation is still claimed, runs one RunCoordinator.connect/3
operation, settles the plan from that operation's result, and projects the
closed check list. A selection naming no provider has no operation to run —
connectivity answers for selected occurrences — so the engine projects the
derived plan directly and reports no activity, which is the only case where a
connect answer skips the coordinator. Anything that fails renders one
catalogued diagnostic and no rows at all. Before active connect work, the
frontend runtime runs its environment-setup callback only when inert
preparation proves that a selected LLM installation uses an environment
credential. The Mix frontend may therefore load .env; the standalone
frontend has no environment-setup callback. The runtime's provider-application
mode then controls optional applications: :command_vm starts a selected
application inside the active provider session, while :host_owned requires
the application to be running already.
Successful models is terminal as well. It loads one bounded host document,
constructs its inert installation catalog, and projects
PtcRunner.Kernel.InstallationCatalog.public_installations/1 in alias order. It
never invokes a local check, selection validator, builder, credential resolver,
OAuth service, provider application, process, port, or network operation, and
it closes the inert catalog before returning the sealed outcome.
Successful staged run preparation returns a sealed
PtcRunner.Kernel.CommandPreparation, not a bare PreparedRun. That wrapper
retains the original command reference, inert catalog, sealed path-free
ProviderRuntimeServices, and only the artifact destinations needed by phase 6
alongside the separately sealed path-free prepared run. A host document is
encrypted into the runtime-services payload during acquisition and is not
retained as plaintext or reopened by dispatch. CommandRuntime carries only
frontend VM policy and callbacks. If inert preparation proves that a selected
LLM installation uses an environment credential, dispatch invokes the sealed
environment-setup callback before creating the execution owner or deadline.
Selected optional applications are admitted inside the active provider session
before it opens a runtime registry. That registry owns the resulting private
authority, and callers that retain it must use ProviderRegistry.close/1 when
the execution scope ends; closing revokes retained builders and credential
access.
CommandEngine.dispatch/1 keeps ExecutionSessionOwner for provider-free
runs, omitting only ProviderExecution and its provider session. A
provider-backed run creates one ProviderExecution, opens at most one provider
session, and uses the same owner and publication path. Before that owner closes
the prepared run's lifecycle marker on any untyped failure, it seals the marker
value and whether execution had started into an internal failure value. Dispatch
therefore never infers that fallback from provider declarations: a provider-bearing
sink-opening failure remains inactive and not_started, while a later marked
failure remains active and incomplete. Phase-12 projection
narrows the Kernel's richer internal usage snapshot to the closed envelope
vocabulary, flattens capability counts by workflow/mission scope, preserves
evaluation-memory evidence, and never includes a private result value.
Construction
validates the complete catalog, requires its installed limits to match the captured
package, requires JSON result projection, binds inspection presence to the
sealed policy, and binds a requested trace directory to policy IDs equal to the
command reference. Relative artifact destinations are anchored once against the
invocation working directory immediately after strict parsing, before host or
application acquisition, so later continuation work and VM-global cwd changes
cannot reinterpret them. The sealed wrapper accepts only absolute destination
paths and its exact field set. If the invocation cwd is unavailable, the engine
seals the ordered keys it could not anchor alongside every absolute destination.
Entry still compares the successfully captured absolute destinations with the
envelope before startup. Artifact-to-artifact distinctness remains in phase 6,
after artifact-specific validation and preparation have selected the
privacy-dependent trace suffix. Frontend dispatch retains the colliding
declaration-owned switches for human rendering. This ordering lets phase 6
project an artifact-specific invalid-destination diagnostic before considering
a lexical collision and preflight earlier absolute classes before projecting an
invalid-destination diagnostic for a later unanchored class, preserving the
fixed trace/inspection/result precedence. It also retains the closed artifact
class and cause through validation and reservation: trace, inspection, and
result failures distinguish invalid, unavailable, and unsafe destinations
without retaining or rendering a filesystem path. Artifact destinations that
resolve to the same file project
arguments/conflicting_arguments: no destination failed to open, so the
rejected combination remains an argument error. A failed or exceptional wrapper
construction releases the prepared run's activity owner before the engine
projects its closed internal diagnostic. Long switch names use their documented
dashed spellings, and underscore spellings before the -- option terminator
are rejected before OptionParser normalization can create an alias; positional
values after the terminator remain opaque to both spelling and duplicate
checks. The exact --version token is resolved across the option-bearing
prefix before command dispatch; combining it with any other token is therefore
a version-mode argument failure even when the preceding command token is
unknown.
The renderer accepts closed CommandDiagnostic, CommandSource, and
CommandSubject values; it never inspects an arbitrary exception or rejected
value. The authoritative phase/code/exit/retryability/message rows live in
PtcRunner.Kernel.DiagnosticCatalog, and mix ptc.gen_docs projects them into
priv/schemas/ptc-command-envelope-v2.schema.json.
CommandOutcome is itself sealed over its exact command mode, validated
envelope, and exit status. Frontends render only through
CommandOutcome.to_map/1; direct or nested mutation invalidates the
attestation. Non-run outcomes admit only the phase/code rows reachable by that
command, as exact pairs rather than whole phases. Static command modes require
provider_activity: false; the private {:doctor, :connect} mode admits only
active doctor and provider-cleanup rows while retaining the public "doctor"
command value. Doctor results always classify readiness: default doctor is
unverified, a completed connect is ready, and an attributable connect
diagnostic is failed. The failed form is a dedicated error-envelope branch:
it retains the primary and secondary diagnostics, carries the result, preserves
the primary nonzero exit status, and requires exactly one failed row correlated
with the primary diagnostic. Result provider_activity is the logical union of
all retained diagnostics. Successful default doctor outcomes require activity
false and only local/declarative/skipped provider checks. Successful connect
outcomes preserve cumulative attempted-work evidence and admit only completed
local/declarative-or-active provider checks; an active pass requires activity
true, while a provider-free or provider-bearing no-op connect can remain false.
Failure settlement reconstructs the connect plan against the exact sealed
preparation, catalog, environment, and plan binding. Only local, selection,
credential, authorization, and connectivity subjects map; acquisition maps to
connectivity only for a descriptor sealed with acquisition connectivity. Every
other pending row is skipped/not_verified_due_to_failure: connect is fail-fast
and returns no partial transcript, so the row deliberately makes no claim about
whether that work ran. Subjectless operation diagnostics, provider-application
diagnostics, cleanup/owner failures, and internal errors retain the ordinary
diagnostic-only envelope.
Default doctor also binds
provider rows to application presence: host-only groups require
application_required and omit selection, while application-backed groups
cannot use that skip. Because V2 has no public connect-mode field, the generated
doctor branches are the union of default and connect outcomes; the sealed
outcome is the boundary that distinguishes the two modes.
Every attested command/coordinator value validates its exact declared field set
before validating its payload attestation. An otherwise authentic nested value
with an undeclared field is therefore invalid at its own boundary and cannot be
re-attested by an outer continuation.
The catalog also owns the phase/code-specific source kinds, provider-subject
operations, operation-specific occurrence policy, and activity policy used by
both constructors and the generated schema. Provider diagnostics cannot carry
document provenance and non-provider diagnostics cannot carry provider
subjects. Activity is fixed false for inert phases. Local and active preflight
diagnostics carry producer-supplied cumulative attempted-work evidence: an
audited-local check is inert, while command-owned application startup, an active
selection or unverified callback, OAuth work, and later connectivity work flip
the value only once that work is attempted. Availability rejection, ordinary
credential lookup, OAuth pre-refusal, and a pre-dispatch timeout can therefore
remain false after the lifecycle marker. Provider acquisition, execution, and
cleanup codes require true because reaching those operations is itself evidence
of attempted work; other later untyped failures conservatively use the
lifecycle marker when exact evidence was lost. Occurrence indices use the
manifest's closed 0..31 bound in both the typed subject constructor and the
generated envelope schema.
Installation-declaration dependency_invalid diagnostics use operation
declaration with a null occurrence; selection-specific declaration failures
retain their workflow or mission occurrence.
A non-null byte span is admitted only when its
CommandSource was constructed with the exact trusted source bytes and the
exclusive end offset is within that retained byte bound.
Component compilation is the one producer of that span. When the prelude
compiler can attribute a failure to a single top-level form,
PtcRunner.Lisp.Prelude.ErrorSpan resolves that form's byte range from the raw
component text, and the resulting bundle diagnostic carries it against
provenance bound to the same bytes. That resolution runs in its own bounded
worker after the primary compile result is final, so optional attribution cannot
reclassify a compile error as a bundle timeout or heap failure. Failures no single form owns
may still carry a parser position: malformed syntax uses a zero-length byte span
at the parser's proven offset, including end-of-file for an unclosed form.
Unsupported-pattern preflight failures and whole-bundle limits keep the null
span. Spans are best-effort by design: an unresolvable one is dropped rather
than guessed, because a wrong range points a reader at innocent code.
Compiler details remain private inputs to an explicit reason allowlist. Only
:parse_error, :unbound_var, and :duplicate_ref become the public
syntax_invalid, undefined_variable, and duplicate_definition catalog
rows; every other compiler reason becomes compile_failed. Syntax uses the
fixed catalog message. Undefined-variable and duplicate-definition messages may
be rebuilt from exact bounded detail shapes, but only after every symbol is
validated against the PTC-Lisp name grammar and found verbatim in the submitted
component source. Dynamic messages require component-source provenance and the
published schema admits only their closed literal-plus-symbol forms. Malformed,
oversized, absent, or ambiguous detail retains the fixed catalog message. No
path forwards a compiler-rendered string.
notes stays pinned to the empty list. The published V2 schema fixes it as
{"const": []}, so any populated array makes an envelope invalid for a strict
V2 consumer; a diagnostic that has to report a rejected value against its bound
needs a later envelope version, not a relaxed V2. Manifest limits that are valid
under the application schema but exceed the installed ceiling therefore use
application/installed_limit_exceeded with the limit's manifest path and a
fixed remediation message; the requested value and ceiling remain internal.
The command-specific host loader preserves closed phase-2 causes instead of
projecting every failure to host_invalid: inaccessible files are
host_unavailable, invalid bytes/JSON are host_invalid, structural failures
are host_schema_invalid, and invalid installed limits are
installed_limit_invalid. Structural and limit diagnostics carry only a path
authorized by the generated host schema. Duplicate properties are structural
host failures. The command loader retains
their schema-authorized parent pointer (the empty pointer for a root duplicate)
instead of collapsing them into an unlocated JSON failure.
At the application source boundary, a missing manifest is
application/application_not_found; other access, type, and confinement
failures remain application/application_unavailable.
Non-null diagnostic paths require a non-null source and are admitted only for
the catalog's phase/code/source-kind combinations; source-less provider
diagnostics therefore cannot carry a path. Finished execution records have two
disjoint schema branches: ok requires a null diagnostic and error requires
a non-null closed diagnostic. Unclassified run failures admit only
execution.state: "not_started"; successful run branches bind normal/private
result projection to the same artifact class. Classified setup and audited-local
failures also remain not_started. The execution owner seals stage and activity
evidence before closing their owner-backed marker: pre-worker failures remain
not_started, while bare failures returned after a worker starts are
incomplete with unavailable usage and evaluation-memory fields. Defensive
publication and projection failures cannot claim that execution never began.
Successful trace and inspection
artifacts are only not_requested or written; a normal result has the same
choice, while a private result must be written. Recovery-only publication
states are confined to the result field of a failed envelope. The generated
schema additionally requires a compatible publication diagnostic as either
the primary or a secondary before it admits
recovery_written or finalization_uncertain; an unrelated execution,
phase-6 destination, or cleanup failure cannot claim a recovery artifact by
itself.
Trace/inspection publication failure or a late destination collision can
justify only recovery_written. finalization_uncertain requires result
publication failure, because only final-link processing can make the remaining
name set ambiguous. A generic caught internal_error carries no publication
stage evidence and therefore cannot justify either recovery state.
Compound outcomes validate the catalog-owned precedence before rendering: the
primary and up to six secondaries must be in order, no phase/code/subject
identity may repeat, and only one diagnostic may come from each cleanup,
internal-catch, result-guard, Kernel-or-session-opening, event-sink,
inspection-sink, or publication category.
Commands that stop before compound work require secondary_errors: [] in both
their sealed outcome constructor and generated envelope branch.
The --private-output path uses one small recovery state machine, not a
general artifact transaction. PublicationAuthority.authorize/4 reserves
destinations before provider activity; PublicationHandle retains the
exclusive file descriptors and captured identities, and ArtifactPublisher
owns the publication ordering.
During destination preflight it exclusively
creates an owner-only (0600) file named
.ptc-private-result-<run_ref>.json in the already-authorized output directory.
The authority's owner process retains the handle and captured file identity;
each handle's raw descriptor remains in its own owner process, which is
controlled by that authority owner rather than by the phase-6 caller. A
single-use claim transfers the claimant monitor to the same owner, so caller
death before the claim is harmless while claimant death cleans every
provisional reservation. Reservation failure is
destination/recovery_reservation_failed before provider activity, and the
file remains empty while execution is in progress. A requested private result
that names its own run-reference-derived recovery file is instead a deterministic
arguments/conflicting_arguments rejection; the frontend identifies only the
owning --private-output switch and phase 6 creates neither reservation.
After a valid result and successful provider cleanup, the publisher writes the
already-bounded bytes through that handle, syncs the file, and syncs its
containing directory. Only completion of both syncs, followed by an identity
check of the recovery name, establishes recovery_written. A write or
file-sync failure removes an invocation-owned partial file and reports
failed. A directory-sync failure leaves the complete recovery name for
inspection but also reports failed, because durability was not proven.
Failures after the sync boundary retain the complete recovery value; if the
recovery name cannot be proven reachable, the state is
finalization_uncertain. Failure before a valid result, including provider
cleanup failure, never materializes result bytes and removes the empty
reservation only after verifying its captured identity. That identity check is
trustworthy exactly once: staging and reservation names are derived from the
destination, so a later run reserves the same names — and, on filesystems that
recycle inode numbers, the same identities. Each handle owner therefore removes
those names at most once and afterwards only releases its descriptor, so
teardown of an abandoned run can never reclaim a reservation a later run holds.
If identity verification or unlinking fails while removing an empty or partial
reservation, the publisher leaves the name untouched, reports result state
failed, and emits publication/recovery_cleanup_failed. No durable complete
result is claimed. This is the sole publication-category diagnostic for that
compound outcome: it replaces an earlier result-publication diagnostic when
cleanup of that failed write also fails, while any higher-precedence provider,
execution, or result-cleanup diagnostic remains primary. The bounded message
directs the caller to inspect the derived recovery basename.
Optional trace and inspection publication follows recovery materialization. If
either fails, the durable recovery file remains and result state is
recovery_written. Otherwise finalization exclusively hard-links the recovery
inode to the requested result name, syncs the directory, unlinks the recovery
name, and syncs the directory again. Only then is result state written. A
late name collision leaves the recovery name and reports recovery_written;
failure to prove that name remains reachable is finalization_uncertain.
The failure states carry these exact proofs:
| Result state | Proven filesystem state |
|---|---|
not_written |
Result bytes were withheld before recovery materialization. |
failed |
Reservation or recovery write/durability failed; no durable complete result is claimed. |
recovery_written |
The complete synced inode is proven reachable only by the derived recovery name. |
finalization_uncertain |
The complete synced inode exists, but a finalization or rollback failure means the recovery name, requested name, or both may remain. |
written |
The requested name is durable and the recovery name is durably absent. |
If the first directory sync after linking fails, rollback unlinks the requested
name and syncs the directory; it returns to recovery_written only when that
state is verified. Failure to unlink the recovery name, failure of the final
directory sync, or an unverifiable rollback is finalization_uncertain.
Callers already know the authorized directory and can derive both safe
basenames from run_ref; public envelopes expose neither path. A VM abort may
leave an empty or partial reservation, but no successful envelope or recovery
state claims it as complete.
Help, version, and doctor success values are closed data contracts, not merely
shape-compatible maps. Help usage/notices and the packaged version are exact
compile-time constants. Doctor check names and status/code pairs come from the
closed runtime/application/viewer/provider vocabulary. The generated schema
requires the runtime/application/viewer prefix. Consumers accepting whole
envelopes must additionally call CommandContract.valid_envelope?/1 after
schema validation. For doctor failures it owns the exact primary-diagnostic/
failed-row correlation and cumulative activity rules that JSON Schema cannot
express. For standalone success results, call
CommandContract.valid_success_semantics?/2 for the byte-order and
per-provider-local ordering rules; the same predicate owns models ordering.
A lone successful local
provider check admits either activity value because its public row deliberately
does not reveal whether the implementation was audited-local or unverified.
Each models row preserves the host contract's required public
installation_revision, matching exactly
^[a-z][a-z0-9._-]{0,127}$. Host decoding reports a missing revision before
generic schema failure, including for an unselected installation.
Any successful active selection, authorization, or connectivity check still
requires provider_activity: true; resolving credentials alone does not prove
provider-facing work. No provider check requires false.
Public diagnostic paths originate as typed property/index segments.
PtcRunner.Kernel.ValueContract is sealed at bounded compilation and retains a
segment only while walking the exact local schema node that declares it,
stopping at the first unknown segment.
Manifest structural validation likewise retains each known section, list
index, declared field with an invalid value, and declared missing property
while stopping before an unknown key. Directory and in-memory acquisition use
the same typed manifest path.
Host structural paths are independently admitted against the generated host
schema.
Strict JSON decoding can retain a duplicate property's raw parent location for
the manifest loader, but only the prefix authorized by the generated manifest
schema crosses into the typed diagnostic path.
The applicable host, manifest, or contract schema walker seals those segments
as an attested CommandPath;
CommandDiagnostic rejects raw segment lists and performs only RFC 6901
escaping of the sealed path. Contract paths additionally carry their compiled
contract's sealed classification authority and are authorized only against the
selected tagged-union branch that produced the classification. The public
classification map omits the internal branch selector; separate attested
evidence supplies the exact branch schema used to construct the authority.
That same selected schema enriches at most one retained violation per object
path with applicable local facts. An actual object can receive sorted, bounded
missing names. A closed object schema additionally supplies sorted, bounded
allowed names and a local undeclared-key count; an open schema never treats
extension keys as undeclared. Enrichment walks only the already authorized
typed path; it never creates a second raw-path channel. ExecutionInput
therefore converts the enriched violation's segments into the same attested
CommandPath and preserves the schema-derived facts on that record. Non-object
values receive allowed-key guidance only for a closed object schema. The agent
prelude independently caps the complete rendered correction diagnostic before
adding it to model history.
ExecutionInput carries that authority with the bounded rejection
classification;
CommandEngine binds that authority to the diagnostic source independently of
the selected path. Diagnostic construction then requires the path authority to
match the source binding, so a path minted from another contract is rejected
even when both are contract-authorized. The same check rejects recombining a
path and source from different classified branches of one contract, while a
path declared only by another union branch cannot be minted. It does not parse
flattened strings or retain the rejected input. Manifest contract-load failures
retain their
portable logical contract name, and bundle failures attributed to one
component retain its portable origin (falling back to its safe component ID);
directory and memory acquisition therefore produce the same public provenance.
External override records that cross aggregate acquisition limits retain the
fixed override source role instead of being attributed to the manifest.
Assembly compiles components, builds providers, constructs workflow and mission
environments, freezes limits and inventories, and returns one
PtcRunner.Kernel.RunConfig. A configuration is one-shot. The Runner owns its
state and attached provider work until terminal publication. Provider-bearing
assembly opens one PtcRunner.Kernel.ProviderSession; each selected provider
gets one scoped PtcRunner.Kernel.ResourceRegistrar. RunConfig retains only
that session rather than an open-ended list of close functions. The caller
retains the separately constructed provider registry; closing a build does not
make a reusable embedding registry stale.
Each acquisition scope is inert through preparation and local preflight,
activates immediately before acquisition, then either commits one idempotent
provider closer or aborts. Each registrar supplies a private signal owner for
process and port roots plus one private scope controller. A provider root must
monitor the signal owner before its init callback synchronously registers
through the registrar; only then may its start operation return. The controller
is a narrow registration gate into one authoritative cleanup owner. Handoff and
cleanup address that owner directly, so they remain available if the gate
stalls. The owner serializes registration, terminalization handoff, normal
cleanup, and session-crash cleanup. Ports must
be owned by a registered process. Abort drains only that scope, while normal
cleanup runs the committed closer before the cleanup owner stops the signal
owner. Roots then get a normal owner-down shutdown window; survivors are killed
within the same cleanup deadline, and delayed termination observation remains
in a separately bounded tail. An unsettled OAuth release or persistence root
leaves that set only after it has stopped accepting work and transferred
ownership to its bounded retry owner. On abnormal session death, the cleanup
owner continues accepting terminal handoffs directly during the cooperative
owner-down window and seals the set when force-close begins. This
avoids a start-then-register gap, keeps provisional roots isolated, and removes
reconciliation races between cleanup paths.
Before callbacks can start, execution transfers the session from its build
creator to the Runner or REPL session owner and binds it to the run's one
provider-task owner. That owner is a separate process outside both lifecycles;
it monitors the session and the run state, so in-flight Kernel provider work is
drained before any closer runs and killed outright when either lifecycle
disappears — including a session terminated at its cleanup deadline, where
terminate/2 never runs. The drain also ends that owner, so an attachment
racing it is refused rather than accepted behind the closers.
Normal close and lifecycle-owner death share one bounded reverse-order resource
drain. Provider-free assembly carries no session and starts no provider owner.
Construction failures close already-built resources in reverse order.
Frontends that build but do not execute a configuration must call the
documented RunBuilder close operation.
Directory acquisition opens each referenced logical document at most once and
compilation uses only the retained bytes. This prevents a single captured path
from changing between validation and compilation. It is not a transactional
snapshot across independent files: trusted deployments must keep an
application directory quiescent during closure acquisition or publish an
immutable versioned directory. Memory acquisition rejects unused supplied
documents, so its map is the exact referenced closure. A multi-segment memory
manifest name establishes the same logical root as the directory containing a
filesystem manifest: component, input, contract, and selected-input names are
resolved relative to it, and the transport prefix does not consume their
logical-name byte or segment limits. Aggregate memory bytes are capped before
document UTF-8 scans.
Acquisition failures originating in an explicit input or component override
retain only a closed {:source_role, role, reason} tag. Command projection
uses that tag to select the fixed public input.json or
component-override.json provenance; it never retains the caller's path.
Descriptor decoding may additionally retain a typed path authorized by the
closed override schema — four required fields plus an optional closed
provenance object. Duplicate and unknown fields expose only
their safe parent; an invalid declared field may expose that declared field.
Descriptor/source byte ceilings and descriptor JSON depth/node ceilings remain
structural document_limit_exceeded failures through both captured and
external override acquisition; they are not collapsed into an override-schema
failure.
Manifest-declared input failures remain attributed to ptc.json.
application_content_digest is input-independent identity for captured
application content. It uses the versioned
ptc.application-content.v2\0 framing documented by
PtcRunner.Kernel.ApplicationPackage: a sorted record stream covers the
projected manifest, environment-and-mission-qualified local and shipped component source,
direct dependency lists, exact contract bytes, and verified override
identities. Override attribution — the resolved environment plus asserted
authoring provenance — is deliberately excluded from that stream and travels
only on the package-facing projection. Content identity answers what an
application is, not who claims to have written part of it, so an asserted
timestamp or acceptance flag must never perturb the digest. The two paths look
alike and are not: ApplicationPackage strips the environment once for the
package projection and once more for the content record whose name already
encodes it. Changing the second would alter every override-bearing digest. The complete manifest input declaration is replaced by the fixed
{"$ptc_input":"excluded"} marker. Input form, logical name, bytes, value, and
digest therefore cannot perturb content identity.
Identity-bearing semantic JSON uses
PtcRunner.Kernel.TypedCanonicalJSON (TJCS). It tags every node before
canonical encoding, so integers and binary64 floats retain different
identities, negative zero is preserved, and arbitrarily large JSON integers
never collapse through IEEE-754. PtcRunner.Kernel.StrictJSON supplies the
shared duplicate-key, UTF-8, finite-number, depth-64, and node-100,000
admission boundary in a time- and heap-bounded worker.
Contract behavior identity is computed from the compiled normalized schema.
Its schema-aware traversal removes title and description only at admitted
schema positions and preserves application property names with those literal
spellings. Exact raw contract bytes remain part of content identity, while the
ptc.contract-behavior.v1\0 hash represents normalized behavior.
ptc_semantic_revision is sem1- plus lowercase SHA-256 over the generated
semantic-build projection and the exact Elixir, OTP, ERTS, BEAM architecture,
compiled scheduler-derived pmap default, conditionally compiled semantic-module
presence, and an explicit runtime-dependency verification mode. Development
and test builds use build_projection mode: the checked-in publisher dependency
projection remains part of the identity, but constructing a short-lived command
does not reopen and hash every compiled dependency artifact. All other build
environments use verified mode and add the consuming-build dependency-artifact
projection, so custom release environments such as staging remain safe by
default. The modes are part of the hashed input and therefore cannot collide.
Verified mode starts from audited runtime roots, traverses required, included,
and optional .app metadata, records absent optional applications, and follows
the required closure of every application that is present. Dependency
presence, regular-file identity, the complete owner/group/other execute mask,
and the bytes under each present dependency's compiled ebin and priv
directories are captured once under the trusted immutable-release assumption.
Artifact bytes are hashed in bounded chunks, so revision construction does not
retain the complete dependency closure in memory. Compatible downstream
resolutions, scheduler-dependent compiled defaults, conditional-compilation
outcomes, optional-dependency presence, and execute-mask changes therefore
cannot reuse a verified release revision. Repository releases are compiled with
MIX_ENV=prod; the standalone-release verification asserts that verified mode
was compiled into the artifact.
priv/semantic_build_inventory.exs owns the classified source boundaries,
code-owned Mix application defaults, explicit semantic files, conditionally
compiled semantic modules, runtime roots, publisher dependency closure, and
local path-dependency content.
The command parser, outcome/envelope/diagnostic/path types, sealed contract
classification evidence, help/version data, and diagnostic catalog are
explicitly classified as frontend contracts and excluded from this
execution-semantics closure. RunCoordinator,
PreparedRun, and ProviderActivity remain included because they participate
in execution preparation and ownership. A frontend wording or argv-only change
therefore does not invalidate application identity, while a coordinator change
does.
Regenerate with mix regen (or mix ptc.gen_semantic_revision) on main before
tagging a release. The release gate runs mix ptc.gen_semantic_revision --check and fails on
dependency inventory drift, missing classified paths, changed semantic bytes,
or a stale projection. It runs there and nowhere else: the projection's hashes
cover the whole source closure, so regenerating it per branch made every pair
of concurrent branches conflict. Between releases the committed projection may
therefore lag the tree. That is safe for the checks that consume it —
ApplicationPackage.valid?/1 compares a package's recorded revision against
SemanticRevision.current(), and both read the same compiled constant and
mode, so the comparison stays self-consistent regardless of the file's age.
What lags in a development build is the accuracy of the identity as a
description of the source and consuming dependency artifacts, which is
precisely what the production compile and release gate re-establish. A verified
revision is conservative: comments, refactors, dependency changes, or runtime
patch changes may produce a new value even when observed behavior is unchanged.
PtcRunner.Kernel.compile_bundle/1 compiles a closed component dependency
graph into one PtcRunner.Kernel.FrozenBundle. Compilation validates code and
records requirements; it grants no authority.
FrozenBundle.hash is the canonical V2 identity of the complete component
graph. It covers each component ID, source hash, and sorted unique direct
dependency list in a domain-separated, length-framed byte format. Rewiring an
edge therefore changes bundle identity even when every component ID and source
byte remains unchanged.
Authority appears only when a frozen bundle is placed in an environment:
| Environment | Purpose | Typical grants |
|---|---|---|
WorkflowEnvironment |
trusted orchestration | model requests, annotations, subordinate evaluation |
MissionEnvironment |
confined generated programs | narrowly selected files, remote tools, or trace queries |
The subordinate evaluator receives only the mission environment. It never
inherits or falls back to workflow capabilities. Preserve that separation in
data structures and function inputs rather than relying on symbol filtering.
Parameterized subordinate evaluation overlays validated JSON at
data/params without changing the mission grant or the opaque program source.
Reserved runtime tools use an explicit ledger-argument projector so canonical
public evidence retains source and parameter identities, never their payloads.
The workflow-only kernel-check-source route reuses the same production
compile stage as mission evaluation. RunState.reserve_source_check/1 charges
the independent subordinate_source_checks budget and snapshots native memory
plus its continuation revision without acquiring the evaluation lease. After
compile, finish_source_check/2 lets closure/deadline or a committed revision
win before a result is published. Checks therefore execute no AST, call no
mission capability, and mutate no continuation. Source is bounded before it is
hashed; an oversized request exposes only its byte count. The trusted-tool
ledger likewise retains only source identity for accepted-size requests.
A run holds exactly one evaluation lease, with two admission modes.
RunState.reserve_evaluation/1 stays fail-fast: :busy while another caller
holds the lease (or a dead evaluation's provider reservations are still
draining), :limit_exceeded once subordinate_evaluations is spent; a
refusal charges no budget. reserve_evaluation/2 with :block — the mode
the kernel-eval tool route uses — parks the caller in a FIFO admission
queue instead, because concurrent agent loops under pcalls collide on the
lease as a matter of course and PTC-Lisp offers them no way to wait or
retry. The provider call happens outside the lease, so branches overlap
freely until they reach kernel/eval-source; contention then serializes the
brief evaluation phase rather than failing the workflow.
The queue's wait is bounded server-side by evaluation_admission_timeout_ms
and the run deadline — the blocking client call is infinite precisely
because the owner's own timers answer first, replying :admission_timeout
or :deadline_expired. Each waiter's absolute deadline, not its timer, is
authoritative: every grant re-checks it, so a lease release ahead of the
timer message cannot admit an expired waiter. Admission re-checks closure,
deadline, and budget when the lease frees, drains rejected waiters with
typed errors, and grants only when no reservation still references a dead
evaluation's lease — the gate that keeps a new evaluation from overlapping
the old one's external effects. That gate is completed by lease-carrying
mission tool grants: every mission capability call presents the lease of
the evaluation that constructed it, and RunState rejects a stale lease
with :stale_evaluation, so a dead evaluation's lingering sandbox cannot
attribute a late call to the next admitted evaluation either. Every enqueued waiter receives exactly one reply, or none only
because its caller died; no transition may drop a parked from, including
the parked release_evaluation_status/2 waiter, which is always answered
{:ok, status} once its lease-scoped reservations drain. While waiters are
queued, fail-fast callers and reserve_source_check/1 keep observing
:busy — queued admission has priority. A timed-out admission is a host
condition, not a program error: the shipped agent.core loop still fails
the outer workflow as evaluation-unavailable without spending a turn or a
model call. pmap and pcalls still reject fail as a worker control
signal, but they retain its bounded, payload-free failure taxonomy, so that
refusal remains publicly classifiable without exposing the failure value.
Each PtcRunner.Kernel.Capability freezes its public identity, effect,
visibility, bounded schemas, validator, and trusted callback. Environment
membership grants authority; descriptions and remote annotations do not.
Public prelude signatures/types and direct capability schemas are compiled
before execution and remain authoritative at their respective boundaries.
PtcRunner.Kernel.MissionInventory owns the deterministic structured and
model-facing projections of the mission API. The shipped agent.prompt
component renders the model context. When prompt-visible prelude functions
exist, they form the facade and raw capabilities are omitted from the prompt;
the underlying environment grant is unchanged.
Exact component, schema, inventory, prompt-rendering, and projection rules live
in Component, FrozenBundle, Capability, MissionInventory, and the Lisp
runtime-contract module docs.
Components may compose other components through declared acyclic dependencies.
Edges should point toward lower-level reusable behavior: capability facades may
reuse pure support components, while policy and ergonomics layers compose the
facades. The bundle compiler admits only public exports from direct dependency
namespaces and derives capability requirements through those calls. Evaluated
source can call every public export in the resolved bundle, including
:discoverable exports omitted from model prompts, so every new edge is also a
callable-surface review. Those exports are reachable in practice as well as in
principle: dir, apropos, doc, and export-meta read the attached
prelude's public exports at runtime, filtered to what the calling program may
invoke, so what a program can discover matches what it can call.
Installed-library selections expand transitively in manifests and through
Library.resolve_components/1; raw compile_bundle/1 callers still provide a
closed set. Fixed analysis profiles go further: their declared component,
namespace, and capability sets must equal the resolved environment exactly.
Declaration order is not significant. Runtime identity records components in
the bundle compiler's canonical dependency order and namespaces and
capabilities in lexical order, so harmless recipe reordering does not change
the digest. The bundle hash still identifies the exact compiled build, and a
source-only bug fix changes the digest. Change the profile ID when the declared
callable surface, authority, limits, persistence policy, result policy, or
other published behavioral contract changes.
The reserved kernel-eval capability delegates dynamic or compiled mission
programs to PtcRunner.Kernel.Evaluation. One transactional continuation is
owned by RunState for the whole run.
At a high level:
- ordinary success continues the workflow and commits bounded definitions and result history;
- explicit
returncommits and completes successfully; - explicit
failterminates as a workflow failure; and - parse, analysis, runtime, limit, projection, and capability failures do not alter the previously committed continuation.
Only inert public observations cross back into workflow Lisp. Native
definitions, result history, callables, Java provenance, and evaluator context
remain inside the continuation owner. The exact outcome algebra, history
rules, correction feedback, and commit preconditions are documented by
PtcRunner.Kernel.Evaluation, PtcRunner.Kernel.RunState, and
PtcRunner.Kernel.ReplSession.
Evaluator audit effects use one PtcRunner.Lisp.Eval.Effects representation.
Nested host callbacks use the evaluator capture boundary, and parallel
evaluation separates Lisp semantics from process scheduling. Do not duplicate
effect merge/order rules or outcome transport outside the owning
PtcRunner.Lisp.Eval.* modules.
The shipped agent.core component owns the multi-turn provider/evaluation
loop. Prompt wording and transition policy belong in agent.prompt; hard
limits and capability authority remain in the Kernel.
PtcRunner.Kernel.RunState is the single mutable owner for one run. It owns
the deadline, open/closed state, reservations, usage, protocol errors, terminal
failure, and mission continuation.
Any decision that depends on current owner state must be one atomic owner operation. A separate read followed by an update is a race and is review-blocking.
PtcRunner.Kernel.Dispatcher validates calls, reserves budgets, supervises the
trusted callback process, normalizes results, and rejects late completion after
timeout or closure. Run termination kills and drains attached provider work
before connector cleanup. Providers are trusted host extensions: the Kernel
contains ordinary faults and bounded results, but it is not a security
sandbox for malicious BEAM code.
When a model-authored call fails the capability's compiled input schema before
callback entry, Dispatcher retains at most three correction facts for the agent
loop: the schema-declared argument path, the violated keyword, and a small
declared numeric, string-length, or item-count bound when one fits. Enum and
const literals are always omitted. Paths are resolved through the frozen
schema, the root is $, array positions are rendered as [], and
non-identifier names use JSON-quoted bracket notation so punctuation and
controls cannot alter path structure. The bounded candidate set is sorted
before the first three facts are retained. Explanations are omitted when the
submitted structure or raw error set exceeds its fixed work budget.
Dispatcher enforces capability_argument_bytes before entering JSV. Validation
and bounded projection then run in one worker whose timeout is the minimum of
the requested call timeout, remaining run time, the authority environment's
timeout, and the enclosing execution deadline after a small result-handoff
reserve. The execution context supplies the heap ceiling: ordinary workflow
runs use the workflow heap, while mission evaluation and REPL execution use
the evaluation heap even though REPL capabilities retain workflow authority.
Proven invalidity is distinct from timeout, heap exhaustion, a validator crash,
or an unrecognized validator result. Those operational failures return
capability_unavailable/input_validation_unavailable without reserving a
capability call, charging protocol_errors, invoking the callback, or emitting
capability lifecycle events. The evaluator records that result's host
provenance privately so agent.core fails the outer workflow without a
correction turn. The low-level cause is not public. Submitted values,
undeclared property names, and enum or const literals never enter feedback or
canonical events. A capability's custom semantic validator remains opaque
because its rejection reason is not a schema-authored fact.
Limits are host-enforced ceilings. PtcRunner.Kernel.LimitCatalog is the
checked-in authority for every field's public name, scope, compiled and
installed defaults, inclusive range, and effective-identity participation.
Every complete installed-limits struct is checked against those rows before an
application source or provider builder is opened. Manifests may narrow only
:manifest_narrowable rows; installed-only operational timeouts pass through
unchanged. Host and manifest schemas are generated from the same scoped rows,
and documentation generation fails when the catalog and Limits struct
diverge. PtcRunner.Kernel.Limits, RunState, BoundedWorker, and
Dispatcher document exact counters, deadlines, byte accounting, and cleanup
ordering. Mission compilation and source checking explicitly use
evaluation_heap_words as Lisp.run_native/2's compile_max_heap; callers
that do not set it compile under their own :max_heap, so no ambient
application default can move a sealed run's compile ceiling. Workflow, mission,
and REPL execution likewise pass the effective live_provider_tasks limit as
Lisp's global max_parallel_workers, so pmap and pcalls batch at the same
ceiling that bounds their concurrent provider callbacks.
Standalone PtcRunner.Kernel.ReplSession is process-affine. Passing its public
value does not transfer ownership. A product that needs transferable or
multi-client sessions requires a separate supervised abstraction rather than
weakening the current owner contract.
The public execution algebra is {:ok, %PtcRunner.Kernel.Result{}} or
{:error, %PtcRunner.Kernel.Error{}}. Capability failures are normally
bounded values returned to Lisp so workflow policy can decide whether they are
terminal.
Observability uses separate planes:
| Plane | Contract |
|---|---|
| Logger | sparse operator diagnostics; no prompts, source, capability payloads, credentials, or transport secrets |
| Telemetry | bounded low-cardinality measurements and closed metadata |
| EventSink/TraceLog | sanitized bounded canonical events and queries |
| InspectionSink/InspectionArtifact | explicit bounded private model, source, capability, and eligible terminal-result evidence |
Do not copy a private field into canonical events merely because it helps debugging. Add correlation metadata to the canonical plane and retain exact payloads only under explicit private authority.
PtcRunner.Kernel.EventSink owns sequence, identity, bounds, loss policy,
terminal finalization, and the immutable terminal batch.
PtcRunner.Kernel.TraceLog owns canonical validation, persistence, discovery,
and queries. PtcRunner.Kernel.InspectionArtifact owns the exact private
artifact grammar, exclusive persistence, loading, and correlation checks.
Inspection V5 covers provider-neutral capability, source, and model evidence,
paired decoded MCP request/response bodies correlated to an existing capability
attempt, and workflow execution prints and errors. Mission-owned source,
capability, prelude, and MCP evidence carries the exact mission name, so
identical component or tool names remain distinguishable. It also admits at
most one strictly JSON terminal result, self-hashed with the same deterministic
canonical JSON identity carried by a successful run-stopped. Ineligible
native results and failed runs emit no result record. MCP inspection records
never include rendered headers or subprocess environment values.
PtcRunner.Kernel.SafeMetadata owns the closed labels and annotation
vocabulary.
The Viewer and PtcRunner.Kernel.RunAnalysis delegate to the immutable
snapshots rather than defining another event model. Custom Inspect
implementations and redacted owner status are defense-in-depth; runtime code
must still avoid logging payload-bearing structures directly.
The catalog has no implicit providers. CLI runs receive exactly the aliases declared by a strict host installation; trusted Elixir embedding constructs an explicit catalog of custom descriptor/implementation pairs. Host installations provide workflow LLM plus mission MCP and native snapshot sources. Catalog construction is inert and never accepts an OAuth principal or store context.
Provider implementations later return capabilities plus optional safe connector metadata
and an idempotent closer. Staged builders may also exchange bounded code-owned
acquisition services after the global preflight and credential barrier; these
opaque values never enter environments or artifacts. A run-bound registry keeps
every builder bound to its sealed descriptor, so preparation reporting a data
class or accepted-class set other than the declared one fails with
provider_declaration_mismatch before preflight, credential resolution, or
acquisition; the declaration phase 5 and sink authorization used stays
authoritative. ProviderAcquisition
resolves those dependencies and immediately commits each successful resource
to the provider session's cleanup stack. For active commands, its preparation,
preflight, and acquisition callbacks—including work behind the private host
installation owner—run in owner-linked workers bounded by the remaining shared
run deadline and provider heap limit. Preflight releases share the provider
cleanup budget. RunBuilder assembles the returned capabilities and transfers
the session into the run lifecycle. Exact
declarative selection grammar belongs in SelectionRules; active transport
behavior belongs in each provider module and the later runtime dispatcher.
Ambient .env acquisition belongs to the CLI frontend, not the Kernel. The
Mix adapter decides once, before entering shared run dispatch, whether a
selected live-LLM installation declares an environment-backed credential, and
loads the nearest .env there, containing a loader failure as a closed command
diagnostic. No Kernel module loads it, so an embedding acquires ambient
environment state only when it chooses to.
Provider occurrence contexts are path-free. They carry safe display identity, application content and effective digests, final bundle hashes, input authority, destination/index, an internal execution-scope ID, effective limits, and derived data/flow/event classes. Application directories, input values, reader callbacks, descriptors, credentials, and host implementation details never cross the package/selection boundary. Any provider-owned filesystem roots remain captured only by the trusted implementation.
The MCP adapter is one host-installed source with typed Streamable HTTP and
stdio transports. Endpoints or process launch details, credentials, upstream
mapping, read/write effects, and installed ceilings are host authority; server
annotations are not. A manifest may only select mapped names and narrow
visibility or limits. Every write-bearing installation requires an explicit
non-empty manifest allow, while an all-read installation retains the omitted
allow convenience.
PtcRunner.Kernel.MCPSource owns common discovery and capability assembly,
PtcRunner.Kernel.MCPProtocol owns pure protocol validation and normalization,
and the transport owners bound and correlate each request. Their module docs
define the exact behavior. MCP tool errors are closed by default; a host
mapping may opt into validated feedback bounded to 1,024 bytes, with terminal
control characters replaced before exposure. Runtime
calls propagate only a derived W3C traceparent, with no baggage or
operator-supplied trace value crossing the provider boundary. Immutable MCP
sources may freeze a host-installed content identity during assembly; this is
provider provenance, not a mission capability grant.
MCPSource labels deterministic local call failures :not_dispatched and
anything after the HTTP operation begins or a stdio write may have been accepted
:possibly_dispatched. Dispatcher consumes that internal evidence: a possible
write becomes non-retryable with indeterminate mutation state, while the
transport provenance never reaches Lisp.
Streamable HTTP and MCP OAuth use the shared direct-Mint
MCPHTTPAdapter HTTP/1 boundary. Each request owns one non-pooled connection,
enforces cumulative response-header and body ceilings, and carries one absolute
deadline across bounded DNS resolution, peer pinning, connection, and response
streaming.
Two further ceilings bound what a peer may deliver, as opposed to what is kept, and both are enforced before Mint parses anything.
:max_receive_bytes is applied as the socket's buffer and caps one socket
message. Because Mint raises buffer to max(buffer, sndbuf, recbuf) whenever
it initiates a connection, the adapter connects in :passive mode, applies the
ceiling to the still-unarmed socket, and activates afterwards. Over TLS the
ceiling bounds the ciphertext read, so one TLS record of already-decrypted
carry-over may be delivered with it. The receive loop additionally refuses any
single message over that declared maximum, because the socket option makes the
bound true rather than enforcing it: Mint's raise happens at the end of its
connect, and over TLS the ssl process accumulates under the raised value if
the worker is descheduled before the ceiling is reapplied.
A derived pending ceiling — max_header_bytes plus one whole delivered message
(max_receive_bytes plus one TLS record), so that a single delivered message can
never breach it alone — caps
what Mint may hold unparsed, and resets whenever Mint returns any response. A
peer that never completes a status line, a chunk-size line, or a chunk extension
yields no response, so no ceiling on a parsed value can see it and Mint buffers
the remainder without one of its own. The reset is what keeps the ceiling
independent of the transfer encoding: chunked framing costs bytes per chunk, so
a cumulative wire ceiling would refuse a legitimate finely chunked body.
Resident bytes are bounded by the accumulated body and headers, the pending
ceiling, and two delivered messages — a reset zeroes the counter while Mint's
leftover survives it, and that leftover is at most one message. Total bytes
read is not bounded: bare 1xx informational responses reset the counter and
advance no ceiling, so a peer can spend bandwidth until the deadline without
accumulating anything. Returning :halt after a complete SSE response, response-size
rejection, or SSE parser rejection closes the response stream. Killing the
request task on deadline expiry, caller death, provider close, or source-owner
death closes it as well. HTTP cancellation never sends
notifications/cancelled; stdio retains its protocol notification.
Cancellation remains advisory and cannot make a possibly dispatched write
retryable.
OAuth authority and grant state stays behind MCPOAuth.Store.
MCPOAuth.Authorization owns explicit, callback-agnostic authorization-code
flows, while a principal-scoped MCPOAuth.TokenManager coordinates only local
single-flight refresh leadership. Discovery, store round-trips, credential
resolution, and network I/O run in bounded non-owner tasks rather than either
owner callback. The store atomically fences bearer admissions, refresh/code
dispatch, generation commits, requirements, and retirement. A worker death
after token dispatch is positive process fencing: the in-memory adapter
terminalizes the exact authorization flow or poisons the refresh generation;
lease expiry by itself never restores a possibly spent credential.
Request completion uses a separate bounded cleanup budget and acknowledges its
admission release before removing owner state. If an admitted worker exits
without that acknowledgement, the request context retains the opaque release
operation and retries it asynchronously; durable adapters cannot depend on
BEAM process monitors to drain an admission. An unacknowledged ordinary
release irreversibly terminates the admitted worker before that detached
cleanup begins. OAuth admission runs in context-owned asynchronous work. If the
request caller exits before receiving its header, the context adopts and
releases any admission created by that work. Provider close returns an error
and leaves that cleanup owner running while a release remains unacknowledged;
it never reports success by killing the retry state.
Likewise, 401 rejection and valid 403 insufficient_scope handling install a
runtime-shared local generation/requirement fence before durable persistence,
using a fresh bounded post-response transition budget rather than the exhausted
HTTP request deadline. A definitive 401 status line ends processing
immediately, even if the remaining header block is oversized, malformed, or
stalled. A 403 ends processing after its complete bounded challenge headers
and before its body. The HTTP response callback contacts the token manager
before returning a result to the bounded provider task. The manager starts the
bounded persistence worker atomically with the shared fence, so a Dispatcher
timeout cannot skip the durable transition.
A store failure is a transport failure and cannot make that manager—or a
replacement manager for the same local store and grant key—reissue the rejected
authority; only a strictly newer sufficient grant clears the local fallback.
Each secret-free fallback transition has its own process-independent
:persistent_term entry. Admission reads and clears satisfied entries in one
operation, so there is no fence-owner restart window within the running VM.
Manager shutdown drains in-flight response persistence before discarding that
fallback. Failed persistence is retained and retried once per close attempt;
continued failure makes transport shutdown fail while the runtime keeps the
shared fence. When provider acquisition fails before it can return a close
handle, the supervised OAuth cleanup owner adopts the token manager and retries
that bounded shutdown, so the retained fence cannot become an orphaned process.
The cleanup owner keeps retrying answered persistence failures because those
attempts can still make progress. A consecutive streak of unanswered close
timeouts instead has a five-minute slot budget; exhaustion terminates the
linked worker abnormally so its manager is reclaimed and the node-global slot
is released. Any answered persistence failure breaks the timeout streak.
An ordinary or malformed dynamic 403 that is not one valid satisfiable
insufficient_scope challenge is a non-retryable authentication or
authorization result; it is not mislabeled as a retryable transport failure.
Concurrent managers may observe an active refresh mutation lease. Followers reload and wait outside all owner processes, bounded by their request deadline, until the winner commits a usable generation, safely releases an undispatched lease that a follower can acquire, or the grant becomes authorization-required.
The client advertises no MCP elicitation, sampling, or roots capabilities and
never retries an input_required result. On tools/call, prompts/get, or
resources/read, a structurally valid state-only result, including an empty
load-shedding request map accompanied by requestState, is a closed denied
policy refusal; a schema-valid non-empty request map is a
capability-negotiation error. Malformed request entries and
input_required on any other method are protocol errors. Provider denial is
terminal at the agent correction boundary, as are the capability-negotiation
and protocol classifications. The provider owner records that terminal
classification before publishing the result, so a later evaluator error,
timeout, or heap kill cannot trigger a correction turn. Once a tool call may
have been dispatched, all three causes preserve the operator-declared effect,
so a write remains indeterminate.
PtcRunner-owned canonical traces remain native rather than passing through
MCP. Host-installed ptc_trace_snapshot and ptc_private_trace_snapshot
providers use TraceSnapshot to capture one directory and
RunAnalysisCapability to expose the same six question-shaped operations used
by run-analysis-v1.
The former admits ordinary traces only; the latter is a private-authorized
capture of ordinary and private traces with per-run provenance. A paired private
ptc_inspection_snapshot receives that already captured trace through the
provider acquisition service, validates all artifacts and correlations before
publication, and composes both snapshots through the same
RunAnalysisCapability builder. Callers receive runs, overview, activity,
conversation, failure, and source; private evidence is unavailable rather
than replaced with primitive inspection record families.
Local analysis profiles are fixed, code-owned recipes selected through the
closed AnalysisProfileRegistry. AnalysisSessionBuilder is the host entry;
AnalysisSession, SessionTrace, and AnalysisResources share continuation,
publication, and cleanup without letting a caller supply modules,
capabilities, limits, or sink policy. run-analysis-v1 remains the Viewer and
ordinary terminal profile. private-run-analysis-v1 adds correlated
private-authorized TraceSnapshot and InspectionSnapshot captures behind a private
interactive-terminal gate. Browser or Lisp input does not supply profile
internals or paths.
| Responsibility | Primary owners |
|---|---|
| Public run boundary | Kernel, RunConfig, Result, Error |
| Components and libraries | Component, FrozenBundle, Library, BundleCompiler |
| Environment authority | Capability, WorkflowEnvironment, MissionEnvironment, Environment |
| Host/application assembly | HostConfig, HostInstallation, ApplicationSource, Manifest, ApplicationPackage, ExecutionInput, ExecutionPolicy, RunRequest, ValueContract, ResultArtifact, ProviderRegistry, RunBuilder, MissionInventory |
| Mutable resources | Limits, RunState, BoundedWorker, Dispatcher |
| Subordinate execution | Runner, Evaluation, RuntimeTools |
| Lisp internals | Lisp.Eval, Lisp.Eval.Effects, Lisp.Eval.Capture, Lisp.Eval.Parallel, Lisp.Eval.ParallelRunner |
| Providers | HostConfig, HostInstallation, ProviderRegistry, ProviderAcquisition, ProviderSession, LLMCapability, MCPSource, MCPProtocol, RunAnalysisCapability |
| Canonical/private evidence | EventSink, TraceLog, TraceSnapshot, InspectionSink, InspectionArtifact, InspectionSnapshot, InspectionQuery, RunAnalysis, SafeMetadata |
| Interactive evaluation | ReplSession, AnalysisProfileRegistry, AnalysisSessionBuilder, AnalysisSession, SessionTrace |
Modules grouped as Kernel internals in ExDoc remain documented for maintenance and review. They are not alternative supported entry points.
- Put commands, endpoints, credentials, effects, and outer ceilings under host authority.
- Freeze schemas and safe metadata before execution.
- Grant the capability only through the intended environment.
- Bound callback execution and normalize every public result/error.
- Prove timeout, owner death, late result, cleanup, redaction, and denied destination behavior through an integration path.
- Keep policy in shipped/local PTC-Lisp unless the Kernel must enforce it.
- Update the single evaluator effect/outcome owner rather than adding a parallel transport.
- Preserve transactional continuation and public projection.
- Test direct, higher-order, parallel, and agent consumers when applicable.
- Update the language specification only for language semantics.
- Reuse
ApplicationSource,ApplicationPackage,RunRequest, andRunBuilder. - Keep manifest data non-executable, logical names portable, and directory acquisition confined.
- Preserve workflow/mission authority and installed-ceiling precedence.
- Keep input outside application content identity and destinations outside the sealed execution policy.
- Keep stdout/stderr and canonical/private artifacts unambiguous.
- Exercise the same configuration through normal run and REPL paths where their contracts overlap.
- Choose the correct observability plane before adding a field.
- Keep canonical metadata bounded, sanitized, and queryable.
- Keep private payload capture explicit, correlated, no-clobber, and independently bounded.
- Update TraceLog/Viewer consumers from the authoritative schema owner.
Update the authoritative Java manifest and structured oracle cases together. Use the generated Java documentation and the pinned JVM/Babashka/PTC conformance tasks as the behavior and overload-selection evidence. Do not add fallback dispatch through the ordinary Lisp environment.
Prefer contract-level integration tests over tests that mirror implementation:
core_contract_test.exs— authority, outcomes, limits, continuation, and dispatch;- manifest and Mix task tests — assembly and frontend behavior;
- provider-specific tests — schema, transport, timeout, cleanup, and redaction;
- event/trace/inspection tests — canonical and private boundaries;
agent_library_test.exs— shipped workflow policy;- Java oracle/conformance tests — admitted Java behavior; and
- tutorial/E2E tests — real user and provider flows.
Run mix precommit before every commit. For an ordinary push, let the tracked
pre-push hook run the full root/Viewer tests and the canonical mix prepush
checks once. Invoke mix prepush directly only for diagnosis or when hooks are
unavailable.
Credential-free interoperability tests may run as focused push and
pull-request jobs even when tagged :e2e. Secret-dependent and model-driven
:e2e tests remain excluded from those pipelines and run through the
scheduled/manual Integration Tests workflow. Tagged tests must skip cleanly
when required credentials or network are absent, avoid fixture-only
assumptions, and assert clean instrumentation for agent flows. Live providers
are nondeterministic; deterministic tests remain the authority for confinement,
ownership, accounting, rollback, and cleanup.
