The Kernel command surface runs through mix ptc in a source checkout and
through the runtime-included ptc_runner release's bin/ptc entrypoint. Both
frontends use the same parser, preparation, execution, result, trace, and
rendering paths.
| Command | Purpose |
|---|---|
mix ptc help [COMMAND] |
Show root or per-command help generated from the accepted declarations |
mix ptc --version |
Show the packaged command version |
mix ptc init DIRECTORY |
Atomically publish the validated two-file application scaffold |
mix ptc validate MANIFEST |
Validate and compile a manifest without running its workflow |
mix ptc run MANIFEST |
Run the manifest's qualified entry and render its public result |
mix ptc run MANIFEST --host-config HOST.json |
Install the provider aliases a provider-bearing manifest selects |
mix ptc run MANIFEST --input INPUT.json |
Run with a confined alternate input object |
mix ptc run MANIFEST --output VALUE.json |
Write only the validated result value |
mix ptc run MANIFEST --trace-dir DIR |
Persist bounded canonical events under the command run reference (DIR must already exist) |
mix ptc run MANIFEST --inspect RUN.inspection.jsonl |
Also write the owner-only private artifact |
mix ptc run MANIFEST --envelope ENVELOPE.json |
Atomically publish the machine-readable V2 command envelope |
mix ptc run MANIFEST --component-override-descriptor D.json |
Compile one selected component from verified replacement source |
mix ptc doctor [MANIFEST] |
Inspect application and provider readiness without running the workflow |
mix ptc doctor MANIFEST --host-config HOST.json --connect |
Perform active provider connectivity checks |
mix ptc models --host-config HOST.json |
List the host document's installed model aliases |
mix ptc.materialize MANIFEST --workflow --component ID --out DIR --source S.clj |
Publish model-authored source as a gated candidate component |
mix ptc repl |
Start the direct transactional PTC-Lisp REPL |
mix ptc repl -e EXPR -l SETUP.clj |
Run repeatable expressions with optional setup |
mix ptc repl --manifest MANIFEST [--host-config HOST.json] |
Reuse a manifest's workflow bundle and one provider session |
mix ptc repl --profile PROFILE --resource NAME=DIR |
Query an immutable trace or inspection capture |
mix ptc.viewer --trace-dir DIR |
Browse canonical JSONL traces locally |
mix ptc init DIRECTORY creates the exact two-file scaffold documented in
Getting started.
Initialization validates the scaffold before filesystem access and publishes
the completed directory atomically without replacing an existing directory or
symlink. A refused initialization leaves the filesystem untouched and names the
condition that stopped it: publication/initialization_target_exists for an
existing target, publication/initialization_parent_missing or
publication/initialization_parent_unusable for the parent directory, and
publication/initialization_failed when no safe public cause can be disclosed.
Every failure reported before publication can be retried once its condition is
resolved.
Run mix ptc help COMMAND or bin/ptc help COMMAND for help generated from the
same per-command declarations the strict parser accepts.
A provider-bearing manifest requires --host-config. Use the standalone
ptc doctor MANIFEST --host-config HOST.json --connect operation when active
provider connectivity must be checked without running the workflow.
Use --show-model-selectors when doctor should also print safe model selectors;
endpoint-bearing openai-compat: selectors remain omitted.
Doctor results carry an explicit readiness. Plain doctor is unverified: it
performs local checks but leaves provider-active rows skipped. A successful
--connect report is ready. When an active diagnostic identifies one report
row, doctor exits nonzero but still renders the JSON report with readiness: "failed"; the named envelope, when requested, also retains the complete
diagnostic. Other pending rows say not_verified_due_to_failure, which means no
sealed evidence was retained for them. Unattributable operation failures,
cleanup failures, owner failures, and internal invariants remain ordinary fatal
diagnostics rather than synthesized health findings.
A fresh Mix invocation safely configures and starts a selected optional
provider application only after the active provider lifecycle begins. A later ptc run
invocation in the same VM reuses an already-running application as host-owned,
so task chaining and iex -S mix do not require restarting the VM. For ReqLLM,
the fresh command-owned start configures its default HTTP/1 Finch pool as one
shard sized from the installed live_provider_tasks ceiling. A manifest may
narrow its own effective limit but does not resize that VM-lifetime pool. Later
host-owned commands keep the already-running application's configuration, so
an embedding that changes installed ceilings in one VM owns the corresponding
aggregate pool-capacity policy.
--trace-dir writes into an existing directory; it does not create one. Create
it first:
mkdir -p traces
mix ptc run ptc.json
mix ptc run ptc.json --trace-dir traces
mix ptc run ptc.json \
--trace-dir traces \
--inspect traces/run.inspection.jsonlThe command creates <run_ref>.jsonl for a normal run and the reserved
<run_ref>.private.jsonl form for a private run:
mix ptc run private-ptc.json \
--trace-dir traces \
--private-output results/run.private.jsonThe envelope reports the run reference and artifact class without publishing a destination path.
Configured result, trace, and inspection destinations must be pairwise distinct after resolving their existing parent-directory identity; final names are Unicode case-folded and normalized so the rule remains safe on case-insensitive filesystems. Deterministic destination conflicts and filesystem-invalid or deterministically unwritable destinations are rejected before provider acquisition; each persistence path still uses its own publication checks to close races after preflight.
Snapshot capture failures report the source kind, measured retained bytes, and
configured retained-byte ceiling when the decoded trace or inspection catalog
is too large. Workflow timeouts likewise name the effective limit
(workflow_timeout_ms or the remaining run_duration_ms), its value, and
whether compilation or execution exhausted it.
--inspect is a host opt-in development feature. It writes a separate bounded
owner-only artifact containing sensitive execution details. Do not publish it
with normal traces. Artifact publication through --output,
--private-output, or --inspect requires a Unix host and POSIX-compatible
mkdir and id executables on PATH; command preflight reports the matching
artifact destination as unavailable when that authority or mode-at-create
primitive is unavailable. Publication validates
both lexical and resolved physical ancestry and rejects an untrusted owner or a
group/other-writable directory without sticky-directory protection, because
another tenant could otherwise replace the private temporary directory before
the artifact is opened. A final parent must also grant create access to the
effective owner, group, or other permission class.
Destination preflight reports an untrusted ancestor under its own reason —
result_destination_unsafe, inspection_destination_unsafe, or
trace_destination_unsafe — rather than the generic persistence or
unavailability failure, because the remedy differs: an untrusted ancestor names
a directory whose owner or mode an operator can correct, while a missing id
or mkdir names an unusable host. Result publication shares that preflight, so
--output and --private-output report result_destination_unsafe too when a
destination turns unsafe after preflight; trace appends and inspection
publication keep their single closed reason.
Ancestry is validated for every result and inspection destination whatever its
class, for every private trace, and for a normal trace that must still be
created. An existing normal trace is validated for writability only. The
append-lock directory under TMPDIR is validated separately and reports
source_unavailable for any fault, including an untrusted ancestor of its
own.
Private trace creation uses the same host primitives and ancestry checks. A
missing trace requires both mkdir and id and is published at mode 0600
before use. An existing private trace requires id, must already be mode
0600, owned by the current process authority or root, and writable by the
effective authority; its parent needs traversal but not create access. The
append retains its permission-checked descriptor. Every trace append also
requires sh and either lockf or flock for its cross-runtime lease. The
per-authority lease directory is owner-only; its first creation additionally
requires mkdir, while later appends validate its type, owner, and exact mode
without changing permissions through an unresolved path.
--input PATH replaces the manifest input file. --private-input PATH uses
the same confined JSON shape and classifies the complete run as private before
provider activity; the two options are mutually exclusive.
--component-override-descriptor compiles one already-selected component from
different source. It is host command authority: a manifest cannot name an
override and a generated program cannot observe one. The descriptor carries
four required fields and one optional provenance object:
{
"component_id": "my.agent",
"base_source_hash": "sha256:<64 lowercase hex>",
"source_hash": "sha256:<64 lowercase hex>",
"path": "candidate.clj",
"provenance": {
"run_id": "run-2026-08-03-0001",
"prompt_hash": "sha256:<64 lowercase hex>",
"authored_at": "2026-08-03T09:15:00Z",
"accept_widened_effect": false
}
}provenance is a closed object: unknown keys are refused, and authored_at
must parse as RFC 3339 in UTC. It records who authored a candidate and is
operator-asserted, verified by nothing — a run_id is a claim about
origin, not evidence of it. There is no model-id field, because a descriptor is
a published artifact and does not carry provider configuration; an
adapter-attested public execution model may be reached through the run id's
canonical provider snapshot, while private targets remain intentionally absent.
Provenance is deliberately
kept out of content identity, so adding it never changes
application_content_digest.
Both hashes use that sha256:-prefixed form. path resolves against the
descriptor's own canonical directory and may not escape it. The descriptor is
at most 64 KiB and the candidate source at most 1 MiB. Standalone command
diagnostics authorize descriptor paths against this exact schema — the four
required fields plus the optional provenance object described below:
duplicate or unknown fields report the safe parent pointer, while an invalid
declared field can report its public field pointer.
Crossing either byte ceiling, or the descriptor JSON depth/node ceiling, is
reported as a document-limit failure with fixed component-override provenance.
Verification is two-sided and happens before the source reaches the compiler.
source_hash proves you are compiling the bytes you believe you extracted;
base_source_hash proves those bytes were derived from the component that is
installed now, so source written against a since-changed base is rejected
rather than compiled against the wrong baseline. The file is opened once and
the bytes that were hashed are the bytes that compile — the path is never
reopened, so a file replaced mid-run cannot substitute different source.
The named component must already be selected in the descriptor's exact workflow-or-mission target; an override replaces source and never introduces a component. Its declared dependencies are preserved, so a candidate cannot quietly acquire a new one. Normal dependency, export, signature, and capability validation still applies: an override changes which source compiles, never what compilation permits.
The run-started event records the component ID, both hashes, the resolved
environment, and any asserted provenance — never the candidate source — so a
trace names the base as well as the candidate. Nothing here writes: producing
candidate source is a separate step, described next.
A model can author a working library inside a run: source handed to
kernel/eval-source may contain def/defn, and those definitions persist
for the life of the run. But a runtime defn dies with the run — it is not in
the frozen bundle, not covered by a component source hash, and absent from
mission_inventory. mix ptc.materialize closes that loop by turning authored
bytes into a candidate a later run can evaluate.
mix ptc.materialize ptc.json --workflow --component my.helper --out private/candidate \
--source authored.clj --origin-run-id run-2026-08-03-0001Source comes from --source (raw bytes) or from --from-result PATH --result-pointer /json/pointer, because mix ptc run --output writes a JSON
result artifact rather than raw Lisp. The pointer must resolve to one string;
anything else is refused rather than coerced.
Use --workflow for a workflow component or --target-mission NAME for one
exact declared mission. The selector is required and becomes the descriptor's
closed qualified target; the task never infers a target from a component ID.
--out must not exist. It is created exclusively at mode 0700 with both files
at 0600 before any content is written, because a candidate extracted from a
private artifact must not be declassified by publishing it. The exclusive
create — not a rename — is the no-clobber guarantee, since POSIX rename may
replace an existing empty directory.
The candidate is then re-acquired through the descriptor just written, which is the exact path a run takes, and gated:
| Criterion | Question |
|---|---|
| G1 | Does it resolve to one environment and compile? |
| G2 | Does every prompt-visible export declare a signature and docstring? |
| G3 | Does any export reach further than the base it replaces? |
| G4 | Are declared dependencies unchanged? (preserved by construction) |
A criterion reports blocked rather than fail when it could not run at all —
a candidate that does not compile has no export table for G2 and G3 to read.
G2 is promotion policy, not something the runtime enforces: the inventory
projects nil docstrings and contracts without objection, which is exactly why
an export promoted without them would compile, ship, and be useless to the next
run's model.
G3 compares per export, never against an application-wide union of capability
names. If a base export A requires read-tool and B requires write-tool,
A gaining write-tool adds no name outside the union while materially
widening A. A widening is refused unless you pass --accept-widened-effect,
which is recorded in the report and in the descriptor's provenance.
The gate does not check capability grants. Real capability names exist only after provider acquisition, so a candidate naming a capability no provider grants passes here and fails at run-time assembly, which stays authoritative. The gate narrows the distance to that failure; it does not remove it.
Promotion only ever replaces a selected component. To have a model author a new library, declare a placeholder component first, with the dependency surface the generated component may consume and a stub for every export its consumers call — a component declaring nothing cannot be depended on, and the base application would not compile. The model then rewrites it, and both hash checks still apply.
Promotion itself stays an explicit human decision. mix ptc.materialize
produces evidence and refuses unfit candidates; it never installs one.
A successful normal run prints the compact JSON result value. Its command
envelope additionally contains:
value— the public workflow result;usage— remaining time, capability calls, evaluations, protocol errors, closure state, and dropped events;evaluation_memory— counts and retained byte totals, never retained values.
Capability failures normally cross into PTC-Lisp as recoverable envelopes. The workflow decides whether to retry, correct, degrade, or fail. Parser, compiler, timeout, heap, source, result, quota, provider, and event failures retain bounded classifications at the Kernel boundary.
Mix and release failures use the same bounded human renderer. Failures returned
by the shared preparation and runtime path render only the closed diagnostic
catalog, their validated provider subject when present, or fixed argument
guidance; they never inspect a rejected runtime term. A provider subject renders
as provider/<alias>/<operation> and adds at workflow[<index>] or
at mission[<index>] when the failing occurrence is known. Name --envelope
when a caller also needs the stable JSON result and exit status in a file.
Envelope publication is additive: it never redirects, suppresses, or replaces
the command's normal terminal rendering.
One-shot stdout and stderr are UTF-8 streams. The runtime-included release writes their rendered text through its Unicode-mode terminal devices, so non-ASCII help, diagnostics, and JSON result strings retain their exact bytes. This presentation contract does not change the raw-byte treatment required by child stdio transports.
When --envelope names a destination, the standalone ptc command additionally
writes one V2 JSON command envelope there. When the arguments parse, every
ordinary or caught path produces exactly one envelope — except a failure to
publish the envelope itself, which exits 74 with no envelope, described below:
- success exits
0; - a classified failure exits with the primary diagnostic's exact
PtcRunner.Kernel.DiagnosticCatalogstatus, one of2through7; the catalog is authoritative because codes within one phase may intentionally have different statuses; and - a caught unexpected failure writes the closed
internal/internal_errorenvelope and exits70.
The envelope schema is priv/schemas/ptc-command-envelope-v2.schema.json.
One-shot command presentation never renders an inspected exception, arbitrary
callback result, credential, private value, provider response, selector, or
filesystem path into either public stream. The long-lived REPL has its own
documented projections; notably, a profile session reports the path of its
successfully published analysis trace.
The shared command core implements every one-shot command through
PtcRunner.Kernel.CommandEngine, while ptc repl shares the same parser and
then opens its long-lived session frontend. Shared run dispatch authorizes destinations,
executes provider-free and provider-backed work through one execution owner,
publishes immutable execution evidence, and returns a schema-valid normal or
private envelope; a private envelope never contains the result value. The Mix
task is a thin renderer over this boundary. Its adapter owns Mix application
bootstrap and adds only interactive --authorize-mcp NAME for the immediately
following run. A nonzero presentation raises a rescuable Mix.Error carrying
the diagnostic status; in its normal mode, the outer mix executable maps that
status to its process exit, while the task and shared adapter never exit or halt
their caller. MIX_DEBUG=1 deliberately reraises Mix exceptions for debugging,
so the outer VM uses its generic exception status 1; the exception still
carries the diagnostic status. Bootstrap failures use the same closed
private-safe run envelope as other internal failures; raw startup reasons and
argv paths are not rendered.
models reads one bounded host document and
returns the installed aliases in lexical order with only their public source,
revision, data-class, accepted-class, and destination declarations. Listing
models invokes no provider callback, credential or OAuth service, optional
application, process, port, or network operation.
With --trace-dir DIR, the command-generated run_ref is also the complete
trace stem. A normal trace is exactly <run_ref>.jsonl; a private trace is
exactly <run_ref>.private.jsonl. The envelope's artifact_class selects which
suffix callers use, so they can locate the file without a published path.
Normal discovery excludes the private suffix; private-aware discovery accepts
and classifies it explicitly. If entropy is unavailable, the command preserves
availability with the fixed fallback reference
cmd-00000000000000000000000000; on that exceptional path the reference still
names the command's artifacts but cannot distinguish concurrent commands.
The envelope destination is named by the caller and is separate from --output
and --private-output, which name the run's result artifact. The command opens
that destination itself and commits the envelope through the same owner-only
staging and atomic no-replace boundary as other artifacts, so no other writer
can interleave with it and an existing entry is never replaced.
The switch is --envelope PATH, separate from --output and
--private-output.
The command writes a short human rendering of the sealed outcome whether or not
an envelope destination is named. Without --envelope it writes no envelope;
with --envelope it additionally publishes the envelope file. A failure
renders the primary phase, code, and catalog message on stderr. A successful
normal run renders its result value on stdout; a successful private run
renders only its completion and artifact class, because the private envelope
omits the value and a private result still requires an authorized owner-only
sink. That rendering is projected from the outcome, obeys the same privacy
rules as the envelope, and is presentation rather than a contract: do not
parse it.
The destination names a file and only a file; there is no stdout spelling. Standalone stdout is not a machine channel — it is shared with the runtime, its optional applications, and their children — so a caller that needs the envelope writes it to a file and reads that file.
The envelope destination is validated before any work begins, once the arguments
have parsed. It is resolved and compared against every artifact destination the
command could write — result, private result, inspection, both trace suffixes,
and the private-result recovery name derived from the run reference — under the
same resolved-parent-identity rule those artifacts use, so an alias such as
out.json against ./out.json or a symlinked parent is rejected up front
rather than at commit. For init the envelope may also be neither the target
directory nor a path beneath it. A collision is an argument failure: it exits
2 and produces no envelope, because the arguments were refused rather than
anything failing to publish.
run, validate, doctor, models, and init accept an envelope
destination. repl does not, and neither do help and version, which
complete without touching the filesystem.
Delivery begins at a successful parse. Once the arguments parse, every later
failure — including a recoverable startup failure — delivers its envelope to the
named destination. Rejected arguments produce no envelope: the command exits
2, the arguments phase's status, and writes one closed stderr line — naming an
unknown switch's accepted list where applicable, since a missing positional or
a malformed value names neither. A VM abort
produces no envelope either.
The command therefore requires no outer process wrapper, no private envelope
descriptor, and no interception of the runtime's own streams. The guarantee
comes from the destination, not from stream discipline: the core command modules
render no stream, but dispatch invokes runtime callbacks, the Mix authorization
notifier writes through Mix.shell, and starting an optional provider
application may emit output. Because the envelope is a file committed atomically
rather than a stream artifact, none of that can interleave with it. The release
entrypoint alone turns a sealed outcome status into a process exit.
Both process streams are consequently non-contractual. The command's own rendering and diagnostics are closed and carry no private value, credential, path, selector, or arbitrary term, but nothing constrains what the runtime, an optional application, or a child writes there — neither content nor secrecy. Capture those streams outside the command if a deployment needs a guarantee on them.
If the envelope destination cannot be opened, staged, or committed, the failure
cannot be reported through the envelope. The command writes a bounded, fixed,
code-owned diagnostic to stderr and exits 74. No valid envelope is promised on
that path.
SIGINT, SIGTERM, VM abort, OOM, and failure before the command boundary are
outside the V2 envelope contract. They may produce no envelope or VM/OS
emergency output, and the OS or shell determines their status. Distribution
tests characterize termination and child behavior but do not promote one observed
signal status to a portable guarantee. A deployment that requires a bounded
signal response, application bootstrap, or child-tree cleanup must use the
separately triggered outer supervisor design.
Build the runtime-included command from a source checkout with:
mix deps.get
MIX_ENV=prod mix release ptc_runner
_build/prod/rel/ptc_runner/bin/ptc --versionThe release includes ERTS, so the target machine does not need Erlang or
Elixir. The standalone runtime deliberately does not load .env; credentials
come only from bindings declared by the trusted host document. Signed downloads,
notarization, package-manager formulas, container images, and single-file
packaging remain separate distribution work.
A failed run reports a phase and a code from the closed diagnostic catalog and
nothing else. That is deliberate — the public envelope never carries arbitrary
text — but it means a workflow that fails on purpose reports
execution/workflow_failed, "the workflow failed", and no trace of whatever the
workflow was trying to say.
Your fail value decides how much survives in the Kernel API and canonical
trace. The command envelope remains the fixed execution/workflow_failed
diagnostic described above. PtcRunner.Kernel.run/2 and the canonical
run-stopped event instead pass the value through a failure taxonomy that
keeps a classification and discards everything else:
(fail …) value |
What Kernel.Error.details and run-stopped carry |
|---|---|
| a string, or any non-map | nothing |
a map with a kind naming a known failure kind |
that kind, readable |
a map with any other kind, or none |
a one-way fingerprint, not readable |
The known failure kinds are invalid-input, invalid-prompt,
invalid-transcript, transcript-limit, turn-limit, model-program-failed,
non-retryable-evaluation, evaluation-unavailable, capability-unavailable,
llm-provider-error,
protocol-error, provider-error, capability-error, assertion-failed, and
unknown-action.
So (fail "the invoice total did not balance") publishes nothing, while
(fail {:kind "assertion-failed" :detail "the invoice total did not balance"})
publishes only failure_kind: "assertion-failed". Prefer the second shape. It
costs one key and it is the difference between a caller knowing what class of
thing went wrong and knowing only that something did. The same projection
survives when pmap or pcalls rejects a worker's fail control signal; the
parallel error remains the terminal reason while its safe failure taxonomy
identifies the underlying framework failure.
Input and result contract failures follow the same public/private split. Their public classification may name schema-declared missing keys at a safe typed path. At a closed-object path it may also name allowed keys and count undeclared keys; an open-object path never classifies extension keys as undeclared. It never names an undeclared submitted key or copies a submitted value. Use private inspection when the exact rejected candidate is authorized and necessary; normal command diagnostics and agent correction feedback remain schema-derived and bounded.
When provider-backed detail is needed, rerun with --inspect:
mix ptc run ptc.json --trace-dir traces --inspect traces/run.inspection.jsonlEvery inspection artifact holds the exact frozen component source in
prelude-source records. If the workflow calls println, its output lands in
an execution-prints record, whether the run succeeds or fails. Provider-backed
activity may add prompts, normalized model responses, generated subordinate
source, capability arguments and results, and MCP request and response bodies.
A failing run also adds an execution-error record with the detailed failure
fields; a provider-free success with no println calls adds no run-time record
beyond prelude-source. The artifact is owner-only and sensitive: read it, do
not publish it beside a normal trace, and see Use private inspection
deliberately.
To exercise the component compiler in a REPL, load the manifest, not the component as a setup file:
mix ptc repl -m ptc.jsonThe -l/--load option dynamically evaluates a setup file and cannot load the
component-only ns and defn- forms. Manifest setup distinguishes malformed
syntax (syntax_invalid), undefined variables (undefined_variable), and
duplicate definitions (duplicate_definition). Other compiler rejections keep
the compile_failed fallback. Undefined-variable and duplicate-definition
messages name only bounded symbols found verbatim in the submitted component;
they are rebuilt from structured compiler detail rather than forwarded compiler
text. Malformed syntax carries the parser's byte position when one is provable,
including a zero-length span at end-of-file for an unclosed form. Other details
retain the fixed catalog message and a null span.
To find the artifacts, use the run reference. It appears in the command
envelope, and every artifact is named from it: <run_ref>.jsonl for a normal
trace and <run_ref>.private.jsonl for a private one.
Start a direct session or reuse a manifest's frozen workflow environment:
mix ptc repl
mix ptc repl -e '(def x 40)' -e '(+ x 2)' -e '(+ *1 1)'
mix ptc repl --manifest ptc.json
mix ptc repl --manifest ptc.json --host-config ptc-host.json
mix ptc repl --manifest ptc.json --trace traces/repl.jsonlDefinitions and the three most recent ordinary successful values persist for
one session. Failed forms keep the previously committed state. A manifest that
selects providers requires --host-config; it acquires those providers once
and reuses the session across expressions. Provider-free manifests omit the
host configuration and still use the same caller-death and sink owner. Private
manifest sessions are interactive-only and require both --private-terminal
and attached stdin/stdout terminals before provider activity can begin. See the
Kernel REPL guide for scripts, stdin, JSONL output, resource
limits, and lifecycle details.
Normal traces contain bounded operational events and omit prompts, model responses, capability arguments/results, and generated source. Query an immutable directory capture through the fixed run-analysis profile:
mix ptc repl \
--profile run-analysis-v1 \
--resource traces=traces \
-e '(analysis/runs {})' \
-e '(analysis/overview "run-id")'Useful functions are:
(analysis/runs {})
(analysis/overview "run-id")
(analysis/activity "run-id" {"limit" 100})
(analysis/failure "run-id" {"limit" 100})The captured files, session code, results, and analysis trace have independent byte ceilings. The profile has no access to private inspection data or to the active run that produced the traces.
Events pair evaluations by evaluation_id and capabilities by capability_id,
and carry environment, status, duration, labels, prelude component IDs and
hashes, limit events, and aggregate usage. The
TraceLog contract defines the event schemas,
sanitization, filtering, pagination, and private sources. See the
Kernel REPL guide for longer interactive investigations.
Pass both output paths when exact development diagnostics are required:
mkdir -p tmp/inspection
mix ptc run examples/kernel-tutorial/03-file-agent/ptc.json \
--host-config examples/kernel-tutorial/ptc-host.json \
--trace-dir tmp/inspection \
--inspect tmp/inspection/run.inspection.jsonlThe inspection path is selected by the host command, not by the manifest or PTC-Lisp. Capture is fail closed and the file uses owner-only permissions. Canonical trace queries never join its payloads into ordinary results.
The credential-free Kernel inspection lab creates correlated canonical and inspection artifacts without a live model.
Canonical traces answer what happened without retaining private content. The inspection artifact answers what the model saw and wrote. It is created with owner-only permissions and contains the full request, response, generated source, and capability payloads. Use the semantic Viewer below, the private run-analysis profile, or the one-shot transcript command:
ptc transcript RUN_ID \
--traces tmp/traces \
--inspection tmp/inspection \
--private-unattended \
--private-output tmp/transcript.private.jsonThe trace, inspection, and private-output directories must satisfy the private
analysis separation and owner checks described in the Kernel REPL guide.
Failures are machine-distinguishable on stderr as
error: transcript/CODE: ...; codes distinguish unavailable or changed
sources, unsupported schemas, ambiguous or incomplete evidence, result limits,
destination failures, and internal failures. No partial output file is
published.
In one verified 03-file-agent run, the request contained four relevant parts:
| Feed part | What the model received |
|---|---|
| System instructions | Call run_ptc_lisp exactly once per turn; ordinary results continue; successful definitions persist; failed definitions roll back; return completes; fail aborts; do not answer in prose. |
| Language summary | PTC-Lisp described as a bounded Clojure-like language, followed by its common forms, namespaces, JSON-map convention, exclusions, and short examples. |
| Available mission API | Only (tutorial.files/read-text path), documented as a read effect from a string path to a string result. |
| User message and model tool | Read brief.txt through that wrapper and return its exact contents; one run_ptc_lisp tool accepting one required program string. |
The request did not contain the file contents, the provider credential,
unrestricted filesystem access, or the workflow's raw llm-request capability.
The model had to express the action through the one advertised mission
function. The provider reported 895 input tokens for that small feed; token
counts change with prompt or provider updates.
The model generated exactly this 47-byte program:
(return (tutorial.files/read-text "brief.txt"))It uses the prompt-visible wrapper rather than guessing a raw capability API,
passes the requested relative path for the host-confined provider to validate
beneath the granted root, and makes the first mission evaluation terminal with
return. It creates no definitions and no ordinary intermediate value, which
matches the public defined_count: 0 and history_count: 0.
More complex agents use the same boundary across several turns, with inspection recording each private request, response, and generated program while the canonical trace retains only bounded operational evidence.
From the repository root, after creating the trace directory:
mix ptc.viewer --trace-dir tmp/inspection \
--inspection-file tmp/inspection/run.inspection.jsonlThe Viewer lists runs and renders paired workflow and mission evaluations,
capability calls, annotations, limits, usage metrics, and raw canonical event
metadata. Use --port to choose a port instead of the default 4123, and
--no-open when running on a remote machine.
The Viewer binds to loopback, pins the selected inspection file, and can enable a bounded run-analysis REPL over an immutable trace capture. It is currently a development/test path dependency and is not included in the published Hex package. Treat this command as source-checkout tooling until standalone Viewer packaging is released. If the selected inspection artifact uses an unsupported schema, Viewer startup reports both the artifact version and the version supported by the running PtcRunner build.
See ptc_viewer/README.md for the Viewer's HTTP
API, configuration, and programmatic start/stop.
Keep a deterministic fixture in normal tests. Compare the stable business value and semantic error classification rather than timestamps, generated run IDs, or remaining milliseconds.
Build the checks in three layers:
- Exercise pure transformations in
mix ptc repl. - Run fixed manifest inputs with
mix ptc run --envelopeand assert.result.valuewithjq. - Put live-provider checks behind an explicit flag and assert a narrow contract, not prose wording.
Layer 2 needs no test framework:
envelope_dir="$(mktemp -d)"
envelope="$envelope_dir/command-envelope.json"
mix ptc run examples/kernel-tutorial/01-orders/ptc.json --envelope "$envelope"
actual="$(jq -c '.result.value' "$envelope")"
test "$actual" = \
'{"order_count":3,"paid_count":2,"paid_total":335.75,"pending_ids":["A-101"]}'For model workflows, test the protocol parser and feedback loop with scripted responses first, then keep the live provider boundary in a small separate credentialed test:
mix test test/ptc_runner/kernel/tutorial_examples_e2e_test.exs --include e2eThat module is tagged :e2e, so ordinary runs exclude its live provider calls.
Do not make correctness depend on an unconstrained natural-language completion
unless the prompt restricts it to one token or one schema.
- Kernel REPL — complete session modes, JSON Lines output, and private inspection analysis.
- Manifests and capabilities — the event policy, contracts, and limits these commands enforce.
- Host configuration — the providers, credentials, and
installed ceilings behind
--host-config. - Building agents — the agent policy that produces the runs you are debugging.