Host-configuration reference
This is the complete contract for installed providers, credentials, transports, data classes, and outer limits.
The strict ptc-host.json file keeps them
separate from the application manifest:
ptc run MANIFEST --host-config ptc-host.jsonA manifest may select an installed alias and ask for less. It cannot add a provider,
change an endpoint or executable, supply a credential, or raise a ceiling. A
provider-bearing manifest requires --host-config; a manifest with no providers
does not.
For a checkout used repeatedly, store the host and optional environment-file references in the separate project configuration:
ptc run ptc-project.json
ptc doctor ptc-project.json --connectLoading validates bounded, path-confined JSON without reading credentials, resolving executables, starting processes, or contacting endpoints. Those actions happen later during preflight and acquisition.
A host schema diagnostic names the bounded rule that failed and includes a
safe JSON Pointer. Installation aliases and other caller-selected map keys are
rendered as *; unknown keys and rejected values are never copied into the
diagnostic. For example, an excessive installation timeout is located at
/install/*/ceilings/timeout_ms and classified as a maximum rule failure.
Start with a small document
{
"credentials": {
"openrouter_key": {"env": "OPENROUTER_API_KEY"}
},
"install": {
"deepseek": {
"source": "llm",
"structured_output_mode": "unsupported",
"usage_guarantees": {"tokens": true, "cost_currency": "USD"},
"installation_revision": "deepseek-policy-v1",
"model": "openrouter:deepseek/deepseek-v4-flash",
"credential": "openrouter_key",
"cache": false
},
"workspace": {
"source": "mcp",
"installation_revision": "workspace-v1",
"transport": {
"type": "stdio",
"command": "node",
"args": ["server.js"]
},
"tools": {
"read_text_file": {"as": "workspace.read", "effect": "read"}
}
}
}
}Only install is required. The other top-level keys are $schema,
credentials, limits, and runtime; unknown and duplicate keys are
rejected. Use ptc docs schema-host
(priv/schemas/ptc-host-config.schema.json in the repository) for the complete
structural vocabulary, defaults, bounds, and editor completion. Runtime
validation remains authoritative for semantic checks.
Every installation needs a public, non-secret installation_revision. Change
it whenever installed behavior or authority changes, including model routing,
MCP mappings and effects, replay fixtures, snapshot policy, or adapter and
launcher builds.
installation_config_digest is computed evidence for that same declaration. It
hashes the normalized install.<alias> configuration after ordinary host
decoding, with a distinct TJCS domain from application identity. Set-valued
declaration fields such as accepts_data and OAuth redirect_uris are ordered
canonically before hashing, so reversing them is not configuration drift;
ordered lists such as transport args keep their written sequence. Matching
digests mean the same declared installation was selected. They do not prove
that a local process, remote endpoint, credential, filesystem path, or server
still grants the same effective authority.
The digest is a sibling of application_content_digest and
effective_application_digest. Host configuration stays out of those
application hashes. ptc validate prints installation_config_digests for
aliases selected by the effective application, without contacting providers.
The same selected map is recorded on run-started, and each connector snapshot
carries the singular digest used while loading the host document.
Documented meaning by source:
- MCP stdio: the same normalized command and configuration declaration, not the resolved executable, working-directory target, symlink target, ambient environment, or server-enforced scope.
- MCP streamable HTTP: the same declared endpoint, authentication policy, tool mappings, and limits; not the server implementation behind the URL.
- LLM: the same declared model, parameters, limits, and credential binding name; not the secret value, account permissions, or provider behavior.
- Snapshot, replay, and other file-backed sources: the same declared path and provider configuration; not the current file contents unless a separate content hash attests them.
Paths are hashed as written after ordinary configuration normalization. The
digest does not expand them against the current directory, resolve symlinks, or
bake machine-local real paths. Changing installation_revision alone leaves
the digest stable; changing the declaration without bumping the revision still
changes the digest. Rotating a credential value does not change it; renaming
the binding does. Compare identifiers, not confidentiality: the digest never
includes secrets.
Declare credentials once
Credentials have exactly one source:
"credentials": {
"openrouter_key": {"env": "OPENROUTER_API_KEY"},
"vendor_token": {"file": "secrets/vendor.token"},
"development_key": {"literal": "not-a-real-key"}
}envreads a portable environment variable during acquisition.filereads at most 64 KiB. Relative paths stay beneath the host document's directory; absolute paths are canonicalized.literalembeds the secret. Reserve it for local development or documents rendered by a secret manager.
Credentials are resolved once and passed explicitly to the provider. A
missing, empty, or unreadable value fails with credential_unavailable; there
is no ambient provider-specific fallback. Never put credentials in a manifest,
PTC-Lisp, traces, or committed files.
With doctor --connect, an LLM endpoint that answers by rejecting the supplied
credential fails the provider's credentials check with
authentication_rejected. Its connectivity check passes because the HTTP
response proves the endpoint was reached. Transport failures, timeouts, and
invalid or unavailable provider responses continue to fail connectivity.
Surrounding whitespace is not part of a secret and is trimmed from every
source, so gh auth token > vendor.token and an editor that adds a trailing
newline both work. Interior structure is preserved: a PEM block or a JSON
service-account key is one credential, and transport.env hands it to the
child process whole. An HTTP header cannot carry a newline, so a credential
bound to transport.auth that still holds one after trimming reports
authentication_rejected — the same class the endpoint's own refusal
reports — rather than an internal fault.
ptc and mix ptc accept --env-file FILE on run, active doctor, and
manifest-backed repl. When any selected installation binds an env
credential — an LLM through credential, or an MCP transport through
transport.env or transport.auth — the frontend loads that exact file before
provider activity; it never searches for one. When loaded, every key assigned
in the file overrides its process value, including when the assignment is
empty; keys omitted from the file retain their process values. Embedded hosts
load no dotenv file implicitly.
Choose a provider source
The source set and placement are closed:
source | Purpose | Environment |
|---|---|---|
llm | Live language model | Workflow |
llm_replay | Frozen model responses | Workflow |
mcp | External tool server | Mission |
ptc_trace_snapshot | Trace queries | Mission |
ptc_private_trace_snapshot | Trace queries joined with authorized private records | Mission |
ptc_inspection_snapshot | Private inspection queries | Mission |
Selecting an alias into the wrong environment fails with
provider_destination_denied. This keeps model authority out of
model-authored mission code.
Live models
A live installation fixes the full model selector, credential, cache policy,
optional request parameters, and request/response ceilings. ceilings.max_calls
optionally caps how many times that alias may be invoked. Omitted, it defaults
to the catalog installed default for workflow_capability_calls_per_name
(2048), independent of the host limits block; a larger value could never
bind and is refused at load. The application may narrow it
with config.max_calls; asking above the host ceiling is refused rather than
clamped. When that selected cap is at or above the run's per-name
llm-request budget, the public per-name quota still binds first.
ceilings.request_timeout_ms is the selected installation's whole-call
deadline. Omitted, it defaults to 120000 ms, clamped by the host
limits.llm_request_timeout_ms ceiling (also 120000 unless the host document
widens or narrows it). The accepted range is 100 through that host ceiling.
Applications may only narrow limits.llm_request_timeout_ms; they cannot raise
the installation ceiling. Changing ceilings.request_timeout_ms requires a new
installation_revision. Dispatcher samples one absolute deadline immediately
before admission and enforces it through the provider worker and structured
output validation. When that LLM clock wins over the enclosing run or workflow
clocks, the public result is retryable timeout/llm_request_timeout. Replay
installations do not carry this deadline. The HTTP receive and connection-pool
timeouts follow the remaining deadline; they do not impose a separate 120-second
cap. An explicitly shorter transport timeout still applies.
"deepseek": {
"source": "llm",
"structured_output_mode": "unsupported",
"usage_guarantees": {"tokens": true, "cost_currency": "USD"},
"installation_revision": "deepseek-policy-v1",
"model": "openrouter:deepseek/deepseek-v4-flash",
"credential": "openrouter_key",
"cache": false,
"params": {
"temperature": 0.2,
"seed": 42,
"max_tokens": 4096,
"top_p": 0.9,
"presence_penalty": 0.0,
"frequency_penalty": 0.0,
"reasoning_effort": "medium"
}
}params is a closed installation-owned map. Except for max_tokens, omitted
fields use the selected adapter/model default; an llm/request cannot add or
replace them. The Kernel always supplies its effective output-token limit as
max_tokens, bounded by an installed value when one is present.
| Field | Accepted value |
|---|---|
temperature | number from 0.0 through 2.0 |
seed | integer from 0 through 2147483647 |
max_tokens | integer from 1 through 1000000 |
top_p | number greater than 0.0 and at most 1.0 |
presence_penalty | number from -2.0 through 2.0 |
frequency_penalty | number from -2.0 through 2.0 |
reasoning_effort | none, minimal, low, medium, or high |
These normalized controls are part of installation identity and the
installation configuration digest. Change installation_revision whenever
they change. Provider option maps, stop payloads, reasoning token budgets, and
the xhigh and max reasoning levels are not admitted.
Set params.max_tokens explicitly when the installation needs a narrower
output budget than the effective Kernel limit. The tutorial keeps the value
explicit so changing only its model selector retains the same request contract.
The built-in adapter prepares the selected model once before constructing its
requester. A selector absent from the bundled model catalog remains usable when
ReqLLM supports its provider unless llm_cost_microusd requires reservation
rates the catalog cannot supply. PtcRunner emits one model_uncataloged warning
for that requester or refusal. Catalog metadata such as pricing, limits, token
estimation, and capability detection may then be incomplete; the warning alone
does not mean the provider request is known to fail. Run envelopes V5 publish
the same fact in their closed warnings array, and canonical run-started
metadata retains it for trace consumers. Failed plain-doctor envelopes publish
the same locally derived warning for each affected provider check without
claiming provider activity; stderr remains the human presentation.
Preparation seals the exact controls into the target and requires the adapter
to attest the same canonical map. The built-in adapter uses ReqLLM's strict
unsupported-option policy and also refuses known lossy routes during local
preflight, before credential loading. For example, Anthropic cannot combine
temperature with top_p or accept OpenAI-style presence/frequency penalties;
several provider families translate reasoning effort to a fixed level or token
budget; and Ollama's direct route admits temperature, seed, and top_p but
not the two penalties or reasoning effort. The direct openai-compat: route
transmits the complete admitted set. OpenRouter does the same for positive
seeds; seed zero is refused because ReqLLM's current option boundary cannot
encode it. A refused combination reports
local_preflight/model_contract_unsupported without publishing the option
value or contacting the provider.
Every live model installation must declare structured_output_mode as
json_schema, json_object, or unsupported. json_schema asks the provider
to enforce the request schema with its native schema mechanism and is refused
at prepare when the adapter can only fall back to a synthetic tool.
json_object asks the provider only for a JSON object, then the Kernel
decodes and validates it. It is refused at prepare unless the adapter can send
that provider's actual JSON-object control: OpenAI-style response_format
json_object on OpenRouter, OpenAI, Groq, Fireworks, xAI, Azure OpenAI, and
Vertex OpenAI-compatible MaaS. Anthropic, Bedrock, Google AI Studio, Vertex
Claude/Gemini, and Azure Claude have no such control and stay unsupported or
json_schema. Direct ollama: selectors refuse both structured modes.
Direct openai-compat: selectors support json_schema and refuse json_object.
For openai-compat:, the mode is your declaration that the server supports
OpenAI-style response_format with type: "json_schema", a json_schema name
of ptc_response, and strict: true. The request schema is sent unchanged;
server support for strict schemas varies, and the adapter cannot verify that
an arbitrary server enforces it. Returned objects are checked by the
llm/request request validator and, separately, by the chat decision backend.
A server that ignores response_format fails only when its answer does not
match the schema. Content must be exactly one JSON object: reasoning prefixes
such as <think> are rejected, so configure your server to separate reasoning
or disable thinking. Valid reported usage is retained for budget settlement
even when the content is rejected or missing, including empty or malformed
choices. Ordinary requests also reject missing or non-string text content;
reported usage still settles their budget. To use Ollama schema output, select
openai-compat:http://localhost:11434/v1|<model>.
For openai-compat:, credential may be omitted, including when the alias
backs a chat decision installation. No credential is resolved and no
Authorization header is sent in that case. Other live LLM routes require a
credential binding. Configured credentials are sent as
Authorization: Bearer <credential>; an unset configured credential still
fails acquisition.
OpenAI-compatible base URLs must use HTTP or HTTPS and contain a host, with no
userinfo, query, or fragment. Credentials require HTTPS except for localhost,
IPv4 loopback addresses (127.0.0.0/8), and IPv6 loopback (::1). Keyless HTTP
endpoints remain supported. Redirects are refused. Responses are limited to
4 MiB while streaming; error bodies retain at most 4 KiB. Invalid successful
responses return invalid_result.
unsupported refuses a request schema before dispatch.
Changing the mode requires a new installation_revision. A schema
together with a non-empty tools list is invalid. Success is a
structured_output object; encoded content is not duplicated.
Direct routes retain their budget rules: an openai-compat: installation can
serve a token budget with usage_guarantees.tokens: true, but cannot serve a
cost budget. Direct routes refuse every reservation_tariff at prepare.
Usage guarantees
Every live model installation also declares the exact closed
usage_guarantees object. tokens: true promises that every dispatched
successful call reports non-negative input and output counts;
cost_currency: "USD" additionally promises a total USD cost. false and
null declare those observations optional, not zero. Preparation seals and
attests the declaration, and a successful response or doctor connectivity
probe that omits promised usage fails as non-retryable
llm_usage_unavailable. Changing either guarantee requires a new
installation_revision. Token and cost guarantees are independent: a
cost-only installation retains an authoritative provider charge even when the
provider omits one or both token counts.
Provider cost numbers and decimal strings, including OpenRouter's reported
usage.cost, are converted immediately to
{"currency":"USD","microunits":N} by exact decimal parsing, with fractions
rounded upward to one millionth of a dollar. The bounded integer N is in
0..9_007_199_254_740_991; binary floating-point values are not retained as
accounting authority.
If the host enables llm_total_tokens or llm_cost_microusd, each live call
must obtain a bounded, request-specific reservation attestation from the
prepared adapter before admission, and every live installation must declare
usage_guarantees.tokens: true. A cost budget additionally requires
usage_guarantees.cost_currency: "USD" and an explicit
reservation_tariff: {"currency":"USD","id":"..."}. Cost reservations bind
to that prepared tariff identity, but its id does not supply model rates;
supported USD reservation pricing for the selected model must also be
available. Tariff details remain private. A token reservation must be at least
the request's authorized output-token ceiling.
Attestation performs no credential lookup or remote work. An absent, crashing,
timed-out, undersized, or otherwise malformed attestation refuses the call
before provider dispatch. Provider errors and
successful calls without valid promised usage conservatively charge the full
reservation and mark the affected ledger incomplete; authenticated usage above
the bound charges the actual value, marks overrun, and prevents later calls.
Replay installations are excluded from these operational ledgers. A reservation
that does not fit remains a recoverable limit_exceeded envelope until the
workflow aborts it; aborting the authenticated envelope reports
execution/runtime_limit_exceeded naming the refused reservation, distinct
from realized overspend.
Model selectors are provider-qualified strings. These are the provider paths PtcRunner configures and exercises directly:
| Prefix | Example selector | Credential binding normally backed by |
|---|---|---|
openrouter: | openrouter:deepseek/deepseek-v4-flash | OPENROUTER_API_KEY |
anthropic: | anthropic:claude-sonnet-4-6 | ANTHROPIC_API_KEY |
openai: | openai:gpt-5-mini | OPENAI_API_KEY |
google: | google:gemini-2.5-flash | GOOGLE_API_KEY |
groq: | groq:openai/gpt-oss-20b | GROQ_API_KEY |
amazon_bedrock: | amazon_bedrock:anthropic.claude-sonnet-4-5-20250929-v1:0 | AWS credentials |
The examples are release-catalog examples, not promises that a provider still
serves an alias. A requester emits model_uncataloged when its selector misses
the bundled catalog; ptc doctor PROJECT --connect --show-model-selectors
tests the selected provider and includes selectors that are safe to disclose.
Direct Anthropic selectors, Anthropic models through OpenRouter, and Claude
models on Bedrock support the adapter's prompt-cache policy when the
installation sets "cache": true.
Agent loops and other requests that give the model callable tools require a
model endpoint with tool-calling support. A model may work for an ordinary
completion while refusing that tool-bearing request; the run then reports
llm_tool_calling_unsupported rather than claiming the configured model is
missing.
The manifest selects only deepseek; it cannot change any field above. When
the adapter attests that the resolved model is safe public identity, provider
snapshots and model-grouped usage include it. Endpoint-bearing or otherwise
private targets remain absent, while alias/revision usage stays attributable.
Use source: "llm_replay" when responses must be deterministic. The
replay evaluation guide owns fixture authoring, the
network-free example, candidate materialization, and component overrides.
A fixture file is JSON Lines. Every line sets schema_version to 2, a
request_hash computed from the provider-neutral request, and exactly one of
response, an ordered responses list, or an ordered outcomes list.
A single provider failure uses a one-element outcomes list. Plain doctor and validate parse the selected file under
the installed ceilings without starting the provider, so a manifest and host
document that validate cannot fail on the fixture when run reaches it. A
missing, empty, malformed, duplicate, or oversized fixture set fails the
local provider check as fixtures_unreadable; the message names the rule the
file broke and, for a line-level rejection, the line number counted with
blank lines. Nothing the line contains is published. The other local checks
(an installed model's adapter, an MCP server's executable) stay out of
validate.
Each outcomes item is exactly {"response": {...}} or
{"error": {"kind": "timeout", "details": null, "retryable": true}}.
Error kinds come from PtcRunner.Kernel.LLMFailureCatalog.provider_kinds/0.
Details must be null or a string of at most 1,024 characters and 4,096 bytes;
longer details are rejected rather than truncated during replay. For example:
{"schema_version":2,"request_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","outcomes":[{"error":{"kind":"timeout","details":null,"retryable":true}}]}Keep the original tokens usage in each successful response. An ordered list
can reproduce a provider failure followed by a successful retry under one
request hash. It reproduces provider classification and reported usage, not
elapsed latency or Kernel deadline scheduling. A failed call without reported
cost remains unknown; replay must not invent zero cost for it.
Fixture matching is exact: changed messages, tools, schema, or provider-neutral
parameters produce another request_hash rather than silently consuming
unrelated evidence. That request includes every model-visible observation
accumulated during the run. Frozen MCP response content is therefore necessary
but not sufficient when opaque cursors, generated identifiers, timestamps, or
runtime diagnostics can vary between hosts. When recording a portable fixture,
keep those values stable and retain host-specific measurements only in
structured details that do not enter a later model request. A
structured-output fixture uses the public
structured_output object rather than encoded content. A miss is a provider
error with :kind :provider_error and :reason :not_found, and llm/request
returns that envelope as a value with
:status :error rather than failing the evaluation, so a workflow that wants a
miss to be fatal calls cap/unwrap! on the raw tool/llm-request envelope. The
run envelope records the miss in usage: that alias's successful_calls stays 0
while calls increments, and capability_refusals records
workflow/provider_error/not_found.
doctor --connect performs a real minimal completion for each selected live
model and may incur provider cost; the readiness report's usage field
attributes what each probe spent, on the rows a run reports. --show-model-selectors adds only safe
selectors; endpoint-bearing openai-compat: selectors remain hidden. ptc models reports the same model_selector field under the same rule, without a
flag and without reading the application.
Resolve local transport paths
Stdio path-shaped command values, cwd, and relative command arguments resolve
from the host document, not from PtcRunner's source checkout and not from the
shell's current directory. A bare command such as node is found on the
inherited PATH; an absolute command is used directly.
For an application in a separate repository, keep its MCP server bundle in
that repository (for example tools/files/server.js) and use a host-relative
path, or install the server executable at a stable absolute location. A
cross-repository ../ptc_runner/examples/... path is useful for local
experimentation but does not make the application independently cloneable.
The self-contained PtcRunner release does not include the repository's example MCP bundles. Copy or package any selected example server with the application, including any runtime that server requires.
MCP servers
An MCP installation fixes its stdio or streamable-HTTP transport and maps upstream tool names to stable public capability names with host-declared read/write effects. A manifest selects and narrows that mapping but cannot change its executable, endpoint, credentials, or effect declarations.
Connecting tools with MCP owns the complete task-shaped setup, including tool mapping, prompt-visible facades, transport bindings, OAuth authorization, immutable snapshot identity, and the runnable filesystem example.
Trace and inspection snapshots
Native snapshot installations expose bounded run-evidence navigation over immutable captures:
"history": {
"source": "ptc_private_trace_snapshot",
"installation_revision": "history-v1",
"directory": "traces"
},
"private-history": {
"source": "ptc_inspection_snapshot",
"installation_revision": "private-history-v1",
"directory": "inspection"
}Directories resolve against the host document and are captured once.
ptc_trace_snapshot reads ordinary traces.
ptc_private_trace_snapshot reads ordinary and .private.jsonl traces and
classifies the run as private_inspection. An inspection snapshot requires
exactly one of those trace sources so it can validate every private artifact
against the captured trace evidence.
Whole-directory trace snapshots admit filename-bound <run-id>.jsonl or
<run-id>.private.jsonl members containing exactly one matching run and trace
identity. Stable malformed, mismatched, split, or conflicting members are
isolated by connected identity component while disjoint valid runs remain
queryable. Selected namespace mutation rejects the complete capture; it never
installs a partial snapshot. Explicit single-file trace sources remain a
separate filename-agnostic aggregate contract.
Set a trace selection's manifest config to {"expose": false} when it exists
only as the inspection source's dependency. It still supplies the frozen trace
capture without creating a second analysis namespace. The
TraceLog and run-analysis reference
defines query
shapes and bounds; Kernel REPL shows them in use.
Keep data classes compatible
data_class says what an installation contributes; accepts_data says which
effective classes it may run beside. Both default to normal and
["normal"]:
"data_class": "normal",
"accepts_data": ["normal"]Assembly computes the strictest selected class and requires every provider to
accept it before any provider opens. Private trace and inspection sources fix
their class to private_inspection, and such a run is forced onto the private
event policy. This prevents a normal vendor connector from silently receiving
or running beside private inspection data.
Set installed ceilings
The optional limits object sets maximums the manifest may narrow:
"limits": {
"run_duration_ms": 86400000,
"workflow_timeout_ms": 86400000,
"subordinate_evaluations": 500,
"workflow_capability_calls": 1000,
"mission_capability_calls": 8000,
"normal_event_count": 20000
}Raising run_duration_ms alone rarely lengthens an agent loop. Check the
workflow timeout, model-call and mission-call quotas, subordinate evaluations,
parallel timeout, and event count/byte ceilings too. Source checks have their
own quota and do not execute code.
The historically named normal_event_count and normal_event_bytes ceilings
bound retained events for both normal and private traces. A private run that
reaches either ceiling keeps its trace and inspection artifacts and reports
execution/event_capture_limit_exceeded with the binding name and value.
install is required and may be empty. A limits-only host document raises
ceilings for an application that selects no providers:
{
"install": {},
"limits": {
"workflow_heap_words": 16000000
}
}Optional rows (llm_total_tokens, llm_cost_microusd,
workflow_loop_iterations, evaluation_loop_iterations) are disabled when
omitted. A positive host value enables the row, becomes the inherited manifest
default and installed ceiling, and may be narrowed by the application. The LLM
budget rows also require live-installation prerequisites; the loop-iteration
rows do not.
The four heap and concurrency rows (workflow_heap_words,
evaluation_heap_words, provider_heap_words, live_provider_tasks) and
llm_request_timeout_ms have no manifest headroom. Raising a heap or concurrency
row needs both the host ceiling and a matching manifest request. Raising the LLM
deadline needs three matching values: the host llm_request_timeout_ms, the
selected live installation's ceilings.request_timeout_ms, and the manifest
request. Every other application-narrowable row can be raised from the manifest
alone, up to its installed ceiling.
Four timeouts are host-only: provider_cleanup_timeout_ms,
local_preflight_timeout_ms, selection_validation_timeout_ms, and
doctor_connectivity_timeout_ms. A manifest cannot declare them.
live_provider_tasks remains a per-run callback bound; it neither sizes nor
describes aggregate hosted LLM capacity. An embedding host that shares a live
provider transport across runs must pass one supervised
ProviderCallAdmission owner through all corresponding
ProviderRuntimeServices. Command-owned one-shot VMs do not start that domain.
Replay, workflows that call no provider, direct LLM and embedding calls, and
callbacks that bypass the installed requester consume no admission slot and
must be bounded by their caller.
The generated Kernel limits reference is the complete table of names, meanings, units, scopes, defaults, accepted ranges, and identity participation. The host schema is generated from the same catalog.
Verify selected providers
Run active local, credential, authorization, and connectivity checks without invoking the workflow:
ptc init kernel-tutorial --example kernel-tutorial
ptc doctor kernel-tutorial/02-deepseek-extract.ptc-project.json --connectreadiness is not_applicable when there are no provider check rows, ready
when every provider row passes, and unverified when any provider row is
skipped. A failed check reports failed and exits nonzero. The report
does not expose endpoints, commands, paths, credentials, or OAuth authority.
Decision model installations
decision installs a generic HTTP backend, a JSON-schema chat backend, or the alpha OpenRouter Decisions backend. Its vendor-neutral capability
is decision-request, exposed by the shipped decision/request prelude.
Jev boolean answers contain measured probabilities; your workflow applies thresholds.
An optional boolean value carries a discrete answer: false is an answer,
while missing or null means absent. Providers never threshold probabilities
to invent this field.
Measured probabilities, distributions, and confidence may be null for a
backend that cannot measure them. The result records the served model ID.
Trace events and inspection model exchanges retain a valid response model ID
as served_model even when answer admission or a reservation overrun rejects
the call. The final result remains a permanent failure; rejected answers are
not published as successful results, and configured aliases are not substituted
for missing or invalid response identity.
Shared decision bounds
max_cost_per_call is a non-negative USD decimal string for every decision backend, including replay. 0.01 USD is recommended
for the OpenRouter backend. max_total_tokens_per_call is a positive integer;
8000 is recommended, and hosts sending large state inputs must raise it.
When the corresponding ceiling is enabled, each call reserves exactly these
bounds against llm_cost_microusd and llm_total_tokens, shared with chat
calls. Validated reported cost and tokens settle after the response. An
exceeded bound is still charged in full and fails permanently with
invalid_result, without retry. Usage that reports no valid value, or lacks
a valid value promised by usage_guarantees, fails permanently with
usage_unavailable, without retry. The call charges the full reservation,
marks the affected ledger incomplete, and retains neither answers nor
served_model. Other invalid usage fails with invalid_result and settles
the valid values. Decision
calls also count against max_active_provider_calls.
Request questions and answer policy
Discovery advertises named question maps with required non-empty instructions
and a type enum (boolean, choice, score), the answer fields and nullable
measurements, a non-empty model, and required non-negative integer
input_tokens and output_tokens in usage. Unknown question keys are refused;
answer and usage extension fields remain permitted.
The bounded schema profile leaves these checks to runtime validation: non-empty
questions and question IDs; criteria shapes and bounds per type (optional
boolean criteria, otherwise exactly true and false string descriptions;
one to 255 choice names mapped to string descriptions; two to ten ordered score
string descriptions); per-type required answer fields; answer IDs matching
question IDs; options and legends matching criteria; probability sums;
weighted-score consistency; and optional usage cost. Passing the discovery
schema alone does not establish a valid decision call.
Trusted workflow code can send all three question types in one call. Select a decision installation in the manifest before calling this prelude:
(decision/request
{"state" {"number" 2}
"questions"
{"positive" {"type" "boolean"
"instructions" "Is the number positive?"}
"sign" {"type" "choice"
"instructions" "Choose the sign of the number."
"criteria" {"negative" "Less than zero"
"zero" "Equal to zero"
"positive" "Greater than zero"}}
"magnitude" {"type" "score"
"instructions" "Rate the absolute magnitude of the number."
"criteria" ["Less than 1" "At least 1 and less than 10"
"At least 10"]}}})Choice criteria map option names to descriptions. Score criteria are ordered
from level zero upward. The response's answers map uses the same question
IDs: boolean answers carry probability and optional value, choice answers
carry choice, and score answers carry score. Measurements may be null;
check for a failure signal before reading answers.
Choose policy explicitly: use a measured probability when present and a
supplied value otherwise, or require measurement and abstain/escalate when
it is null. Test absence with nil? so false remains a real answer.
Unavailable measurement is not low confidence. Backend interchangeability
promises the contract, not identical answers or measurements; a workflow
requiring probabilities must handle their absence.
OpenRouter Decisions backend (alpha)
{
"credentials": {"decisions_key": {"env": "OPENROUTER_API_KEY"}},
"install": {
"decisions": {
"source": "decision",
"model": "typesafe/jev-1.13",
"credential": "decisions_key",
"routing": {"zdr": true, "data_collection": "deny", "allow_fallbacks": false},
"usage_guarantees": {"tokens": true, "cost_currency": "USD"},
"max_cost_per_call": {"currency": "USD", "amount": "0.01"},
"max_total_tokens_per_call": 8000,
"installation_revision": "decisions-v1"
}
}
}The closed installation requires model, a credential binding name,
usage_guarantees, installation_revision, and both per-call bounds.
Credentials are resolved from credentials; literal keys are refused.
Optional routing accepts only boolean zdr, data_collection (allow or
deny), and boolean allow_fallbacks, sent as the backend's provider field.
Optional reservation_tariff identifies a host policy; it does not compute a
reservation. ceilings, data_class, and accepts_data use the same fields
as live LLM installations. Decision sources belong in the workflow.
HTTP backend
Use backend: "http" to send measured decisions to a host-owned endpoint.
This closed variant requires source, backend, endpoint, model,
usage_guarantees, installation_revision, and both per-call bounds.
Optional fields are credential, allow_insecure_loopback, ceilings,
data_class, and accepts_data. Unknown fields are refused.
{
"source": "decision",
"backend": "http",
"endpoint": "http://127.0.0.1:8321/decisions",
"allow_insecure_loopback": true,
"model": "anyjev/qwen3-8b@cal-2026-09",
"usage_guarantees": {"tokens": true, "cost_currency": null},
"max_cost_per_call": {"currency": "USD", "amount": "0"},
"max_total_tokens_per_call": 8000,
"ceilings": {"request_timeout_ms": 45000},
"installation_revision": "local-decisions-v1"
}HTTPS endpoints are allowed anywhere. Plain HTTP requires
allow_insecure_loopback: true and a literal 127.0.0.1 or ::1 address;
localhost is refused. HTTPS must omit the allowance or set it to false.
Userinfo, fragments, whitespace and control characters are refused. Optional
credential names a host credential binding and sends a Bearer header; it
requires HTTPS. Workflow code cannot choose the endpoint.
The server receives a JSON POST with model, state, and questions:
{"model":"local-v1","state":{"number":2},"questions":{"positive":{"type":"boolean","instructions":"Is the number positive?"}}}A small offline fixture server can answer that request with:
{"model":"local-v1@cal-1","answers":{"positive":{"type":"boolean","probability":0.9,"confidence":0.8}},"usage":{"input_tokens":20,"output_tokens":10,"cost":0}}Boolean wire types stay boolean. The response follows the public decision
contract and can be saved as a decision_replay fixture response; request
hashes use the same provider-neutral state and questions. The recording
fixture in test/support/decision_http_fixture.ex demonstrates a minimal
local server. Invalid answers fail permanently while valid usage and served
model identity still settle. Served identifiers may differ from declared
selectors; the mismatch is recorded without rejecting it. Acquisition identity
contains the declared model and backend, excludes the endpoint, and changing
the endpoint changes the installation configuration digest.
Automatic HTTP retries and redirects are disabled for both HTTP and
OpenRouter decisions. Any 3xx response is a non-retryable protocol failure;
request state is never sent to a redirect target. The receive timeout is the
minimum of ceilings.request_timeout_ms, limits.llm_request_timeout_ms, and
the remaining run deadline. Calls occupy the shared provider admission slot.
A zero cost bound is a host declaration. With the cost ledger enabled,
cost_currency: null is refused at load; use "USD" and report cost. Reported
cost: 0 settles zero with complete accounting. A response without cost
fails with usage_unavailable; it charges the zero reservation and marks the
ledger incomplete, so the recorded amount may be below real spend. Positive reported cost under a zero bound is preserved
and charged, and the call fails permanently with invalid_result without
retry. With the cost ledger disabled, the cost bound is unused. Token bounds
remain positive.
The runtime constrains only the configured destination. A local server can
forward data elsewhere; the runtime cannot establish the server's data-handling
behavior. doctor, including doctor --connect, does not check connectivity
for this backend. Data policy and workflow-only placement apply unchanged.
JSON-schema chat backend
A decision installation with backend: "chat" names an existing llm
installation through llm. Decode refuses missing aliases, other sources,
and LLM modes other than json_schema.
{
"source": "decision",
"backend": "chat",
"llm": "structured-chat",
"installation_revision": "chat-decisions-v1",
"max_cost_per_call": {"currency": "USD", "amount": "0.01"},
"max_total_tokens_per_call": 8000
}This closed variant requires the six fields above and accepts ceilings,
data_class, and accepts_data. The linked LLM owns its model, credential,
cache, parameters and usage guarantees; do not repeat those on the decision
installation. Data classes must match, and the decision's accepted data must
be a subset of the linked LLM's. Configuration identity includes the linked
LLM declaration. Only the decision alias needs selection in the manifest.
The bounded chat schema supports up to 128 named questions.
One request sends state and all question instructions and criteria with an
object schema: booleans, option-name enums, and integer score-level enums.
Boolean value, choice choice, and score score carry the discrete answers.
All probabilities, distributions and confidence are null. The answer model
identifies the adapter-attested public chat selector (private when hidden);
served-model identity for chat remains the LLM adapter's existing contract.
Usage keeps reported tokens and optional USD cost (normalized fixed-point
{"currency":"USD","microunits":200} is accepted as well as numeric cost).
Each call reserves and settles the decision bounds, occupies one shared admission slot, and records one decision model exchange. The underlying chat callback does not create another capability call, reservation or exchange.
Decision replay
decision_replay uses the same JSON Lines fixture format and request hashing
as llm_replay, with vendor-neutral decision responses. It requires fixtures,
installation_revision, max_cost_per_call, and max_total_tokens_per_call;
no credential or model selector is needed. Its optional ceilings are the
replay ceilings. Reservations and settlement use the same shared ledgers as
live decisions. Optional usage_guarantees uses the live installation's shape
and defaults to tokens and USD cost required. Set it to the corresponding live
guarantee when replaying responses without cost; an enabled cost ceiling still
requires USD usage. Replay calls use the host's llm_request_timeout_ms bound,
including through warm serving runtimes and shared provider admission.
Warm decision replay retains the fixture acquisition while each run has an
independent response cursor; repeated calls inside one run consume its sequence.
See examples/decision-refund-triage/ptc-project.json for a
complete offline project.
Next steps
- Manifests and capabilities selects and narrows these aliases.
- Building agents uses the installed model and mission capabilities.
- Running and debugging runs and inspects the application.
- Connecting tools with MCP installs external tools without putting transport authority in a manifest.
- Evaluating with replay fixes model responses for deterministic comparisons.