This is the complete ptc command and process contract.
Every installation
exposes the same command grammar and runtime path.
Run ptc help COMMAND for the exact switches accepted by an installed version.
Choose a command
| Command | Purpose |
|---|---|
ptc init DIRECTORY | Publish a validated minimal application without replacing an existing target |
ptc init DIRECTORY --example NAME | Publish one embedded example tree instead of the scaffold |
ptc docs [PAGE] | List the documentation embedded in this executable, or print one page |
ptc help [COMMAND] | Print the root command list, or the exact switches one command accepts |
ptc validate MANIFEST or PROJECT | Load and compile without executing the workflow, and read the input files the declarations name |
ptc run MANIFEST or PROJECT | Execute the application entry |
ptc run MANIFEST --env-file FILE | Load environment-backed credentials from this exact file |
ptc doctor [MANIFEST or PROJECT] | Report application and provider readiness |
ptc models PROJECT.json or --host-config HOST.json | List public installed model-alias declarations, each with the safe selector it configured |
ptc transcript RUN_ID ... | Publish one correlated private model transcript |
ptc repl | Open a direct, manifest-backed, or analysis session |
ptc viewer PROJECT.json | Browse a project's captured traces in a local web UI |
ptc viewer PROJECT.json --env-file FILE | Use one exact dotenv file for Viewer-started workflows and missions |
Help is generated from the same declarations as the strict parser, so use
ptc help COMMAND as the canonical command and option reference.
A provider-bearing manifest needs a host configuration. A project document can remember that path and its environment file. Before running it, active provider checks can make real requests and may incur cost:
ptc doctor ptc-project.json --connect
Plain doctor reports readiness: "unverified" when its local checks pass.
Missing provider commands, unreadable replay fixtures, and other attributable
local failures produce failed check rows, readiness: "failed", and a nonzero
exit without activating a provider. Successful active checks report ready;
an attributable active failure also reports failed and exits nonzero. A
manifest or package rejected during application validation is likewise
reported as a failed application check instead of an internal command error.
Complete readiness reports, including readiness: "failed", are written to
stdout. Failed reports retain their nonzero exit status; failures that cannot
produce a complete report are written to stderr. Runtime logger output,
including TLS handshake alerts, is written to stderr so stdout remains one
JSON document.
--show-model-selectors adds only safe selectors.
Every readiness report carries usage, on the LLM rows a run reports. Each
probed model alias contributes one call with the tokens and cost the provider
attributed to it, so a CI step can account for what the check spent:
{"usage": {"llm_usage_state": "available",
"llm_usage": [{"alias": "llm", "installation_revision": "v1",
"calls": 1, "successful_calls": 1, "usage_calls": 1,
"missing_usage_calls": 0,
"usage": {"input": 8, "output": 1, "total_cost": 3.0e-6}}]}}The probe asks for one output token, so this is about attribution rather than
magnitude. A command that activated no provider reports an empty list, because
it spent nothing. A failure that did activate one reports
"llm_usage_state": "unavailable" and a null list rather than claiming zero:
the request may have been billed with no result left to account for it. As in a
run, total_cost is omitted when any call could not be priced, and
missing_usage_calls counts calls whose usage may exist but was not observed,
including an in-flight request stopped by the run clock. It is not limited to
successful completions.
Read the embedded documentation
Every installation carries the language specification, references, and JSON
Schemas that describe its own version. ptc docs lists them; ptc docs PAGE
prints one page verbatim to stdout:
ptc docs
ptc docs agent-guide
ptc docs schema-manifest
Pages are embedded when the executable is built, so they need no network
access and cannot describe a different version. An unrecognized page name is
rejected as invalid arguments. docs publishes no envelope and reads no
application, host configuration, or project document.
Coding agents and LLMs driving the executable should start at
Drive ptc as an agent, served as
ptc docs agent-guide.
Run a manifest
For normal local use, keep stable paths in a project document:
ptc run ptc-project.json
ptc viewer ptc-project.json
The project form creates its fixed owner-only artifact layout as needed. See Project configuration. Direct manifest invocation remains the explicit low-level form below.
To inspect one mission with the same project paths but without starting the workflow, name it at invocation time:
ptc repl --project ptc-project.json --mission review
The project document remains the only path/configuration file normally supplied
to a command; mission declarations continue to live only in ptc.json.
The trace directory must already exist:
mkdir -p traces
ptc run ptc.json
ptc run ptc.json --trace-dir traces
ptc run ptc.json \
--env-file .env \
--host-config ptc-host.json \
--trace-dir traces \
--inspect traces/run.inspection.jsonl \
--envelope results/command.json
Useful run switches are:
--input INPUT.jsonreplaces the manifest input with another normal object. Resolve the path as an application-relative document first; otherwise treat it as absolute or relative to the process working directory (same rule as--component-override-descriptorhost paths).--private-input INPUT.jsondoes the same and classifies the run as private.--output VALUE.jsonpublishes a normal result value without replacing an existing file.--private-output VALUE.jsonpublishes a private result at owner-only mode and keeps it off stdout.--trace-dir DIRwrites<run_ref>.jsonlor<run_ref>.private.jsonlaccording to the run's artifact class.--inspect FILEwrites sensitive execution evidence to an owner-only.inspection.jsonlfile.--envelope FILEatomically publishes a convenience copy of the stable V3 command envelope. When a project enablesartifacts.envelope, the project's.ptc/envelopes/<run_ref>.jsonledger entry is still written for that run.run,validate,doctor,models, andinitall accept the flag; the document it publishes carries status, run reference, result or classified error, and artifact state. Parse it rather than scraping stdout, which is a human presentation channel that may also carry application output.
The command envelope reports the run reference and artifact class, not artifact paths. Output, trace, inspection, and envelope destinations must be distinct. All publications are no-replace and recheck their destination at commit time.
Atomic publication may reserve owner-only sibling paths named
.ptc-private-* or .ptc-private-result-*. They normally disappear at commit
or cleanup, but an abruptly terminated process can leave one behind. Because a
completed reservation can contain private prompts, responses, source, or a
result, ignore both patterns as well as the configured artifact root. New
projects created by ptc init include all three patterns in .gitignore.
ptc init requires a DIRECTORY that does not already exist. It assembles the
complete scaffold or selected example tree and publishes it atomically without
replacing anything. To add PtcRunner to an existing repository, initialize a
new sibling or subdirectory, then deliberately copy or move the generated
files the repository wants.
ptc init DIRECTORY --example NAME publishes one of the walkthrough projects
this executable embeds instead of the scaffold, under the same no-replace
commit. Run ptc init with an unknown example name to have the embedded
names listed. The trees are byte-identical to the repository's, so the commands the
guides print work from wherever the copy was created rather than from one
checkout directory.
For runs that produce a validated terminal event batch, execution.usage
includes the required aggregate llm_spend projection, llm_usage grouped by
alias and installation revision,
llm_usage_by_model grouped by an attested public resolved model, and
unattributed_model_calls. llm_spend is byte-equivalent to the value in the
canonical run-stopped usage: empty and incomplete contain only state,
unpriced also contains non-negative input and output, and available
adds a non-negative total_cost. Only available can report a measured zero
cost; the other states never substitute zero for absent pricing. Rows report call counts, usage-presence counts, and
summed token and total_cost values. Terminal accounting pairs each
llm-request capability-started with its capability-stopped by
capability_id. An unmatched start is one observed call with unknown usage:
calls increments, successful_calls does not, and missing_usage_calls
increments. A row includes total_cost only when every call that could carry
usage has valid priced usage; an unmatched or unpriced call leaves the
aggregate cost unknown and omitted, not reported as zero, while measured
input/output totals are retained. llm_usage_state: "available" means the
terminal batch could be reconstructed, not that every call supplied usage.
llm_usage_state: "unavailable" pairs all three
aggregate fields with null when terminal evidence cannot be validated,
including dropped capability-started or capability-stopped events, while
preserving other known usage. Non-empty events_dropped for other event types
means an available detailed summary covers retained evidence and may not be
complete. llm_usage_state describes reconstruction of those detailed rows;
it does not replace the independently sealed four-state llm_spend value.
Artifact publication currently requires a Unix host with POSIX-compatible
mkdir and id; trace append also needs sh and either lockf or flock.
Private artifacts and newly created traces require trusted ancestry, safe
ownership, and restrictive modes. Preflight refuses unsafe or unwritable
destinations before provider acquisition, while descriptor-based publication
checks close later filesystem races.
--inspect is an explicit host development authority. It may contain prompts,
model responses, generated source, capability arguments and results, MCP
payloads, prints, and detailed failures. Do not publish it with normal traces.
Evaluate replacement component source
Use --component-override-descriptor to evaluate one already-selected
component without installing it. A trusted build step creates the owner-only
candidate and descriptor from model-authored source. The
replay evaluation guide owns the workflow
for holding model responses fixed and comparing a baseline with that candidate.
The component reference
defines every descriptor field. Candidate creation is not currently a
standalone command; a source checkout provides mix ptc.materialize as
documented in the repository's maintainer guide on embedding.
Read results and failures
A successful normal run prints the compact JSON result value. A private run does not print its value. The V3 envelope records the result class, artifact states, bounded usage, retained-memory counts, and the closed diagnostic when one exists.
Capability failures normally enter PTC-Lisp as recoverable envelopes so the workflow can correct, retry, degrade, or fail. Parser, compiler, timeout, heap, source, result, quota, provider, and event failures retain bounded Kernel classifications.
One-shot public diagnostics come from a closed catalog. They never render an
arbitrary exception, rejected value, provider response, credential, or private
payload. A provider subject appears as provider/<alias>/<operation> with its
workflow or mission occurrence when known.
Host, project, and application schema diagnostics distinguish a closed set of
violated rules and carry only schema-authorized JSON Pointers. A missing
required field may name that schema-declared field; an unknown caller-authored
key is omitted and the pointer stops at its declared parent. Caller-selected
map members such as installation aliases and mission names render as *.
validate also reads the files a declaration owns rather than the environment
it will run in. A replay installation names a fixture file, so validate parses
it under the installed ceilings and reports the rule a rejected file broke —
with the line number for a line-level rejection. It still acquires nothing: an
installed model's adapter and an MCP server's executable are environment
dependencies and belong to doctor.
On success, the validate result includes mission_grants: for each named
mission, the sorted parseable data/<name> grants, every public export ref,
and selected mission provider names. This is the static grant declaration;
validate does not acquire providers, so capability tool names remain unresolved.
kernel/mission-inventory lists model-visible capabilities once a run or REPL
session has built the frozen inventory.
Environment files fail before provider acquisition with a cause-specific code:
environment_file_not_found, environment_file_not_regular,
environment_file_unreadable, environment_file_too_large, or
environment_file_invalid_utf8. The code identifies whether to create the
named --env-file/project file, change its permissions, or repair its bytes;
the public envelope still does not publish a host filesystem path.
When an agent turns a provider failure into workflow failure, the command
retains one bounded class when the adapter can prove it:
llm_authentication_failed, llm_payment_required, llm_rate_limited,
llm_model_not_found, llm_tool_calling_unsupported, llm_request_invalid,
llm_access_denied,
llm_timeout, llm_provider_unavailable, or the non-retryable fallback
llm_provider_failed. No response body is retained.
The failing model alias remains attributable through usage/provider evidence;
run ptc doctor PROJECT --connect for a minimal provider check and use private
inspection only when authorized detail is necessary.
Component compile failures with a provable location print the logical component
name and the envelope's half-open byte range, for example at main.clj bytes [45,58). The same canonical offsets remain available in error.span when
--envelope is requested. An unknown namespace is a separate closed diagnostic:
the compiler carries the rejected namespace and canonical sorted namespace list
as structured detail, and the command boundary rebuilds the public list and JSON
hint after validating that detail. It never forwards the compiler-rendered
string. For a shipped namespace such as kernel/, select its library and add
the component dependency as described in
Select a shipped prelude.
Use the standalone process contract
For machine integration, name an envelope file instead of parsing stdout:
ptc run ptc.json --envelope command-envelope.json
The standalone streams are human presentation channels and may also contain
output from applications or children. The envelope is an atomic, no-replace
file whose JSON Schema this executable serves as ptc docs schema-envelope
(priv/schemas/ptc-command-envelope-v3.schema.json in the repository). Its
status and exit-code relationship is sealed by the same command contract.
After arguments parse, an ordinary or caught command outcome publishes one
requested envelope. This includes a recognized run, validate, doctor, or
models invocation whose named project fails schema validation: project
diagnostics terminate before command bootstrap or project references are
opened, but after the envelope destination is admitted. Malformed command
syntax, conflicting arguments, invalid envelope destinations, and VM/OS
termination can produce no envelope.
Publication is no-replace, so a destination that already exists is refused
during argument admission with arguments/envelope_destination_exists and exit
2, before any provider work: a repeated CI step is told to remove the file
rather than paying for a run whose result it cannot receive. If envelope
publication itself fails, the standalone command exits 74 and cannot report
that failure through the missing envelope. Success exits 0; classified
failures use their diagnostic catalog status; caught internal failures use
70.
run, validate, doctor, models, and init accept --envelope.
repl, transcript, viewer, docs, help, and version do not. A private run
envelope omits the result value. Installation, packaging, and container
commands live in the installation documentation,
not in this process-contract reference.
Branch on the exit status
An exit status is a class, not an identity: several diagnostics share one.
runtime_limit_exceeded, run_timeout, turn_limit_exceeded,
capability_quota_exceeded, and model_output_truncated all exit 6. Branch
on the status to decide whether to retry, and read error.code from the
envelope when the branch needs to know which failure it was.
A recoverable capability error does not change the exit status. Exhausting
workflow_capability_calls_per_name returns
{"status":"error","kind":"limit_exceeded","reason":"capability_quota","details":{"limit":"workflow_capability_calls_per_name","name":"llm-request","limit_value":2}} as a
value into PTC-Lisp; a workflow that reads past it can still return and the
command exits 0. execution.usage.capability_refusals counts those errors
from environment capability callbacks and the implicit runtime routes the
Kernel grants with them (workflow/limit_exceeded/capability_quota).
Runner-added routes such as kernel-eval are not counted. At most 2 distinct
classes are named; further classes increment $overflow. Assert
capability_refusals is {} when a CI job requires that every counted
capability call succeeded, or have the workflow fail, when a quota must end
the run.
The tables below are generated from the diagnostic catalog the command dispatches on, so they list every status a command can exit with and every diagnostic behind it.
| Status | Meaning | Phases |
|---|---|---|
| 0 | the command succeeded | — |
| 2 | the arguments were rejected before any document was read | arguments |
| 3 | a declaration document was unavailable, invalid, or rejected | project, host, application, bundle, provider_declaration |
| 4 | a selected provider could not be checked or acquired | local_preflight, active_preflight, provider_acquisition |
| 5 | the workflow ran and failed | execution |
| 6 | the run exceeded a limit or its duration | execution |
| 7 | the run produced no usable artifact: a destination, result, or publication failure | destination, execution, result_cleanup, publication |
| 70 | the command failed internally | internal |
| 74 | the requested envelope could not be published, so no envelope describes this failure | — |
Every classified diagnostic and the status it exits with:
| Status | Phase | Code | Retryable | Message |
|---|---|---|---|---|
| 2 | arguments | conflicting_arguments | no | choose only one option from the conflicting argument group |
| 2 | arguments | docs_page_unknown | no | no documentation page is served under that name |
| 2 | arguments | envelope_destination_exists | no | the envelope destination already exists |
| 2 | arguments | example_unknown | no | no example is embedded under that name |
| 2 | arguments | invalid_arguments | no | use the documented arguments for this command |
| 2 | arguments | invalid_command | no | use one of the supported commands |
| 2 | arguments | project_host_undeclared | no | the project document declares no host block; add one to use this command |
| 3 | application | application_not_found | no | the application manifest does not exist |
| 3 | application | application_unavailable | no | the application is unavailable |
| 3 | application | contract_invalid | no | an application value contract is invalid |
| 3 | application | document_limit_exceeded | no | the application document closure exceeds its limit |
| 3 | application | duplicate_property | no | an application document contains a duplicate property |
| 3 | application | event_identity_conflict | no | the command event identity conflicts with the application |
| 3 | application | input_contract_failed | no | the selected input does not satisfy the input contract |
| 3 | application | input_invalid | no | the selected input is not an admissible JSON object |
| 3 | application | installed_limit_exceeded | no | an application limit exceeds the installed ceiling; lower it or raise the host-configured ceiling |
| 3 | application | invalid_json | no | an application document is not valid JSON |
| 3 | application | override_invalid | no | the component override is invalid |
| 3 | application | reference_missing | no | a referenced document is unavailable; for --input/--private-input try an application-relative name or an absolute/working-directory path |
| 3 | application | required_property_missing | no | the application manifest is missing a required property |
| 3 | application | schema_validation_unavailable | yes | application schema validation timed out or exceeded its resource bound; retry the command |
| 3 | application | schema_violation | no | the application manifest does not satisfy its schema |
| 3 | bundle | bundle_invalid | no | the component bundle is invalid |
| 3 | bundle | bundle_limit_exceeded | no | the component bundle exceeds a compile limit |
| 3 | bundle | compile_failed | no | the component bundle could not be compiled |
| 3 | bundle | duplicate_definition | no | the component bundle defines the same name more than once |
| 3 | bundle | entry_invalid | no | the workflow entry is not a public bundle export |
| 3 | bundle | mission_undeclared | no | the workflow entry evaluates into a mission and the manifest declares none |
| 3 | bundle | syntax_invalid | no | the component source is not valid PTC-Lisp |
| 3 | bundle | undefined_variable | no | the component source contains an undefined variable reference |
| 3 | bundle | unknown_namespace | no | the component source references an unavailable namespace |
| 3 | host | host_invalid | no | the host configuration is invalid |
| 3 | host | host_schema_invalid | no | the host configuration does not satisfy its schema |
| 3 | host | host_unavailable | no | the host configuration is unavailable |
| 3 | host | installation_endpoint_credentials_require_https | no | configured MCP credentials require an https endpoint |
| 3 | host | installation_endpoint_insecure_loopback_forbidden | no | allow_insecure_loopback is not permitted on an https endpoint; remove it |
| 3 | host | installation_endpoint_insecure_loopback_required | no | a plain-http MCP endpoint requires allow_insecure_loopback |
| 3 | host | installation_endpoint_invalid | no | an installed MCP endpoint is not admissible; streamable_http requires an https URL, or allow_insecure_loopback with a credential-free plain-http loopback address |
| 3 | host | installation_endpoint_literal_loopback_required | no | allow_insecure_loopback requires a literal 127.0.0.1 or [::1] address |
| 3 | host | installation_revision_missing | no | an installed provider is missing its behavior revision |
| 3 | host | installed_limit_invalid | no | an installed limit is invalid |
| 3 | host | schema_validation_unavailable | yes | host schema validation timed out or exceeded its resource bound; retry the command |
| 3 | project | project_schema_invalid | no | the project configuration does not satisfy its schema |
| 3 | project | schema_validation_unavailable | yes | project schema validation timed out or exceeded its resource bound; retry the command |
| 3 | provider_declaration | data_policy_denied | no | the selected providers do not admit the effective data class |
| 3 | provider_declaration | dependency_invalid | no | the selected provider dependency graph is invalid |
| 3 | provider_declaration | placement_denied | no | the provider is not allowed in this destination |
| 3 | provider_declaration | provider_unknown | no | the selected provider is not installed |
| 3 | provider_declaration | selection_invalid | no | the provider selection is invalid |
| 3 | provider_declaration | selection_unverifiable | no | the provider selection cannot be verified declaratively |
| 4 | active_preflight | authentication_rejected | no | provider authentication was rejected |
| 4 | active_preflight | authorization_rejected | no | explicit provider authorization was rejected |
| 4 | active_preflight | authorization_required | no | explicit provider authorization is required |
| 4 | active_preflight | authorization_unavailable | yes | the authorization service is temporarily unavailable |
| 4 | active_preflight | connectivity_outcome_unknown | no | the connectivity outcome could not be committed safely |
| 4 | active_preflight | connectivity_protocol_error | no | the provider returned an invalid connectivity response |
| 4 | active_preflight | connectivity_rate_limited | yes | the provider connectivity operation is rate limited |
| 4 | active_preflight | connectivity_rejected | no | the provider rejected the connectivity operation |
| 4 | active_preflight | connectivity_timeout | no | the connectivity operation exceeded its budget |
| 4 | active_preflight | connectivity_unavailable | yes | the provider connectivity operation is temporarily unavailable |
| 4 | active_preflight | connectivity_unsupported | no | the provider does not implement the declared connectivity check |
| 4 | active_preflight | credential_unavailable | no | a required provider credential is unavailable |
| 4 | active_preflight | provider_application_unavailable | no | a required provider application is unavailable |
| 4 | active_preflight | selection_rejected | no | the provider rejected the normalized selection |
| 4 | active_preflight | selection_validation_failed | no | active provider selection validation failed |
| 4 | active_preflight | selection_validation_timeout | no | active provider selection validation timed out |
| 4 | local_preflight | adapter_unavailable | no | a required provider adapter is unavailable |
| 4 | local_preflight | authorization_not_applicable | no | --authorize-mcp applies only to an installation that declares OAuth |
| 4 | local_preflight | authorization_target_unknown | no | --authorize-mcp must name an installed provider the application selects |
| 4 | local_preflight | command_not_found | no | a required provider command could not be found |
| 4 | local_preflight | environment_file_invalid_utf8 | no | the named environment file is not valid UTF-8 |
| 4 | local_preflight | environment_file_not_found | no | the named environment file does not exist |
| 4 | local_preflight | environment_file_not_regular | no | the named environment file is not a regular file |
| 4 | local_preflight | environment_file_too_large | no | the named environment file exceeds the 1 MB limit |
| 4 | local_preflight | environment_file_unreadable | no | the named environment file cannot be read safely |
| 4 | local_preflight | environment_unavailable | no | a required local environment is unavailable |
| 4 | local_preflight | executable_unavailable | no | a required provider executable is unusable |
| 4 | local_preflight | fixtures_unreadable | no | provider fixtures could not be read |
| 4 | local_preflight | launcher_unavailable | no | a required provider launcher is unavailable |
| 4 | local_preflight | local_check_timeout | no | a local provider check timed out |
| 4 | provider_acquisition | capability_requirement_missing | no | a component requires a capability that the selected providers did not supply |
| 4 | provider_acquisition | provider_acquisition_timeout | yes | the selected provider exceeded its acquisition timeout budget |
| 4 | provider_acquisition | provider_endpoint_connection_refused | yes | the installed endpoint refused the connection |
| 4 | provider_acquisition | provider_endpoint_name_unresolved | no | the installed endpoint hostname could not be resolved |
| 4 | provider_acquisition | provider_endpoint_tls_failed | no | the installed endpoint did not complete a TLS handshake |
| 4 | provider_acquisition | provider_policy_changed | no | the selected provider policy changed during acquisition |
| 4 | provider_acquisition | provider_protocol_error | no | the selected provider returned an invalid acquisition response |
| 4 | provider_acquisition | provider_protocol_version_unsupported | no | the endpoint did not advertise support for MCP protocol 2026-07-28 |
| 4 | provider_acquisition | provider_tool_missing | no | the installed endpoint does not expose a declared tool |
| 4 | provider_acquisition | provider_unavailable | no | the selected provider could not be acquired |
| 5 | execution | evaluation_failed | no | the evaluation failed |
| 5 | execution | invalid_agent_config | no | an agent configuration option is outside its supported range |
| 5 | execution | llm_access_denied | no | the LLM provider denied access to the configured model |
| 5 | execution | llm_authentication_failed | no | the LLM provider rejected authentication; check the installed credential |
| 5 | execution | llm_model_not_found | no | the LLM provider could not find the configured model |
| 5 | execution | llm_payment_required | no | the LLM provider rejected the request for billing or credit reasons |
| 5 | execution | llm_provider_failed | no | the LLM provider request failed |
| 5 | execution | llm_provider_unavailable | yes | the LLM provider is unavailable |
| 5 | execution | llm_rate_limited | yes | the LLM provider rate limited the request |
| 5 | execution | llm_request_invalid | no | the LLM provider rejected the configured request |
| 5 | execution | llm_timeout | yes | the LLM provider request timed out |
| 5 | execution | llm_tool_calling_unsupported | no | the configured model does not support tool calling |
| 5 | execution | mission_failed | no | a subordinate mission failed |
| 5 | execution | provider_failed | no | a provider failed during execution |
| 5 | execution | replay_fixture_missing | no | no replay fixture matches the workflow request |
| 5 | execution | workflow_failed | no | the workflow failed |
| 6 | execution | capability_quota_exceeded | no | a capability quota was exceeded |
| 6 | execution | model_output_truncated | no | model output was truncated before producing a usable agent action |
| 6 | execution | run_timeout | no | the run duration limit was exceeded |
| 6 | execution | runtime_limit_exceeded | no | a runtime limit was exceeded |
| 6 | execution | turn_limit_exceeded | no | the agent turn limit was exceeded |
| 7 | destination | destination_exists | no | an artifact destination already exists |
| 7 | destination | inspection_destination_unavailable | no | the inspection destination is unavailable |
| 7 | destination | inspection_destination_unsafe | no | the inspection destination is unsafe |
| 7 | destination | inspection_directory_missing | no | --inspect must name a file in an existing directory |
| 7 | destination | invalid_destination | no | an artifact destination is invalid |
| 7 | destination | invalid_inspection_destination | no | --inspect must name a valid destination ending in .inspection.jsonl |
| 7 | destination | invalid_result_destination | no | the result destination is invalid |
| 7 | destination | invalid_trace_destination | no | the trace destination is invalid |
| 7 | destination | private_destination_required | no | the run requires an authorized private destination |
| 7 | destination | recovery_reservation_failed | no | the private result recovery reservation failed |
| 7 | destination | result_destination_unavailable | no | the result destination is unavailable |
| 7 | destination | result_destination_unsafe | no | the result destination is unsafe |
| 7 | destination | result_directory_missing | no | --output and --private-output must name a file in an existing directory |
| 7 | destination | trace_destination_unavailable | no | the trace destination is unavailable |
| 7 | destination | trace_destination_unsafe | no | the trace destination is unsafe |
| 7 | destination | trace_directory_missing | no | --trace-dir must be an existing normal directory |
| 7 | execution | event_capture_limit_exceeded | no | the trace event capture limit was exceeded |
| 7 | execution | event_sink_unavailable | no | the trace event sink is unavailable |
| 7 | execution | inspection_capture_limit_exceeded | no | the private inspection capture limit was exceeded |
| 7 | execution | inspection_sink_unavailable | no | the private inspection sink is unavailable |
| 7 | publication | destination_collision | no | an artifact destination appeared before publication |
| 7 | publication | initialization_failed | no | project initialization failed |
| 7 | publication | initialization_parent_missing | no | the initialization target's parent directory does not exist |
| 7 | publication | initialization_parent_unusable | no | the initialization target's parent directory is unusable |
| 7 | publication | initialization_target_exists | no | ptc init publishes only to a new directory; choose a target that does not already exist |
| 7 | publication | inspection_publication_failed | no | inspection publication failed |
| 7 | publication | recovery_cleanup_failed | no | private result recovery cleanup failed |
| 7 | publication | result_publication_failed | no | result publication failed |
| 7 | publication | trace_publication_failed | no | trace publication failed |
| 7 | result_cleanup | provider_cleanup_failed | no | provider cleanup failed |
| 7 | result_cleanup | provider_cleanup_timeout | no | provider cleanup timed out |
| 7 | result_cleanup | result_contract_failed | no | the workflow result does not satisfy its contract |
| 7 | result_cleanup | result_invalid | no | the workflow result is invalid |
| 7 | result_cleanup | result_limit_exceeded | no | the workflow result exceeds its limit |
| 70 | internal | internal_error | no | the command failed internally |
Diagnose a failed run
The command reports a closed phase/code pair. If a workflow deliberately calls
fail, its value is not copied into the command diagnostic. The Kernel API and
canonical run-stopped event retain only a safe taxonomy:
- a map whose
kindis recognized retains that readable kind; - an authenticated
agent.coreturn-limit failure additionally retains the fixedagent_turnsname, the effective integer ceiling from 1 through 128, and the closed reason the loop stopped (turn_limit_exceeded,intermediate_result,evaluation_error, orprotocol_error); - an unknown map kind retains only a one-way fingerprint;
- a string or other non-map retains no detail.
Prefer a framework classification such as:
(fail {:kind "assertion-failed" :detail "private explanation"})The public evidence retains assertion-failed, not the private explanation.
The Runner adds the fixed agent_turns fields only after the shipped agent's
private runtime route has authenticated the exhaustion failure.
For exact authorized detail, capture inspection evidence:
ptc run ptc.json \
--trace-dir traces \
--inspect private/run.inspection.jsonl
Each inspection artifact includes the frozen component sources. It adds
execution prints and any provider-backed private activity that occurred. A
failure can add detailed execution-error evidence. A raised capability
callback additionally records its bounded exception class, message, and
formatted stacktrace while the trace retains only the closed
provider_error / exception category. Exception text and stacktrace paths can
contain sensitive data and are not reliably redactable; read the artifact only
through an authorized private sink.
To debug compilation with the manifest bundle, use manifest mode:
ptc repl -m ptc.json
-l dynamically evaluates setup code and does not accept component-only forms
such as ns or defn-.
Use workflow REPL sessions
Start a scratch session or attach the manifest's frozen workflow environment:
ptc repl
ptc repl -e '(def x 40)' -e '(+ x 2)' -e '(+ *1 1)'
ptc repl --manifest ptc.json --host-config ptc-host.json
ptc repl --manifest ptc.json --trace traces/repl.jsonl
Successful definitions and three-value history persist for one session; failed
forms preserve prior state. Manifest providers are acquired once and reused.
Private manifest sessions require an attached terminal and
--private-terminal. The Kernel REPL guide covers all modes,
input forms, privacy gates, JSON Lines, bounds, and cleanup.
An interactive session on a terminal runs under the Erlang line editor, so the
usual editing keys work — Ctrl+A/Ctrl+E, word motion, Ctrl+K/Ctrl+U
and yank, arrow-key history, and Ctrl+R reverse search. Two keys behave
differently there than in a plain terminal reader:
:quitleaves the session.Ctrl+Ddeletes the character under the cursor instead of ending input, because the editor binds it that way and offers no end-of-input binding to rebind it to.Ctrl+Copens the BEAM break menu, as it does iniex. Presscto return to the prompt orato abort the command.
A direct session keeps its submitted lines between runs, under
ptc/repl-history in the user cache directory, so the previous session's
expressions are one arrow key away. A manifest session can carry a private
event policy, so it edits and recalls within the session but writes nothing to
disk.
Analysis profile sessions (--profile) read under the source limit that mode
enforces, which the line editor cannot drive. They keep the plain reader, and
with it Ctrl+D as end of input; :quit works there too. Every
non-interactive form — -e, a script argument, -, or redirected input —
reads exactly as before.
Query traces
Traces contain bounded operational events, not prompts, model responses, capability payloads, or generated source. Query one immutable directory capture through the fixed public profile:
ptc repl \
--profile run-analysis-v1 \
--resource traces=traces \
-e '(analysis/runs {})' \
-e '(analysis/open "run-id")'
Public analysis supports runs, open, and read; the public activity
collection contains trace events. open advertises the private collections
but they require a correlated inspection snapshot and private authority.
analysis/runs defaults to a compact projection containing run ID, status,
duration, LLM calls, evaluations, terminal reason, and completeness flags. Pass
{"view" "full"} when selecting by the complete metadata record:
(analysis/runs {"status" "error"})
(analysis/runs {"status" "error" "view" "full"})When a project already declares its artifact root, reuse it instead of repeating resource paths:
ptc repl --project ptc-project.json \
--profile run-analysis-v1 \
-e '(analysis/runs {})'
The public profile derives traces; the private profile derives both traces
and inspection when those artifact classes are enabled. An explicit
--resource NAME=DIR overrides only that derived resource.
The TraceLog and run-analysis reference defines event schemas, sanitization, filtering, pagination, and source classes. The Kernel REPL guide covers longer investigations.
Inspect a private model conversation
Capture canonical and inspection artifacts in separate trusted locations. The credential-free Kernel inspection lab creates a correlated pair without a live model.
For one transcript, avoid a REPL:
mkdir -p tmp/transcript
ptc transcript RUN_ID \
--traces tmp/traces \
--inspection tmp/inspection \
--private-unattended \
--private-output tmp/transcript/conversation.private.json
The command reserves an owner-only destination before capture. The parent of
--private-output must already exist and be reached without a symbolic link
— on macOS /tmp is a symlink, so /tmp/out.json is refused. Trace,
inspection, and output directories must be pairwise physically separate: no
directory may equal, contain, or be contained by either of the others. A file
in the current directory fails when that directory contains --traces; create
a sibling directory instead, as above. A rejection names the two conflicting
switches and their physical relationship, and discloses no path. Ambiguous,
incomplete, changed, unsupported, or oversized evidence fails without a
partial output.
Use private-run-analysis-v1 when you need several correlated questions or
custom PTC-Lisp analysis. Its results can include exact messages, generated
source, effective components, capability payloads, prints, diagnostics, and
terminal values. The attached-terminal and unattended switches are accident
guards, not access control; treat every downstream sink as private.
To walk the same capture from an ordinary application rather than a session,
Debug a failed run installs it as a snapshot
provider and follows typed evidence links with the shipped debug.nav prelude.
Browse traces in the Viewer
ptc viewer ptc-project.json --env-file .env
The project document supplies the trace root and optional inspection root, plus
the port, browser-opening preference, REPL setting, and private-data grant. The
Viewer pins the selected data and can open a bounded analysis REPL over an
immutable capture. --port overrides the project's port; 0 asks the
operating system for a free one and is the project default. Startup prints the
selected address. If an explicitly selected port is occupied, the command
probes loopback: another PTC Viewer is reported with its exact project document
path, while any other listener is reported as an occupied service. The command
runs in the foreground until Ctrl+C, and opens a browser only when the project
asks for it and a terminal is attached.
The Viewer does not search the invocation directory or its parents for a
.env file. Environment-backed provider credentials come from the inherited
process environment, the project's host.env_file, or an explicit
--env-file FILE. The command-line file is resolved when the Viewer starts
and overrides the project's environment-file reference for every workflow or
mission launched from its Live tab. It is read only when the selected provider
actually requires an environment credential.
The Viewer ships inside the standalone release and the container image. It is
not part of the published Hex package, where ptc doctor reports it as an
unavailable optional companion and ptc viewer says so rather than failing
obscurely. See the
Viewer documentation
for its complete HTTP API.
Expose it deliberately, or not at all
The Viewer has no authentication and can display private inspection records
when the project grants them, so it binds 127.0.0.1 and reaches nothing else.
--listen 0.0.0.0 is the only way to change that, it accepts no other address,
and it prints a warning when used. Authenticated remote Viewer hosting is not a
goal of this command.
A container is the one place the wildcard is routine, because it is not an
exposure decision there. Inside a container 127.0.0.1 is the container's own
loopback, while a published port forwards to the container's external
interface, so a loopback bind refuses every connection a -p mapping delivers.
Binding 0.0.0.0 inside the container's network namespace is what makes the
mapping reachable, and the host-side exposure decision moves to the publish
rule:
image=ghcr.io/andreasronge/ptc_runner:VERSION
docker run --rm \
--user "$(id -u):$(id -g)" \
--env HOME=/tmp \
-p 127.0.0.1:4123:4123 \
-v "$PWD:/work" \
"$image" viewer /work/ptc-project.json --listen 0.0.0.0 --port 4123
The 127.0.0.1: prefix on -p is what keeps this equivalent to a loopback
bind. Writing -p 4123:4123 instead publishes an unauthenticated trace browser
to every host that can reach the machine. The command cannot enforce that
prefix; you must write it. The user mapping preserves access to the
mounted project's owner-only artifacts; do not run this form from a root shell.
Watch and launch live runs
A Kernel run reports to the Live tab when PTC_VIEWER_URL names the Viewer:
PTC_VIEWER_URL=http://127.0.0.1:4123 ptc run ptc.json
The reporter is best-effort and does not alter the run result. Frames are
correlated to their owning run, and the terminal frame is published only after
provider cleanup and trace-event finalization establish the actual
outcome. A timeout failure names the binding limit, its configured duration,
and the manifest key that raises it, in both the launch diagnostic and the
ended Live card; for example, parallel_timeout_ms limit 60000 ms was exceeded during execution; raise limits.parallel_timeout_ms in the manifest, and the installed host ceiling if it is lower. Mission
sessions currently show their bounded command-output tail in the launch panel
instead of streaming frames.
An ended workflow card offers View result. That action captures a fresh, internally consistent trace snapshot, confirms that the matching run exists, and then opens its detail view in the Runs tab. The Viewer therefore does not need to be restarted after a run it launched.
The ordinary project command also configures the Live project details and one fixed launch target from that same project document. The browser may edit workflow input or choose one declared mission, but it cannot choose a project, manifest, working directory, or command:
ptc viewer ptc-project.json --env-file .env
Viewer-launched workflow cards use the manifest label and workflow entry as
their human-facing title, while retaining the cmd-... value as the stable run
identifier. The Live tab and GET /api/live/runs list newest first, and each
card shows when the Viewer first saw that run, so an edited ceiling cannot be
read off an older card as a stale enforcement.
The command is already a long-running PtcRunner host, so Viewer-started work
runs inside that BEAM instance under the ordinary execution-session owner. A
host-injected adapter receives a semantic workflow or mission request and a
direct live-frame sink; no mix or ptc child process is started. The adapter
dispatches the named project through the same command engine, so its host,
environment, and artifact defaults remain authoritative.
Live browser reads require a page opened at localhost, 127.0.0.1, or
::1; mutations additionally require the page's same-origin nonce. A reporter
connecting from a non-loopback address must send the configured token through
PTC_VIEWER_TOKEN. Generate a new value for each Viewer process, for example
with openssl rand -hex 32.
When a host-published port makes the browser's network peer non-loopback, open the Live tab once with the same token:
http://localhost:4123/?live_token=THE_TOKEN#/liveThe page removes the query parameter after bootstrapping and authenticates all Live API reads and mutations with the token. The SSE stream carries it in its own encoded query because the browser EventSource API cannot set headers.
For Docker, keep the Viewer bound to 0.0.0.0 inside the container and keep
the published host port on loopback. Viewer-started runs report directly inside
the container process. A separately started run in the same container can
report over container loopback. A host-side run can use
PTC_VIEWER_URL=http://127.0.0.1:4123; another container must instead use the
Viewer container's service name on a shared Docker network (for example,
PTC_VIEWER_URL=http://viewer:4123) or an explicitly configured host-gateway
address. Both must set the matching PTC_VIEWER_TOKEN. This protects live
ingestion and browser mutations; it does not turn the trace browser into an
authenticated remote service, so -p 4123:4123 remains unsafe.
Test a workflow
Use deterministic fixtures for normal tests. Assert the business value and semantic error classification, not timestamps, run references, or remaining milliseconds.
One shell-level check can use the stable envelope:
ptc init kernel-tutorial --example kernel-tutorial
artifact_dir="$(mktemp -d)"
envelope="$artifact_dir/command-envelope.json"
ptc run 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"]}'
Test scripted model responses before a small live-provider boundary. The replay evaluation guide shows the deterministic path; the Quickstart keeps one deliberately small live check.
Next steps
- Kernel REPL covers session and analysis modes.
- Manifests and capabilities defines the application boundary these commands enforce.
- Host configuration defines provider installation.
- Building agents explains the agent policy producing the runs.