Application-manifest reference

Copy Markdown View Source

This is the complete contract for ptc.json.

A manifest declares one runnable PTC-Lisp application: code, input, provider selections, limits, events, and optional trace labels. Loading is strict, path-confined, and inert; it executes no workflow or provider callback.

The JSON Schema served as ptc docs schema-manifest (priv/schemas/ptc-application-manifest.schema.json in the repository) is the complete structural reference. Runtime loading additionally performs semantic checks and path-confined referenced-file handling. Manifest schema diagnostics include a safe JSON Pointer when one is available; a missing-required diagnostic points to the absent schema-declared property. The message also names the bounded JSON Schema rule (type, pattern, maximum, required, or another supported rule) without copying the rejected value or an unknown caller-authored key. Paths through named maps use * for the caller-selected name, and an unknown property stops at its schema-owned parent.

Start with one workflow

{
  "version": 1,
  "workflow": {
    "components": [
      {"id": "my.workflow", "path": "workflow.clj"}
    ],
    "entry": "my.workflow/run"
  },
  "input": {"value": {"question": "What should I process?"}}
}

The Kernel evaluates (my.workflow/run data/input). The entry must be a qualified public function that accepts the decoded input object. Unknown or duplicate keys, unsafe paths, malformed identifiers, and invalid JSON values are rejected before execution.

Compose components and libraries

A local component declares its source and sorted, unique dependencies:

{
  "id": "my.workflow",
  "path": "workflow.clj",
  "dependencies": ["my.helpers"]
}

Select an installed PTC-Lisp library by ID:

{"library": "agent.core"}

Library dependencies expand deterministically. Local and installed components compile into one immutable bundle. Missing dependencies, cycles, duplicate selections, undeclared cross-component calls, and ID collisions fail before the workflow runs. See Components and preludes for bundle rules.

Public PTC-Lisp functions may declare input and output signatures:

(ns tutorial.signatures "Small signed mission functions." {:visibility :prompt})

(defn double
  "Double one integer."
  {:signature "(value :int) -> :int"}
  [value]
  (* value 2))

The runtime validates inputs before the body and successful outputs afterward. A pre-body contract failure is safe to correct; a failure after capability activity is not automatically retried because repeating external effects may be unsafe. Run the credential-free 05-signature-feedback example to see the correction flow. Signature syntax defines the grammar.

Supply input and named missions

Input is either an inline JSON object or a manifest-relative JSON object file:

"input": {"value": {"task": "Summarize the report"}}
"input": {"path": "input.json"}

Named missions isolate subordinate code, data, provider grants, inventory, model context, and continuation state:

"missions": {
  "reader": {
    "components": [{"id": "app.reader", "path": "reader.clj"}],
    "data": {"mode": "source"},
    "providers": ["reader_workspace"]
  },
  "writer": {
    "components": [{"id": "app.writer", "path": "writer.clj"}],
    "data": {"mode": "destination"},
    "providers": ["writer_workspace"]
  }
}

Mission provider names refer to unique occurrences already selected under providers.mission; a mission can narrow authority but cannot introduce it. "default" is an ordinary declared name, not an implicit fallback. Definitions and *1/*2/*3 history never cross between missions.

Workflow code selects the mission explicitly with the kernel/eval*, kernel/check-source, or mission-introspection functions. The shipped agent.core loop uses "default" only when its own mission option is omitted, and that mission must exist. The named-mission-reader-writer example demonstrates distinct read and write grants.

All manifest references use portable, lowercase logical names and resolve beneath the canonical manifest directory. Absolute paths, traversal, devices, non-regular files, and symlink escape are rejected. These rules cover files the host loads. The manifest path itself is selected by the caller and its basename does not have to use the logical-name grammar. Model-visible files require a separately installed and selected provider such as ptc-fs-mcp.

Validate inputs and results

Use bounded manifest-relative JSON Schema contracts:

"contracts": {
  "input_schema": {"path": "task.schema.json"},
  "result_schema": {"path": "candidate.schema.json"}
}

The input contract covers manifest input and command-line input overrides. It is checked before preflight, credentials, processes, or discovery. The result contract is checked after execution and evidence capture, but before stdout or artifact publication. A mismatch returns input_contract_failed or result_contract_failed; non-object input returns input_invalid. When the safe failure projection identifies a non-root declared path, the command envelope and terminal diagnostic include its JSON Pointer. Terminal rendering escapes unusual contract-authored property names rather than emitting their control bytes. Missing-required failures name the first missing schema-declared property, including when the absent property is at the contract root.

The agent.main/run entry also gives a model-authored terminal candidate one ordinary correction turn when budget remains. Feedback is schema-derived and bounded: it may identify a safe declared path, missing required names, allowed names, and an undeclared-name count, but never an undeclared submitted name or value.

Contracts are closed object schemas by default. The profile supports common object, array, scalar, enum, const, and bound keywords, plus the asserted sha256 string format. It also supports one root discriminated oneOf for closed object branches. References, regexes, nested composition, union types, and general-purpose oneOf are rejected.

Two edges are worth knowing before you write one. enum and const must carry a sibling type, so {"enum": ["a", "b"]} is rejected and {"type": "string", "enum": ["a", "b"]} is accepted. The accepted bounds are minimum, maximum, minLength, maxLength, minItems, and maxItems; exclusiveMinimum and exclusiveMaximum are not in the profile.

The supported keyword profile above is deliberately closed. Unsupported schema composition is rejected during inert application loading rather than being partially interpreted at runtime. A contract_invalid rejection names the rule it broke and the JSON Pointer of the offending node inside the schema document — for example contract schema declares an unsupported "type" at /properties/sum/type in result.schema.json — so a misspelled type, a keyword outside the profile, and an unsatisfiable bound are told apart without bisecting the schema. Every reported segment is a key or index the submitted document carries, and the same pointer appears in the envelope's path.

--output PATH atomically publishes only the validated result value without replacing an existing file. Use --private-output for a private run; it publishes an owner-only artifact and keeps the value off stdout. See Running and debugging for destination requirements.

Select host-installed providers

A manifest selects public aliases; all credentials, endpoints, commands, mappings, effects, and outer ceilings stay in the host document:

"providers": {
  "workflow": [
    {"name": "deepseek", "config": {"default": true}},
    {"name": "frozen-model"}
  ],
  "mission": [
    {
      "name": "workspace",
      "config": {
        "allow": ["workspace.read", "workspace.write"],
        "model_visible": ["workspace.read"]
      }
    }
  ]
}

Multiple live or replay LLM aliases may be selected. At most one may be the default. A request selects an alias with its model field; omitting it works only when one alias is selected or a default is declared. Selection never falls back implicitly. Each selected alias may set config.max_calls to cap requests to that model; the host install's ceilings.max_calls is the outer ceiling (catalog default 2048 when omitted). An alias cap binds only when it is stricter than the public llm-request per-name budget.

For MCP, allow selects installed public names without changing their effects declared in ptc-host.json. It may be omitted only when every installed mapping is read-only. If any mapping is a write, an explicit non-empty list is required. model_visible may name any subset of the authorized allow names, including mappings whose host model_visible flag is false. Omitted, it defaults to the authorized names the host already marked visible. Visibility never grants or denies call authority.

Native trace and inspection aliases derive three navigation capabilities: runs, open, and read. open advertises the named collections and their filters; read returns one native bounded page. Public trace sources provide the activity collection and return evidence_unavailable for private collections. An inspection alias composes its required trace snapshot with authorized private records. Set the trace dependency's config to {"expose": false} when only the aggregate inspection namespace should be callable.

Snapshot acquisition is fail closed and immutable: malformed, duplicate, orphaned, or oversized evidence rejects the capture rather than exposing a partial catalog. Private inspection also requires every selected provider to accept the private_inspection data class before any directory opens.

Treat the workflow bundle and manifest as application code. Treat model-generated source, mission input, file content, and provider output as untrusted data. Host configuration documents the installed side of every provider source.

Narrow installed limits

Manifest limits are positive hard ceilings:

"limits": {
  "run_duration_ms": 30000,
  "workflow_capability_calls": 16,
  "workflow_capability_calls_per_name": 8,
  "mission_capability_calls": 32,
  "subordinate_evaluations": 8,
  "subordinate_source_checks": 8,
  "terminal_result_bytes": 250000
}

The host installs maximums. A manifest may request a lower or equal value; omission uses the normal runtime default capped by a lower installed ceiling. Limits also bound time, heaps, concurrency, retained definitions/history, source, capability values, and trace events.

Installed-only operational timeouts cannot appear in a manifest. The generated Kernel limits reference lists every name, meaning, unit, default, range, and scope.

Choose event privacy and labels

Trace events are sanitized and bounded:

"events": {"policy": "normal"}

"private" changes trace discovery and sink requirements. It does not create a prompt/response transcript. Exact model exchanges, generated programs, connector payloads, and prints require the separate host-selected inspection artifact; a manifest cannot enable or choose that destination.

Optional labels support trace grouping:

"labels": {
  "name": "report-agent",
  "model": "deepseek",
  "provider": "openrouter",
  "tags": {"mode": "agent", "environment": "staging"}
}

Labels do not affect execution, authority, prompts, results, or provider selection. name, model, and provider are fingerprinted in traces; tag keys and values come from a small fixed vocabulary. Labels are application claims, not authoritative provider identity. Use the provider snapshot and canonical usage for accounting, and never put prompts, results, credentials, paths, or arbitrary user text in labels.

Next steps

The generated application-manifest schema is the exact field reference; this guide documents the additional semantic and authority rules applied at load.