Command-line reference

Copy Markdown View Source

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

CommandPurpose
ptc init DIRECTORYPublish a validated minimal application without replacing an existing target
ptc init DIRECTORY --example NAMEPublish 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 PROJECTLoad and compile without executing the workflow, and read the input files the declarations name
ptc run MANIFEST or PROJECTExecute the application entry
ptc run MANIFEST --env-file FILELoad 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.jsonList public installed model-alias declarations, each with the safe selector it configured
ptc transcript RUN_ID ...Publish one correlated private model transcript
ptc replOpen a direct, manifest-backed, or analysis session
ptc viewer PROJECT.jsonBrowse a project's captured traces in a local web UI
ptc viewer PROJECT.json --env-file FILEUse 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.json replaces 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-descriptor host paths).
  • --private-input INPUT.json does the same and classifies the run as private.
  • --output VALUE.json publishes a normal result value without replacing an existing file.
  • --private-output VALUE.json publishes a private result at owner-only mode and keeps it off stdout.
  • --trace-dir DIR writes <run_ref>.jsonl or <run_ref>.private.jsonl according to the run's artifact class.
  • --inspect FILE writes sensitive execution evidence to an owner-only .inspection.jsonl file.
  • --envelope FILE atomically publishes a convenience copy of the stable V3 command envelope. When a project enables artifacts.envelope, the project's .ptc/envelopes/<run_ref>.json ledger entry is still written for that run. run, validate, doctor, models, and init all 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.

StatusMeaningPhases
0the command succeeded
2the arguments were rejected before any document was readarguments
3a declaration document was unavailable, invalid, or rejectedproject, host, application, bundle, provider_declaration
4a selected provider could not be checked or acquiredlocal_preflight, active_preflight, provider_acquisition
5the workflow ran and failedexecution
6the run exceeded a limit or its durationexecution
7the run produced no usable artifact: a destination, result, or publication failuredestination, execution, result_cleanup, publication
70the command failed internallyinternal
74the requested envelope could not be published, so no envelope describes this failure

Every classified diagnostic and the status it exits with:

StatusPhaseCodeRetryableMessage
2argumentsconflicting_argumentsnochoose only one option from the conflicting argument group
2argumentsdocs_page_unknownnono documentation page is served under that name
2argumentsenvelope_destination_existsnothe envelope destination already exists
2argumentsexample_unknownnono example is embedded under that name
2argumentsinvalid_argumentsnouse the documented arguments for this command
2argumentsinvalid_commandnouse one of the supported commands
2argumentsproject_host_undeclarednothe project document declares no host block; add one to use this command
3applicationapplication_not_foundnothe application manifest does not exist
3applicationapplication_unavailablenothe application is unavailable
3applicationcontract_invalidnoan application value contract is invalid
3applicationdocument_limit_exceedednothe application document closure exceeds its limit
3applicationduplicate_propertynoan application document contains a duplicate property
3applicationevent_identity_conflictnothe command event identity conflicts with the application
3applicationinput_contract_failednothe selected input does not satisfy the input contract
3applicationinput_invalidnothe selected input is not an admissible JSON object
3applicationinstalled_limit_exceedednoan application limit exceeds the installed ceiling; lower it or raise the host-configured ceiling
3applicationinvalid_jsonnoan application document is not valid JSON
3applicationoverride_invalidnothe component override is invalid
3applicationreference_missingnoa referenced document is unavailable; for --input/--private-input try an application-relative name or an absolute/working-directory path
3applicationrequired_property_missingnothe application manifest is missing a required property
3applicationschema_validation_unavailableyesapplication schema validation timed out or exceeded its resource bound; retry the command
3applicationschema_violationnothe application manifest does not satisfy its schema
3bundlebundle_invalidnothe component bundle is invalid
3bundlebundle_limit_exceedednothe component bundle exceeds a compile limit
3bundlecompile_failednothe component bundle could not be compiled
3bundleduplicate_definitionnothe component bundle defines the same name more than once
3bundleentry_invalidnothe workflow entry is not a public bundle export
3bundlemission_undeclarednothe workflow entry evaluates into a mission and the manifest declares none
3bundlesyntax_invalidnothe component source is not valid PTC-Lisp
3bundleundefined_variablenothe component source contains an undefined variable reference
3bundleunknown_namespacenothe component source references an unavailable namespace
3hosthost_invalidnothe host configuration is invalid
3hosthost_schema_invalidnothe host configuration does not satisfy its schema
3hosthost_unavailablenothe host configuration is unavailable
3hostinstallation_endpoint_credentials_require_httpsnoconfigured MCP credentials require an https endpoint
3hostinstallation_endpoint_insecure_loopback_forbiddennoallow_insecure_loopback is not permitted on an https endpoint; remove it
3hostinstallation_endpoint_insecure_loopback_requirednoa plain-http MCP endpoint requires allow_insecure_loopback
3hostinstallation_endpoint_invalidnoan installed MCP endpoint is not admissible; streamable_http requires an https URL, or allow_insecure_loopback with a credential-free plain-http loopback address
3hostinstallation_endpoint_literal_loopback_requirednoallow_insecure_loopback requires a literal 127.0.0.1 or [::1] address
3hostinstallation_revision_missingnoan installed provider is missing its behavior revision
3hostinstalled_limit_invalidnoan installed limit is invalid
3hostschema_validation_unavailableyeshost schema validation timed out or exceeded its resource bound; retry the command
3projectproject_schema_invalidnothe project configuration does not satisfy its schema
3projectschema_validation_unavailableyesproject schema validation timed out or exceeded its resource bound; retry the command
3provider_declarationdata_policy_deniednothe selected providers do not admit the effective data class
3provider_declarationdependency_invalidnothe selected provider dependency graph is invalid
3provider_declarationplacement_deniednothe provider is not allowed in this destination
3provider_declarationprovider_unknownnothe selected provider is not installed
3provider_declarationselection_invalidnothe provider selection is invalid
3provider_declarationselection_unverifiablenothe provider selection cannot be verified declaratively
4active_preflightauthentication_rejectednoprovider authentication was rejected
4active_preflightauthorization_rejectednoexplicit provider authorization was rejected
4active_preflightauthorization_requirednoexplicit provider authorization is required
4active_preflightauthorization_unavailableyesthe authorization service is temporarily unavailable
4active_preflightconnectivity_outcome_unknownnothe connectivity outcome could not be committed safely
4active_preflightconnectivity_protocol_errornothe provider returned an invalid connectivity response
4active_preflightconnectivity_rate_limitedyesthe provider connectivity operation is rate limited
4active_preflightconnectivity_rejectednothe provider rejected the connectivity operation
4active_preflightconnectivity_timeoutnothe connectivity operation exceeded its budget
4active_preflightconnectivity_unavailableyesthe provider connectivity operation is temporarily unavailable
4active_preflightconnectivity_unsupportednothe provider does not implement the declared connectivity check
4active_preflightcredential_unavailablenoa required provider credential is unavailable
4active_preflightprovider_application_unavailablenoa required provider application is unavailable
4active_preflightselection_rejectednothe provider rejected the normalized selection
4active_preflightselection_validation_failednoactive provider selection validation failed
4active_preflightselection_validation_timeoutnoactive provider selection validation timed out
4local_preflightadapter_unavailablenoa required provider adapter is unavailable
4local_preflightauthorization_not_applicableno--authorize-mcp applies only to an installation that declares OAuth
4local_preflightauthorization_target_unknownno--authorize-mcp must name an installed provider the application selects
4local_preflightcommand_not_foundnoa required provider command could not be found
4local_preflightenvironment_file_invalid_utf8nothe named environment file is not valid UTF-8
4local_preflightenvironment_file_not_foundnothe named environment file does not exist
4local_preflightenvironment_file_not_regularnothe named environment file is not a regular file
4local_preflightenvironment_file_too_largenothe named environment file exceeds the 1 MB limit
4local_preflightenvironment_file_unreadablenothe named environment file cannot be read safely
4local_preflightenvironment_unavailablenoa required local environment is unavailable
4local_preflightexecutable_unavailablenoa required provider executable is unusable
4local_preflightfixtures_unreadablenoprovider fixtures could not be read
4local_preflightlauncher_unavailablenoa required provider launcher is unavailable
4local_preflightlocal_check_timeoutnoa local provider check timed out
4provider_acquisitioncapability_requirement_missingnoa component requires a capability that the selected providers did not supply
4provider_acquisitionprovider_acquisition_timeoutyesthe selected provider exceeded its acquisition timeout budget
4provider_acquisitionprovider_endpoint_connection_refusedyesthe installed endpoint refused the connection
4provider_acquisitionprovider_endpoint_name_unresolvednothe installed endpoint hostname could not be resolved
4provider_acquisitionprovider_endpoint_tls_failednothe installed endpoint did not complete a TLS handshake
4provider_acquisitionprovider_policy_changednothe selected provider policy changed during acquisition
4provider_acquisitionprovider_protocol_errornothe selected provider returned an invalid acquisition response
4provider_acquisitionprovider_protocol_version_unsupportednothe endpoint did not advertise support for MCP protocol 2026-07-28
4provider_acquisitionprovider_tool_missingnothe installed endpoint does not expose a declared tool
4provider_acquisitionprovider_unavailablenothe selected provider could not be acquired
5executionevaluation_failednothe evaluation failed
5executioninvalid_agent_confignoan agent configuration option is outside its supported range
5executionllm_access_deniednothe LLM provider denied access to the configured model
5executionllm_authentication_failednothe LLM provider rejected authentication; check the installed credential
5executionllm_model_not_foundnothe LLM provider could not find the configured model
5executionllm_payment_requirednothe LLM provider rejected the request for billing or credit reasons
5executionllm_provider_failednothe LLM provider request failed
5executionllm_provider_unavailableyesthe LLM provider is unavailable
5executionllm_rate_limitedyesthe LLM provider rate limited the request
5executionllm_request_invalidnothe LLM provider rejected the configured request
5executionllm_timeoutyesthe LLM provider request timed out
5executionllm_tool_calling_unsupportednothe configured model does not support tool calling
5executionmission_failednoa subordinate mission failed
5executionprovider_failednoa provider failed during execution
5executionreplay_fixture_missingnono replay fixture matches the workflow request
5executionworkflow_failednothe workflow failed
6executioncapability_quota_exceedednoa capability quota was exceeded
6executionmodel_output_truncatednomodel output was truncated before producing a usable agent action
6executionrun_timeoutnothe run duration limit was exceeded
6executionruntime_limit_exceedednoa runtime limit was exceeded
6executionturn_limit_exceedednothe agent turn limit was exceeded
7destinationdestination_existsnoan artifact destination already exists
7destinationinspection_destination_unavailablenothe inspection destination is unavailable
7destinationinspection_destination_unsafenothe inspection destination is unsafe
7destinationinspection_directory_missingno--inspect must name a file in an existing directory
7destinationinvalid_destinationnoan artifact destination is invalid
7destinationinvalid_inspection_destinationno--inspect must name a valid destination ending in .inspection.jsonl
7destinationinvalid_result_destinationnothe result destination is invalid
7destinationinvalid_trace_destinationnothe trace destination is invalid
7destinationprivate_destination_requirednothe run requires an authorized private destination
7destinationrecovery_reservation_failednothe private result recovery reservation failed
7destinationresult_destination_unavailablenothe result destination is unavailable
7destinationresult_destination_unsafenothe result destination is unsafe
7destinationresult_directory_missingno--output and --private-output must name a file in an existing directory
7destinationtrace_destination_unavailablenothe trace destination is unavailable
7destinationtrace_destination_unsafenothe trace destination is unsafe
7destinationtrace_directory_missingno--trace-dir must be an existing normal directory
7executionevent_capture_limit_exceedednothe trace event capture limit was exceeded
7executionevent_sink_unavailablenothe trace event sink is unavailable
7executioninspection_capture_limit_exceedednothe private inspection capture limit was exceeded
7executioninspection_sink_unavailablenothe private inspection sink is unavailable
7publicationdestination_collisionnoan artifact destination appeared before publication
7publicationinitialization_failednoproject initialization failed
7publicationinitialization_parent_missingnothe initialization target's parent directory does not exist
7publicationinitialization_parent_unusablenothe initialization target's parent directory is unusable
7publicationinitialization_target_existsnoptc init publishes only to a new directory; choose a target that does not already exist
7publicationinspection_publication_failednoinspection publication failed
7publicationrecovery_cleanup_failednoprivate result recovery cleanup failed
7publicationresult_publication_failednoresult publication failed
7publicationtrace_publication_failednotrace publication failed
7result_cleanupprovider_cleanup_failednoprovider cleanup failed
7result_cleanupprovider_cleanup_timeoutnoprovider cleanup timed out
7result_cleanupresult_contract_failednothe workflow result does not satisfy its contract
7result_cleanupresult_invalidnothe workflow result is invalid
7result_cleanupresult_limit_exceedednothe workflow result exceeds its limit
70internalinternal_errornothe 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 kind is recognized retains that readable kind;
  • an authenticated agent.core turn-limit failure additionally retains the fixed agent_turns name, the effective integer ceiling from 1 through 128, and the closed reason the loop stopped (turn_limit_exceeded, intermediate_result, evaluation_error, or protocol_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:

  • :quit leaves the session. Ctrl+D deletes 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+C opens the BEAM break menu, as it does in iex. Press c to return to the prompt or a to 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#/live

The 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