The host document is the operator's half of a PtcRunner deployment. It is a strict JSON file, separate from any application manifest, that decides which providers exist, what their names mean, where their credentials come from, what data they may touch, and how large a run may get.
A manifest may then select an installed alias and narrow it. It can never introduce a provider, name an executable or endpoint, carry a credential, or raise a ceiling. Pass the document explicitly:
mix ptc run MANIFEST --host-config ptc-host.jsonA provider-bearing manifest requires --host-config; a provider-free manifest
runs without one.
Loading is bounded, path-confined, duplicate-key rejecting, and side-effect free. Credential declarations are validated but not read, executables are not resolved, no process is started, and no endpoint is contacted. Those belong to the later preflight and acquisition phases.
{
"credentials": {
"openrouter_key": {"env": "OPENROUTER_API_KEY"}
},
"install": {
"deepseek": {
"source": "llm",
"installation_revision": "deepseek-policy-v1",
"model": "openrouter:deepseek/deepseek-v4-flash-0731",
"credential": "openrouter_key",
"cache": false
},
"workspace": {
"source": "mcp",
"transport": {
"type": "stdio",
"command": "node",
"cwd": ".",
"args": ["../mcp/filesystem/dist/server.js", "--root", "files"],
"inherit_environment": true,
"env": {}
},
"tools": {
"read_text_file": {"as": "workspace.read", "effect": "read"}
},
"installation_revision": "filesystem-sample-0.1.0",
"ceilings": {"timeout_ms": 15000, "max_catalog_tools": 8}
}
}
}Only install is required. The other top-level keys are credentials,
limits, runtime, and $schema. Unknown keys are rejected. A document is at
most 1 MB and holds at most 128 installations and 128 credentials. Every alias,
credential name, and public tool name matches ^[a-z][a-z0-9._-]{0,127}$.
Optional properties use omission for their default; an explicit JSON null
does not mean omitted and fails structural validation.
Every installation requires installation_revision. It is a public,
non-secret behavior identity matching exactly
^[a-z][a-z0-9._-]{0,127}$. Change it whenever model or endpoint behavior,
adapter/launcher build, MCP mappings or effects, replay fixtures, snapshot
policy, or another installed authority detail changes. A missing revision is
reported as host/installation_revision_missing for the affected alias before
generic schema reporting, even when the application does not select it.
The canonical structural description is shipped as
priv/schemas/ptc-host-config.schema.json for editor completion. Runtime
decoding stays authoritative for semantic checks such as unique public tool
names, credential references, reserved headers, and portable environment names.
Standalone command diagnostics classify duplicate properties as structural
host failures and retain only the duplicate's schema-authorized parent pointer.
A credential is declared once and referenced by name. There are exactly three forms:
"credentials": {
"openrouter_key": {"env": "OPENROUTER_API_KEY"},
"vendor_token": {"file": "secrets/vendor.token"},
"fixed_key": {"literal": "sk-example-not-a-real-key"}
}envreads a process environment variable at acquisition time. The name must be a portable^[A-Za-z_][A-Za-z0-9_]*$identifier.filereads a path. A relative path is confined to the host document's own directory; an absolute path is canonicalized before reading. The value is at most 64 KiB.literalcarries the secret inline. Use it only for local development or a document that a secret manager renders at deploy time.
Credentials are resolved once, at provider acquisition, and passed to the
adapter explicitly. There is no ambient provider-specific environment lookup. A
missing variable, unreadable file, or empty value fails the run with
credential_unavailable rather than falling back.
Credentials never belong in a manifest, in PTC-Lisp, in a canonical trace, or
in a committed project file. A safe provider snapshot records the public
installation revision and normalized selected policy. For a live LLM it also
records acquisition.resolved_model only when the active adapter explicitly
attests that exact target as safe public identity. Private targets and secrets
remain absent.
A command-owned VM disables dependency .env readers before starting the
selected provider application. A reused host-owned application keeps whatever
its own start applied, because it was already running.
For a selected shipped LLM whose declared credential uses env, the Mix
frontend loads the nearest .env through PtcRunner.Dotenv once, after
preparation has established which providers are selected and before any
execution owner, provider session, or run clock exists. Loading is command
setup rather than bounded run work: it reads the filesystem and mutates the VM
environment, so it can be neither deadline-cancelled nor rolled back, and it
never consumes the run budget. One declared credential
triggers the load, but the load is not scoped to it: the loader walks up from
the invocation directory to the filesystem root and sets every variable in the
first .env it finds. This happens once per VM and those variables persist for
the process lifetime, so a later invocation in the same VM keeps them. Existing
process variables take precedence. Embedded hosts do not load .env
implicitly; export an env credential before invoking them, or use a
shell-local environment manager or explicit secret source:
export OPENROUTER_API_KEY="$(secret-tool lookup service openrouter)"
mix ptc run ptc.json --host-config ptc-host.jsonTools such as direnv may populate the process environment before the command
starts. A .env file being Git-ignored does not make it a safe plaintext secret
store.
There are six closed source identifiers. Each one may only be installed into one environment, and that placement is enforced at assembly:
source |
Purpose | Required keys | Environment |
|---|---|---|---|
llm |
Live language model | installation_revision, model, credential |
Workflow only |
llm_replay |
Frozen model responses | installation_revision, fixtures |
Workflow only |
mcp |
External tool server | installation_revision, transport, tools |
Mission only |
ptc_trace_snapshot |
Canonical trace queries | installation_revision, directory |
Mission only |
ptc_private_trace_snapshot |
Private-authorized canonical trace queries | installation_revision, directory |
Mission only |
ptc_inspection_snapshot |
Private inspection queries | installation_revision, directory |
Mission only |
Selecting an alias into the wrong environment fails with
provider_destination_denied. This is what keeps model authority out of
model-authored mission code.
"deepseek": {
"source": "llm",
"installation_revision": "deepseek-policy-v1",
"model": "openrouter:deepseek/deepseek-v4-flash-0731",
"credential": "openrouter_key",
"cache": false,
"params": {"temperature": 0.2, "seed": 42, "max_tokens": 4096},
"ceilings": {"max_request_bytes": 1000000, "max_response_bytes": 1000000}
}The host fixes the full model identifier, so a manifest's short alias carries no
provider-resolution magic and cannot override its credential. params is
optional and closed: temperature (0–2), seed (a non-negative signed 32-bit
integer), and max_tokens (1–1,000,000). Provider support varies, so use
parameters the installed model actually implements.
cache is the host's fixed cache policy. A workflow may express a cache
preference in its request, but this setting takes precedence. Request and
response ceilings default to 1,000,000 bytes and cannot exceed 1,048,576.
Live acquisition validates and captures the configured full model identifier
once. Attestation, callback construction, provider readiness, and execution use
that same value. The built-in ReqLLM adapter publishes its ReqLLM-routed model
identifiers, while endpoint-bearing openai-compat and local Ollama targets
remain private. Custom adapters publish nothing unless they implement the
optional attestation callback. Private calls still appear in alias/revision
usage and count as unattributed in model-grouped usage.
The resolved model, when public, is part of both provider snapshot hashes. A
model change therefore changes canonical provider snapshot identity even when
the operator forgets to change installation_revision. The revision remains
the operator's policy/deployment label rather than the sole evidence of the
execution target.
The active doctor --connect check for a selected live model performs one real
minimal completion and may incur provider cost. The probe forces a one-token
output ceiling, disables retries and redirects, and uses the installed
credential under doctor_connectivity_timeout_ms; provider errors and timeouts
produce a nonzero doctor report. An attributable diagnostic becomes the failed
local, selection, credential, authorization, or connectivity row. Other pending
rows are not_verified_due_to_failure, because the fail-fast operation retains
no per-step success transcript.
ptc doctor reports installed or selected workflow models in a
model_aliases list, including alias, source, installation revision, default
status, and whether the application selected it. --show-model-selectors adds
safe configured selectors. Endpoint-bearing openai-compat: selectors are
omitted even with that flag so doctor output cannot disclose URL credentials or
deployment endpoints.
Evaluation needs a model whose answers do not move between a baseline run and a candidate run, otherwise a behavioural difference cannot be attributed to the candidate. A replay source is ordinary configuration rather than a test hook: the same manifest selects a replay alias or a live one, and nothing about the application changes between them.
"frozen-model": {
"source": "llm_replay",
"installation_revision": "frozen-model-v1",
"fixtures": "evaluation/replay.jsonl",
"ceilings": {"max_entries": 10000, "max_result_bytes": 1048576}
}The fixture file is JSON Lines. Each entry names the request_hash it answers
and carries either one response or an ordered responses sequence. The
sequence form exists for a request that repeats identically — a retry, or a
loop that rebuilds the same prompt. An ordinary multi-turn agent loop does not
need it, because each turn carries the accumulated transcript and therefore
hashes differently.
The hash covers the deterministic encoding of the provider-neutral request the workflow actually built, before any adapter sees it, so a fixture is not tied to the vendor that recorded it. The match is exact by construction: a run whose prompt, messages, or tools differ at all produces a different hash and fails rather than replaying a response recorded for a different question.
An MCP installation fixes the transport, the tool mapping, and the effect of every mapped tool. It accepts 1 to 128 tools.
"workspace": {
"source": "mcp",
"installation_revision": "workspace-v1",
"transport": {"type": "stdio", "command": "node", "args": ["server.js"]},
"tools": {
"read_text_file": {
"as": "workspace.read",
"effect": "read",
"description": "Read one UTF-8 file beneath the granted root.",
"model_visible": false,
"error_feedback": "closed"
},
"snapshot_info": {
"as": "workspace.info",
"effect": "read",
"model_visible": false,
"error_feedback": "closed"
},
"write_text_file": {
"as": "workspace.write",
"effect": "write",
"description": "Replace one UTF-8 file beneath the granted root.",
"model_visible": true,
"error_feedback": "closed"
}
},
"snapshot_identity": {"tool": "snapshot_info", "field": "snapshot_hash"},
"ceilings": {
"timeout_ms": 5000,
"max_catalog_tools": 128,
"max_result_bytes": 1000000
}
}Only the public as name crosses the capability boundary; the upstream name
stays inside the installation. effect is the required operator declaration
read or write. MCP server annotations such as readOnlyHint,
destructiveHint, and idempotentHint do not change it. A manifest can select
or hide a mapping but cannot change its installed effect.
An installation containing any write mapping requires every selecting
manifest to provide an explicit, non-empty allow list, even when that
particular selection chooses reads only. This makes adding a write mapping to an
existing installation fail closed for unchanged manifests rather than silently
widening their authority. Omitted allow remains a convenience only for
all-read installations. Installation plus explicit selection is standing
authorization; there is no separate per-call approval prompt. The mapped
effects remain visible in the host document and capability inventory.
The declared effect also controls failure safety. A read transport failure keeps
its provider retry policy. A write failure after dispatch may have begun is
non-retryable and reports mutation_state: indeterminate independently from its
specific timeout, protocol, domain, validation, or transport cause.
See Building agents for the
retry table.
model_visible decides whether the capability appears in model context. It
grants nothing: a granted hidden capability stays callable by exact name, and an
ungranted one stays denied. A manifest may narrow the visible set but never
extend it beyond what the installation marked visible.
error_feedback defaults to closed. Setting it to bounded exposes at most
1,024 bytes of validated text from an MCP isError result as untrusted
recoverable error detail, with terminal control characters replaced. Enabling
it trusts the installed server not to place secrets, paths, or stack traces in
that text. Public canonical events stay closed either way.
snapshot_identity names one mapped read-only tool and a field in that tool's
result. PtcRunner calls it once during assembly with an empty argument object,
so the tool must require no arguments. The field must hold a lowercase
sha256: digest, published as content_snapshot_hash. The identity tool need
not be selected into the mission environment; failing to obtain a valid identity
closes provider assembly.
Install it when the server serves content that cannot change during a run, so
the digest identifies exactly the bytes the run could have read. Only the
digest's shape is checked here; nothing verifies that the server is genuinely
immutable. The
filesystem sample
explains when the field is worth installing, and repo-analyst.host.json in the
repository root is a working installation of it.
Ceilings default to a 5,000 ms end-to-end timeout (maximum 300,000), 128 catalog tools, and 1,000,000 result bytes (maximum 1,048,576).
A stdio transport launches a local executable:
"transport": {
"type": "stdio",
"command": "node",
"cwd": ".",
"args": ["../mcp/filesystem/dist/server.js", "--root", "files"],
"inherit_environment": true,
"env": {"VENDOR_TOKEN": {"binding": "vendor_token"}},
"start_timeout_ms": 5000,
"grace_ms": 250,
"stderr_bytes": 65536
}command is required. A relative cwd resolves against the host document.
env injects credentials by binding name rather than value, so the secret never
appears in the document. Environment names must be portable identifiers and may
not shadow the compatibility set HOME, LC_ALL, LOGNAME, PATH, SHELL,
TERM, or USER. The runtime always pins LC_ALL=C.UTF-8 so locale-sensitive
servers encode MCP frames as UTF-8. inherit_environment controls only the
remaining compatibility names; it never inherits the caller's locale. Startup,
shutdown grace, and captured stderr are all bounded. env accepts at most 249
bindings, reserving space within the launcher's 256-entry limit for the seven
runtime compatibility names.
A streamable_http transport reaches a remote server:
"transport": {
"type": "streamable_http",
"endpoint": "https://mcp.example.com/v1",
"auth": [
{"scheme": "bearer", "binding": "vendor_token"},
{"scheme": "api_key", "binding": "vendor_token", "header": "X-Api-Key"}
]
}auth accepts at most eight entries. bearer and basic need only a binding;
api_key also names its header. Protocol-owned headers are reserved and
rejected, including authorization, content-type, host,
content-length, connection, transfer-encoding, proxy-authorization, and
the mcp-* family.
Interactive authorization is a Mix-frontend capability. --authorize-mcp is a
Mix-only option and the standalone command disables the interactive
authorization notifier, so an OAuth-protected installation cannot complete a
first authorization from the packaged command or a container. Authorize through
Mix in a source checkout. A packaged invocation against an unauthorized
installation fails with an authorization diagnostic rather than prompting.
Tokens live only in the memory of the run that acquired them; no persistent
token storage ships today.
An OAuth installation replaces static auth with a host-owned oauth block:
"transport": {
"type": "streamable_http",
"endpoint": "https://mcp.example.com/v1",
"oauth": {
"installation_id": "primary-account",
"issuer": "https://accounts.example.com",
"scope_ceiling": ["documents.read", "offline_access"],
"default_scopes": ["documents.read"],
"refresh_access": "when_supported",
"client": {
"registration": "pre_registered",
"client_id": "ptc-runner-local",
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"loopback_redirect": {
"host": "127.0.0.1",
"path": "/callback"
}
}
}
}Authorize a named installation explicitly before provider acquisition:
mix ptc run ptc.json --host-config ptc-host.json \
--authorize-mcp workspaceThe command binds an operating-system-selected loopback port, prints one
authorization URL for you to open, waits for the exact callback, and only then
builds the provider. It never launches a browser. The option composes with the
immediately following run only. Repeat --authorize-mcp to authorize multiple
named installations.
The CLI store is process-local: the grant and any later 403 scope requirement
exist only for that command invocation, so another invocation must authorize
again. Embeddings may retain state across runs only by supplying their own
secure persistent MCPOAuth.Store adapter; PtcRunner does not ship one.
The CLI supports public clients with token_endpoint_auth_method: "none" and
an exact 127.0.0.1 or ::1 loopback redirect. localhost, fixed callback
ports, confidential clients, and HTTPS callbacks require an embedding
application using PtcRunner.Kernel.MCPOAuth.Authorization and an explicit
principal-scoped PtcRunner.Kernel.MCPOAuth.Context.
The host pins the exact resource, issuer, client, maximum scopes, refresh
policy, redirect authority, and permitted network origins. The manifest and
MCP server cannot widen them. Pre-registered clients support none and
client_secret_basic; a confidential secret is resolved just in time and is
not part of the ordinary provider credential barrier. Client ID Metadata
Documents are supported for public clients when the authorization server
advertises them. Dynamic Client Registration (DCR) is deliberately unsupported:
the final MCP profile deprecates it, and providers such as Google use
console-managed pre-registration instead.
PtcRunner requires an explicit, non-empty authorization scope. Challenge
scopes take priority, then Protected Resource Metadata scopes, then installed
default_scopes; the result must stay within scope_ceiling. If every source
is empty, authorization stops before interaction. This
MCP-OAUTH-EXPLICIT-SCOPE policy deliberately tightens MCP's omit-scope
fallback so a stored grant never has unreported authority.
Resource metadata that requires DPoP or contains signed_metadata is rejected.
The signed-metadata rejection is a PtcRunner interoperability restriction:
PtcRunner does not verify signed metadata and therefore refuses to consume
unsigned fields beside it. Authorization-server or client metadata requiring
PAR or DPoP is likewise unsupported.
Normal execution never opens an authorization interaction. A command refuses a
selected OAuth installation that no --authorize-mcp named before any provider
work, reporting active_preflight / authorization_required for that alias.
Beyond that point an absent, rejected, expired-without-refresh, or
indeterminate grant returns mcp_authorization_required, which a command
reports as that same closed code against the occurrence that hit it. A 401
rejects only the bearer generation
actually sent. A valid 403 insufficient_scope challenge stores a private
scope requirement for the next explicit authorization in the same store
lifetime. That server response overrides the sent generation's nominal token
scope report. A delayed response is discarded only when a strictly newer token
generation reports every required scope; otherwise the requirement remains.
If either response-driven store transition fails, the current provider reports
a transport failure and retains an equivalent runtime-shared local fence rather
than issuing that authority again. A replacement provider using the same local
store and grant key remains fenced. Retry provider shutdown after the store is
healthy, or install a strictly newer grant containing the complete requirement;
do not treat a failed close as permission to discard the old provider.
These response-driven transitions use a separate bounded cleanup budget, so a
response arriving at the HTTP deadline cannot skip its local fence. The scope
requirement or token rejection persists in a bounded non-owner worker started
with that fence, so a provider-task timeout cannot skip the durable transition
either. A definitive 401 status line stops immediately, even when the
remaining header block is oversized, malformed, or stalled. A 403 stops
after its complete bounded challenge headers, before the body. The HTTP
response callback installs the manager fence before handing a result back
to the bounded provider task, so cancellation cannot open a gap between parsing
the response and fencing its bearer generation. Closing the manager drains
those bounded persistence workers before discarding its local state. A
failed persistence is retained and retried during close; if the retry still
fails, provider shutdown reports a transport error and the runtime keeps the
shared fence. If provider acquisition fails before returning a provider close
handle, a supervised cleanup owner retains the manager and retries bounded
shutdown until persistence succeeds or the manager exits.
An ordinary 403, or a malformed or unsupported Bearer challenge, is a
non-retryable authentication or authorization result. Only failure to persist
one valid, satisfiable insufficient_scope challenge is reported as a
retryable transport failure.
PtcRunner does not
retry the original MCP request in either case. This is
MCP-AUTH-DEV-001, a deliberate safety deviation from MCP's recommended
step-up-and-retry behavior: a tools/call may be a write and must not be
replayed automatically.
Stdio containment is provided by the optional launcher companion, which pins a frozen SHA-256 identity of the executable, separates streams, and bounds cleanup. Override its location when needed:
"runtime": {"stdio_launcher": "/opt/ptc/bin/ptc-runner-launcher"}The path must be absolute.
These serve PtcRunner's own evidence back to a mission as query capabilities, reading an immutable capture rather than live files.
"history": {
"source": "ptc_private_trace_snapshot",
"installation_revision": "history-v1",
"directory": "traces",
"ceilings": {"max_source_bytes": 8000000, "max_result_bytes": 1048576}
},
"private-history": {
"source": "ptc_inspection_snapshot",
"installation_revision": "private-history-v1",
"directory": "inspection",
"ceilings": {
"max_files": 1024,
"max_source_bytes": 64000000,
"max_result_bytes": 1048576
}
}Directories resolve against the host document. Acquisition reads and validates once; later queries use the frozen capture even if the path contents change. Result ceilings have a floor of 158 bytes, which is the smallest response that can carry the reserved content hash plus an empty page.
ptc_trace_snapshot captures ordinary *.jsonl traces only.
ptc_private_trace_snapshot captures the immutable union of ordinary and
*.private.jsonl traces, reports each run's source class, excludes
*.inspection.jsonl, and fixes the installation data class to
private_inspection. Neither source accepts configurable data_class,
accepts_data, or private-mode fields.
Selecting an inspection snapshot also requires exactly one trace snapshot: the canonical capture is taken first, and every private artifact is validated against it. Selecting both trace source kinds with inspection is therefore an invalid duplicate service. Manifests and capabilities lists the derived capability names, TraceLog contract is normative for the query contract, and Running and debugging covers producing the artifacts in the first place.
A manifest that selects the trace snapshot only as the inspection provider's
acquisition dependency may set its provider config to {"expose": false}.
The trace provider still exports its opaque frozen snapshot to the paired
inspection provider, but contributes no duplicate prompt-visible analysis
capabilities.
Every installation carries a data class and a set of classes it accepts:
"vendor": {
"source": "mcp",
"installation_revision": "vendor-v1",
"data_class": "normal",
"accepts_data": ["normal"],
"transport": {"type": "streamable_http", "endpoint": "https://example.com"},
"tools": {"search": {"as": "vendor.search", "effect": "read"}}
}data_class is what the provider contributes and defaults to normal.
accepts_data is what it is willing to be run alongside and defaults to
["normal"]. Assembly computes the strictest class across every selected
provider and then requires that every one of them accepts it; otherwise the run
fails with provider_data_class_denied before anything opens.
The effect is a fail-closed contamination rule. A vendor connector left at the
defaults can never be selected into a run that also touches private inspection
data, because it does not accept private_inspection. Three source kinds fix
their class rather than declaring it: ptc_trace_snapshot is always normal;
ptc_private_trace_snapshot and ptc_inspection_snapshot are always
private_inspection; all three accept both classes. A run whose effective
class is private_inspection is forced onto the private event policy.
An optional limits block replaces the compiled installed ceilings. The
compiled defaults suit one bounded run; an agent that must work for hours needs
more turns, model calls, and trace events than they allow:
"limits": {
"run_duration_ms": 86400000,
"workflow_timeout_ms": 86400000,
"subordinate_evaluations": 500,
"subordinate_source_checks": 500,
"workflow_capability_calls": 1000,
"workflow_capability_calls_per_name": 1000,
"mission_capability_calls": 8000,
"mission_capability_calls_per_name": 8000,
"normal_event_count": 20000,
"normal_event_bytes": 2000000000
}Every cataloged limit name is accepted. Existing application limits accept
positive integers through 2,592,000,000. Two of them matter specifically for
parallel agent workloads: parallel_timeout_ms bounds one pmap/pcalls
operation (installed default 300,000 ms — multi-turn agent loops running
under pcalls need this headroom), and evaluation_admission_timeout_ms
bounds how long a concurrent kernel-eval waits for the run's single
evaluation lease before failing as evaluation-unavailable (installed
default 120,000 ms). Four operational timeouts instead have narrow,
installed-only contracts:
| Limit | Installed default | Accepted range | Identity metadata |
|---|---|---|---|
provider_cleanup_timeout_ms |
5,000 | 100–30,000 | Included |
local_preflight_timeout_ms |
5,000 | 100–30,000 | Included |
selection_validation_timeout_ms |
5,000 | 100–30,000 | Included |
doctor_connectivity_timeout_ms |
10,000 | 100–30,000 | Excluded; doctor-only |
Applications cannot declare or narrow those four names. The first three are
copied into sealed execution limits and marked as effective-identity
participants; the doctor-only value is excluded. local_preflight_timeout_ms
bounds the whole audited-local step rather than one check: every applicable
occurrence spends what remains of one anchored deadline, so a selection with
many local checks cannot multiply it. All four are now imposed rather than only
sealed: RunCoordinator.local_checks/3 anchors the audited-local deadline, and
a provider session seals the selection-validation and connectivity budgets from
its own limits, so naming an operation cannot widen either. Any omitted name
keeps its cataloged installed default.
Raising a ceiling here does not by itself lengthen any run. The manifest rule is unchanged for manifest-narrowable limits: an application may still only request values at or below what is installed, so a manifest that needs the larger budget must ask for it. Both documents stay explicit — the host decides the maximum an operator permits, and the manifest declares what its application needs. See Manifests and capabilities for the application side and the full limit vocabulary.
The most common reasons a long agent loop stops early are
subordinate_evaluations (its turn count), the workflow total and per-name
capability ceilings (its model calls), the mission total and per-name ceilings
(its tool calls), and both the count and byte ceilings for normal events (its
retained trace evidence). Raising run_duration_ms alone does not help, because
the binding deadline for one workflow entry is workflow_timeout_ms.
Generated-source validation instead consumes subordinate_source_checks; it
does not consume an evaluation or mission capability-call reservation.
The shared doctor --connect command checks the selected providers without
invoking the workflow:
mix ptc doctor examples/kernel-tutorial/02-deepseek-extract/ptc.json \
--host-config examples/kernel-tutorial/ptc-host.json \
--connectThe sealed outcome reports one closed check row per required local,
credential, authorization, and connectivity operation, plus whether provider
activity occurred. It exposes no endpoint, command, path, credential, OAuth
authority, or secret-derived identifier. This operation is distinct from run;
the removed run --check route is not an alias or hidden compatibility path.
readiness is ready only when the active operation completed. An attributable
readiness diagnostic returns readiness: "failed", the failed row, and a
nonzero status while preserving the full diagnostic in a named command
envelope. Plain doctor reports readiness: "unverified" because its active rows
remain intentionally deferred.
- Manifests and capabilities — the application half: selecting installed aliases, narrowing them, and requesting limits.
- Building agents — how workflow policy uses the model capability these aliases install.
- Running and debugging — running a manifest against this document and reading what came out.
Exact field and failure contracts live in the PtcRunner.Kernel.HostConfig and
PtcRunner.Kernel.HostInstallation module documentation. The
Kernel maintainer guide describes provider ownership and
lifecycle.