← All documentation

Agent library reference

This reference defines the shipped agent.core and agent.main PTC-Lisp interfaces. For a guided workflow, see Building agents.

The libraries implement a provider-neutral loop: request one run_ptc_lisp action, evaluate its program in a named mission, and either finish or send bounded feedback for another turn. The Kernel continues to own execution, authority, effects, and resource limits.

Install the library

A local workflow component must declare agent.core as a dependency and the manifest must select the shipped library:

{
  "id": "my.agent",
  "path": "agent.clj",
  "dependencies": ["agent.core"]
},
{"library": "agent.core"}

The resolved closure includes agent.failure, agent.feedback, agent.machine, agent.native, agent.prompt, agent.retry, kernel, llm, result, and workflow.event. agent.failure is a generated :discoverable classifier over the existing bounded LLM envelope; it does not change public outcomes or fail-fast evidence. agent.machine is a :discoverable pure constructor/advance reducer for the shipped loop. Discoverable visibility is not an authority boundary; the exports remain callable and are not an application customization API.

Select {"library": "agent.main"} when the manifest entry is agent.main/run; its dependency closure includes agent.core.

Entry functions

All agent.core entries take a task string and configuration map.

agent.core/run

(agent.core/run task config)

Runs the loop as a terminal workflow entry. By default it completes the outer program with:

{:ok true :value application-value}

Set "result_envelope" to false to complete with the raw application value. The exact value this entry returns is the application result: a manifest result_schema describes that envelope by default, or the raw object when result_envelope is false. Invalid candidates receive bounded correction feedback while a turn remains; exhausting correction reports authenticated result_contract_failed for that same projected value.

agent.core/run-value

(agent.core/run-value task config)

Returns the application value to its PTC-Lisp caller without completing the outer workflow and without validating it against the manifest result contract. A subject failure or provider failure calls fail, so use this entry when the caller should continue only after a successful agent result.

By default the returned value is raw and unvalidated. Set return_contract to the name of one application-declared phase_return_schemas entry to constrain the value crossing back to workflow code. The selected contract is rendered in the initial prompt, and each invalid explicit return consumes a turn and receives bounded correction feedback while another turn remains. Exhausting correction propagates an authenticated phase_return_contract_failed error; it does not expose the rejected value.

agent.core/run-outcome

(agent.core/run-outcome task config)

Represents model-attributable completion or failure as data. The outcome remains workflow data; this entry does not validate against the manifest result contract:

{:status :returned :value application-value}

{:status :subject-failure
 :kind failure-kind
 :error bounded-error}

{:status :provider-failure
 :error bounded-llm-envelope
 :model resolved-alias}

Subject failures include model-program failure, exhausted turns, and a non-retryable generated-program error. Prompt, transcript, evaluation-admission, provider-callback crashes, and other host failures still fail the outer workflow. Typed LLM provider envelopes, named capability-quota refusals, and alias-resolution protocol errors return as :provider-failure so the caller can inspect the closed kind and reason. :model is the resolved installation alias after routing, including when the caller omitted model and a manifest default selected it. It is omitted only when no alias was resolved and none was requested.

run-outcome also defaults to raw, non-validating returns. Its optional top-level return_contract selects one named phase_return_schemas entry for this standalone loop. A valid return keeps the existing :returned shape. An invalid final-turn return becomes candidate-free :subject-failure data with kind :phase-return-contract-failed; an ordinary turn limit remains :turn-limit. Selection is explicit: PtcRunner does not infer a contract from the mission name, the registry contents, or the application result schema.

Set retain_programs to an integer from 1 through 128 to attach admitted generated programs on that returned outcome. Omitted or nil keeps the shapes above. When set, every returned outcome — including subject and provider failures after earlier evaluations — also includes :programs and :programs-omitted. Entries are ordered by one-based global turn and contain :turn, :mission, exact :source, and a bounded :execution summary. A continued execution includes its model-visible observation preview and whether that preview was truncated; a returned execution records only :returned because the outcome already carries its value. Failed executions retain their closed outcome, a bounded message, and the :kind and :reason classifiers of the evaluation or of the closed error envelope an explicit or capability failure carries as its value. A classifier is retained only when its name is 1 to 128 characters of letters, digits, ., _, :, /, or -, whether it arrives as a keyword or a string; anything else is omitted rather than disclosed, and the raw evaluation value is never retained. Each retained observation is independently capped at 2,048 characters.

Enabling retention explicitly discloses these model-visible observations to the calling workflow. A later reviewer may omit rolled-back failures when all granted effects are read-only, as in the DABStep example. A workflow with write or otherwise irreversible effects must not assume that a failed evaluation had no external effect.

Retention keeps the newest complete entries that fit both the requested count and a fixed 2,000,000 UTF-8-byte source ceiling; an individual admitted program larger than that ceiling is omitted in full. Protocol errors and source rejected before evaluation are not retained. Host and infrastructure failures that abort the outer workflow still have no outcome to annotate.

The envelope keys are PTC-Lisp keywords. The values of :status, :kind, and :reason are also keywords; the value of the :retryable? field is a boolean. The table spells keyword values with their leading : to preserve their in-workflow types. Comparing one of these values with its JSON-projected name string is false: use (= :limit_exceeded (get response :kind)), not (= "limit_exceeded" (get response :kind)).

:kind and :reason are facts. This entry does not add a runtime recovery: retry | choose-alternate | abort axis; the workflow chooses a disposition. Restarting with another alias starts another agent loop. It does not resume the previous transcript. Subject failures that require authenticated later propagation include an opaque :failure-token; pass the original outcome to fail-outcome rather than reconstructing it.

ObservationEnvelope :kind valueEnvelope :reason valueTypical policy
Transient transport or provider failure:provider_error:timeout, :unavailable, :rate_limited, or :transport_error with boolean :retryable? value trueretry the same alias
Whole-call live LLM deadline:timeout:llm_request_timeout with boolean :retryable? value trueretry the same alias or choose another
Permanent per-alias provider refusal:provider_error:authentication_failed, :payment_required, :denied, :not_found, :invalid_request, :tool_calling_unsupported, and other non-retryable provider reasonstry another alias or abort
Per-alias max_calls exhaustion:limit_exceeded:capability_quota with :details field :limit value :max_callsanother alias may still run
Global LLM capability quota:limit_exceeded:capability_quota with a workflow or mission capability-call limitno selected alias can run
Aggregate LLM token or cost budget:limit_exceeded:llm_total_tokens or :llm_cost_microusd with the refused reservationinspect remaining, raise the limit, or abort
Invalid or unknown alias:protocol_error:unknown_model_alias, :invalid_model_alias, or :model_alias_requiredcorrect the request; no provider attempt occurred

A spent per-alias max_calls is a named quota refusal, not a subject failure and not a transport error. run-value, run-result-value, run-phased-result-value, agent.core/run, and agent.main still fail-fast on every :provider-failure. When one of those fail-fast paths propagates an installation max_calls or a workflow or mission capability-call quota to the command boundary, it reports execution/capability_quota_exceeded while retaining the bounded quota message. An authenticated aggregate llm_total_tokens or llm_cost_microusd reservation refusal stays a recoverable capability value until the workflow aborts it; fail-fast entries then report execution/runtime_limit_exceeded naming the refused reservation, not workflow_failed. agent.core/run-outcome still returns that envelope as a provider-failure value. Do not infer the terminal cause from llm_budget.refused > 0: a later unrelated failure is not the earlier budget refusal.

agent.core/fail-outcome

(agent.core/fail-outcome outcome)

Use this after inspecting a run-outcome result and deciding to abort rather than retry or select another model. A :returned outcome is returned unchanged, including any retained programs, so callers can then read its :value. A subject or provider failure aborts with the same authenticated Kernel diagnostic as the fail-fast agent entries:

(let [outcome (agent.core/run-outcome task config)]
  ;; Inspect outcome here and retry or select another model when appropriate.
  (get (agent.core/fail-outcome outcome) :value))

The operation does not infer diagnostics from arbitrary nested maps. Only a canonical outcome carrying evidence retained by the Kernel can produce a specialized public diagnostic; forged maps and ordinary fail values remain private execution/explicit_failure errors.

agent.core/run-result-value

(agent.core/run-result-value task config)

Runs the same loop, validates every raw model-authored terminal candidate against the manifest result contract, and returns the contract-valid value to its PTC-Lisp caller. Use this when that raw value is itself the final contract-shaped application result. Invalid candidates receive bounded correction feedback while a turn remains. If correction is exhausted, the Kernel reports an authenticated result_contract_failed diagnostic with the effective turn count, the final schema constraint, and an attested contract path when one is available. That authenticated cause is retained when the call is composed through sequential or parallel higher-order functions such as map, pmap, and pcalls.

This is the composable result-contract entry used by agent.main/run.

agent.core/run-phased-result-value

(agent.core/run-phased-result-value task config)

Runs ordered mission phases while retaining the exact correlated assistant and tool transcript. Each phase requires mission and max_turns, and may provide an instruction, delivered when its phase begins: the first phase's instruction is appended to the initial task, and at each later boundary the host rebuilds the system prompt from the next mission's authority and appends that phase's instruction as a user message. A return in a non-final phase closes that phase and is retained as bounded observation evidence; only the final phase can satisfy the application result contract.

Set a phase's terminal_only to true when it may only make the final decision; only the final phase may declare it, because an earlier phase that exhausted without a terminal action would hand off to the next phase and void the obligation. PtcRunner parses each generated program before evaluation and accepts only one top-level return or fail form. A nonterminal program is not evaluated and receives bounded correction feedback while a phase turn remains; exhausting the phase this way reports the terminal-source-required turn-limit reason.

run-phased-result-value replaces mission and max_turns with a non-empty phases vector of at most eight maps. Each phase accepts mission (non-empty string), max_turns (integer, 1–128), optional instruction (non-empty string), and optional terminal_only (boolean). The sum of phase turn budgets must not exceed 128; an invalid vector fails with invalid-agent-config/invalid-phases before any provider request.

A non-final phase may also select an application-declared named handoff schema with return_contract. Its complete contract is shown only in that phase's system prompt. Invalid explicit returns receive bounded correction feedback while a local turn remains; an invalid final-turn return or exhaustion without an explicit return fails instead of transitioning. The final phase rejects return_contract and uses the optional application result schema.

Validating entries render the active application result schema in the final phase. Raw-result entries state that the exact returned value is validated. Envelope-producing agent.core/run states that the host validates the success envelope and tells the model to return only its value, avoiding double wrapping. run-value and run-outcome render no application result obligation. When either raw standalone entry selects return_contract, it renders only that named handoff contract with standalone-loop wording. A top-level selector is incompatible with run-result-value, run-phased-result-value, and agent.core/run; those entries reject it before provider or mission activity.

agent.main/run

Set the manifest entry to agent.main/run and supply:

{
  "task": "Complete the requested analysis.",
  "agent": {
    "max_turns": 8
  }
}

agent.main/run delegates to agent.core, validates each terminal candidate against the manifest result contract while a correction turn remains, and returns the raw contract-valid value. It is a domain-blind convenience entry; it does not add task-specific prompt policy.

Configuration

KeyDefaultAccepted valueEffect
modelselected installation defaultstringSelects a manifest-installed workflow model alias.
mission"default"non-empty stringSelects the mission whose API is rendered and whose environment evaluates programs.
return_contractomitteddeclared contract name or nilOn run-value and run-outcome, selects one named phase_return_schemas contract for standalone in-loop validation.
max_turns4integer, 1–128Bounds provider requests and generated programs in this loop.
max_program_chars64000integer, 1–1,000,000Bounds one run_ptc_lisp program string.
max_observation_chars2048integer, 1–65,536Bounds the untrusted structural preview and println body of one successful observation.
max_transcript_chars262144integer, 1–1,000,000Bounds the JSON-encoded prospective provider request.
consolidate_at_turns_remainingomittedinteger, 1–effective max_turnsAdds generic consolidation guidance at and below this remaining-turn count.
retain_programsomittedinteger, 1–128On run-outcome only, attaches admitted generated programs on the returned outcome.
result_envelopetruebooleanChanges only agent.core/run; false returns the raw value and validates that raw value against the result contract.

For the four max_* options, an omitted or nil value selects the documented default. retain_programs is run-outcome only: omitted or nil means no retention, and a present value is an integer from 1 through 128. An out-of-range integer or a non-integer fails with invalid_agent_config before any provider request or mission evaluation. The command diagnostic names the option, its inclusive range, and the entry it belongs to, plus either the rejected integer or the received type — never the original non-integer content. Signature validation still rejects unknown configuration keys. consolidate_at_turns_remaining fails the same way with invalid-agent-config/invalid-consolidation-threshold when set outside 1 to the effective max_turns.

Omitted or nil mission selects default. An empty, non-string, or unknown mission fails; the loop never falls back to a different environment.

Omitted or nil return_contract preserves raw standalone behavior. A malformed name fails as invalid-agent-config/invalid-return-contract; a valid but undeclared name fails as invalid-agent-config/unknown_phase_return_contract. Both are resolved before provider or mission activity. Caller-authored projections, schemas, and validation objects are ignored as authority and cannot enter the prompt or validator.

When one model installation is selected, model may be omitted. With several, omission uses the selection marked "default": true. Otherwise the request fails and lists the available aliases. A model value is an installation alias, never a raw provider model ID. An unknown or null alias does not fall back.

Agent options do not raise Kernel limits. Source size, subordinate evaluations, provider calls, memory, results, events, parallel work, and the run deadline remain bounded independently. See Application-manifest reference.

Turn and transcript protocol

The model receives exactly one function tool:

{
  "name": "run_ptc_lisp",
  "parameters": {
    "type": "object",
    "additionalProperties": false,
    "required": ["program"],
    "properties": {"program": {"type": "string"}}
  }
}

A valid action contains exactly one tool call with:

  • a non-empty string ID;
  • the name run_ptc_lisp;
  • exactly one program string argument;
  • a non-blank program within max_program_chars.

Assistant narration may accompany the tool call and is retained with it. Text without a call, several calls, a wrong name, malformed arguments, or an invalid program produces a protocol correction while a turn remains. The correction retains what the model produced: bounded assistant narration (at most 2000 characters), and, when the call was well-formed except for program size, the authentic assistant/tool pair. Malformed tool_calls are not replayed. Kernel guidance belongs in the user correction, never in an assistant turn.

The initial task and every retained feedback message state the number of turns remaining, including the next program. With one turn left, the message requires the next program to call return or fail. A configured consolidation threshold adds domain-blind synthesis guidance before the final turn.

If the loop spends its effective max_turns without returning, run-outcome returns bounded subject-failure data. Entries and callers that propagate that failure through run, run-value, or run-result-value report execution/turn_limit_exceeded and name the effective turn ceiling.

What the message recommends depends on how the final turn ended, because only some of these endings are answered by buying more turns:

ReasonFinal turn producedReported remedy
intermediate_resulta successful program that did not returnraise max_turns, or reduce the work per turn
evaluation_errora program that failedraise max_turns, or simplify the work per turn
protocol_errorno usable run_ptc_lisp callcheck tool-calling support and any configured max_tokens first

Provider-reported output truncation is terminal when it prevents a usable action. A response ending in length still executes a complete, admissible run_ptc_lisp call. Without one, the shipped non-streaming tool loop fails immediately as execution/model_output_truncated (exit 6) without spending a protocol-error count or another model turn. The provider subject names the selected router alias. When the adapter can authenticate the effective max_tokens request cap after provider request normalization, the diagnostic also names that cap and its bindings. It describes the cap as request metadata: a provider may silently enforce a lower ceiling. If ReqLLM rewrites, removes, or ambiguously resolves the cap, the response and terminal diagnostic omit the unprovable cap metadata while preserving the terminal truncation result.

The cap's bindings list is closed and canonically ordered. Live Kernel requests preserve application_limit for the effective limits.llm_request_output_tokens, installation_param for installation params.max_tokens, or both when their values tie. Direct adapter callers use configured; computed defaults use adapter_default, model_output_limit, and remaining_context. Every tied constraint is retained. The remedy follows the binding: raise the manifest limit (and its installed host ceiling if lower), the installation parameter, or both; increase the direct adapter option for configured; choose a model with a larger output/context limit when catalog metadata binds; or reduce the requested output or retained transcript.

The canonical failed run-stopped event retains the bounded agent_turns limit name, its value, and the same limit_reason, so trace consumers can present the same cause.

Before dispatch, the loop JSON-encodes the complete prospective request: system prompt, accumulated messages, tool schema, and optional model alias. A request larger than max_transcript_chars, or one that cannot be encoded, fails without calling the provider. Earlier assistant/tool pairs are never silently discarded to make the request fit. The provider may enforce a lower request limit.

Exceeding max_transcript_chars reports itself the way the turn ceiling does: execution/runtime_limit_exceeded naming the limit and its effective value, with the canonical failed run-stopped event retaining failure_kind: transcript-limit plus the bounded limit name and value. A request that cannot be encoded at all remains a workflow failure, because no ceiling was reached.

Prompt and mission API

agent.prompt/initial-state, agent.prompt/render, and agent.prompt/transition form the replaceable prompt-policy seam. Each must return the documented state or text shape; invalid results fail the workflow as prompt errors.

The default prompt renders one deterministic Available API for the selected mission. If any prompt-visible PTC-Lisp facade functions exist, raw tool/... entries are suppressed. Otherwise direct capabilities are shown. Function signatures, schema constraints and descriptions, effect metadata, and bounded documentation are rendered when available.

Top-level mission data keys are rendered as data/<name> values with their bounded structural types, but never their values. The inventory is the complete prompt-visible mission surface. dir and export-meta inspect visible attached prelude exports. apropos and doc cover those exports plus installed callable capabilities, fixed built-ins, and the bounded Java surface. They do not enumerate data values. At Kernel boundaries, doc can identify an exact indexed public export and its unattached owning shipped library. The index is not searched by apropos and grants no documentation or call authority. When the inventory is empty, the prompt says so explicitly instead of leaving a blank heading. The generic examples do not name data/input; an agent sees that reference only when the selected mission actually grants an input data key.

Numeric Kernel limits remain enforced but are not copied into the default prompt. Trusted workflow code can read the exact frozen structured inventory with kernel/mission-inventory.

Agent prompts must remain domain-blind: they may describe the language, available API, task, budgets, and correction policy, but not fixture values, benchmark domains, or expected answer patterns.

Evaluation outcomes

The loop interprets mission evaluation outcomes as follows:

OutcomeLoop behavior
(return value)Completes as :returned; validating entries (agent.core/run, run-result-value, run-phased-result-value, and agent.main) first check the result contract.
Ordinary valueSends a bounded success observation and continues if a turn remains.
(fail value)Becomes a subject failure, except for the proven read-only capability case below.
Retryable evaluation errorSends bounded correction feedback if a turn remains.
:busy or :limit_exceeded admissionFails the workflow as evaluation-unavailable; correcting source cannot clear a host condition.
Terminal provider-originated mission failureBecomes a model-program subject failure.
Host input or output validation unavailableFails the workflow as capability-unavailable/input-validation-unavailable or capability-unavailable/output-validation-unavailable.

An ordinary successful turn commits definitions and adds its native value to the bounded *1/*2/*3 history. A terminal return commits definitions but does not add the returned value. Failed turns preserve the previously committed definitions and history.

Feedback bounds and disclosure

Successful observations contain a heap-proportional value preview and chronological println output. Preview traversal has independent collection, depth, node, string, character, and UTF-8 byte ceilings; it does not first serialize the complete value. The default pass preserves sibling shape under a small per-string ceiling. When the shape pass reports truncation only from implicit ceilings, one bounded greedy pass raises the item and depth ceilings to larger internal traversal bounds and the node and string ceilings to the output budget. It replaces the preview only when the complete compact representation fits every active ceiling; an incomplete retry is discarded in favor of the original shape-preserving preview. Explicit traversal ceilings never trigger this adaptive pass.

Feedback distinguishes value-preview truncation from println output omitted while assembling the bounded observation. For an ordinary successful result, the exact retained value is available as *1, so feedback advises reusing it instead of repeating a capability call. An explicit return does not advance history; feedback for a non-final phased return therefore refers only to persisted definitions and never claims that the returned result is in *1. Omitted print output is not stored in *1. The earlier per-call max_print_length bound remains separate and carries its own visible ... (kept/total chars) marker.

Truncated previews continue to report the ceilings hit and sampled map keys (including bounded nested samples). The body is marked as untrusted and escapes its closing marker. Closures and other executable values cross only as inert display labels.

The preview is non-authoritative presentation work: a preview failure cannot turn a committed evaluation into a failure. A retained-memory or *1 history commit rejection instead rolls the candidate continuation back and reports actionable guidance. Programs with only read effects may be corrected and retried; after a write or unknown effect, the loop tells the model not to repeat the program because the external effect cannot be rolled back.

Heap and compile-heap feedback explicitly says that the failed program was rolled back, earlier definitions remain available, and a corrected program should filter, page, project, or reduce before collecting a large result.

Evaluation-error feedback contains a bounded outcome, error code, and safe message. A correctable capability error contains its public kind and reason. Input-schema feedback may name up to three schema-declared paths, violated constraints, and small declared numeric, length, or item bounds. It does not echo submitted values, enum or const literals, undeclared property names, opaque validator reasons, or provider details.

When a validating agent entry rejects a returned value, feedback may identify permitted and missing keys at retained closed-object paths, only the count of undeclared submitted keys, and small schema-declared numeric, length, or item bounds. Open objects report missing required keys without treating extension keys as invalid. Candidate values, tagged-union matches, enum literals, and const literals are omitted from model feedback. Result-contract diagnostics are capped at 32,768 characters, then the whole request is checked against max_transcript_chars.

The terminal exhaustion transition is fail-only and available only to the shipped agent.core prelude. It revalidates the final candidate against the host's frozen result contract and creates no additional candidate-bearing record. The candidate does not enter public errors, command envelopes, trace events, or ordinary logs. Existing authorized private inspection can still contain the model response and generated source that produced it.

An outer fail value is not copied into the public Kernel error or trace. Framework failure kinds remain readable; application-defined scalar kinds become stable fingerprints. Exact failure values, prompts, generated source, and capability payloads require an authorized private inspection artifact. Built-in LLM adapters retain only a bounded provider status and human-readable reason there; request bodies, raw response bodies, headers, and transport causes are not retained as provider-error details.

Verify a proposed result

agent.core/run-outcome accepts an optional workflow-owned verify function. It runs after a candidate satisfies the optional return_contract, before the loop reports a returned result. It receives {:status :candidate :value value} plus :programs and :programs-omitted when retention is enabled.

(agent.core/run-outcome task
  {"mission" "analysis" "max_turns" 4 "max_corrections" 1
   "verify" (fn [candidate]
              (let [checked (kernel/eval-with "verification"
                              (program (return (checks/verify data/params)))
                              {"candidate" (get candidate :value)})]
                (if (= :returned (get checked "outcome"))
                  (get checked "value")
                  (fail {:kind :verification-unavailable}))))})

Here checks/verify is an application component in a separately granted mission; selecting the callback grants the proposing agent no new tools. The callback can also perform a deterministic comparison directly in workflow code, as DABStep does. The application supplies the meaning of correctness.

The report uses string keys:

statusBehavior
acceptedReturn the candidate as :status :returned.
rejectedFeed back the discrepancy and allow a correction if safe and budgeted.
unresolvedStop immediately without accepting the candidate.

Rejected and unresolved reports require nonblank feedback of at most 2,048 characters. Optional evidence is returned to the workflow, not appended to the model conversation. The last report is attached as :verification. Unresolved, unsafe, or exhausted verification returns :subject-failure with :kind :verification-failed, no top-level :value, and reason :unresolved, :unsafe-effects, or :correction-exhausted.

max_corrections defaults to 1 and accepts 0 through 128. It does not add turns: each correction uses the original max_turns, transcript, mission state, and run-wide resource budgets. A successful evaluation's state remains committed when its candidate is rejected; verification is not rollback. Any write or unknown effect in the loop prevents further verification-driven correction. Accepted candidates may have effects; acceptance does not prove that an external action happened unless the verifier checks it.

The callback runs as trusted workflow code under existing workflow and run deadlines. Keep verification read-only. The runtime's effect guard covers agent evaluations, not arbitrary effects a workflow callback chooses to make. Callback errors, invalid reports, and resource failures fail the workflow; they are not treated as rejection. Only standalone run-outcome supports verification; phased and fail-fast entries reject this configuration.

An agent-verification annotation records only accepted, rejected, or unresolved. Candidate values and feedback remain in workflow data and, when enabled, private inspection. These annotations describe workflow decisions, not runtime attestations of business correctness.

Retry and effect safety

The loop retries only while another turn remains. Retryability also depends on whether repeating a program could repeat an effect the Kernel cannot undo:

Observed activity before failureRetry policy
No capability callCorrectable.
Only declared read capabilitiesCorrectable.
Only reserved mission runtime routesCorrectable.
Any declared write capabilityDo not repeat.
Any capability with missing effect metadataDo not repeat.

Runtime routes such as capability discovery and usage queries are treated as safe reads. A resource kill after reads remains correctable because it commits no Lisp state and repeats no external mutation.

Read failures preserve their provider retry classification. Once a write may have been dispatched, every non-success is non-retryable and may report mutation_state: indeterminate; cancellation does not prove rollback. Provider callback crashes and process deaths are unclassified and therefore non-retryable. MCP input_required exchanges are terminal even on a read route.

An explicit fail is terminal by default. It becomes correctable only when the evaluator proves all of the following:

  1. the failed value is the exact last recorded capability result, preserved through a direct call, simple lexical binding or helper forwarding, or cap/unwrap!;
  2. the capability failure is marked retryable by the evaluator;
  3. every observed capability effect was declared read.

Rebuilding, copying, or destructuring an equal error-shaped map does not qualify. Neither does an error-shaped value after an unrelated read.

After an unsafe evaluation failure, the loop never repeats that program. If a turn remains, it offers one closing turn that asks the model to decide from evidence already gathered. A second unsafe failure, or no remaining turn, becomes a non-retryable-evaluation subject failure.

Provider-neutral requests

agent.core calls the llm/request wrapper. Its request supports:

KeyShape
systemoptional string
messagesrole/content message array
toolsoptional provider-neutral tool definitions
schemaoptional JSON Schema object for structured output
cacheoptional boolean preference; host-fixed policy wins
modeloptional manifest installation alias

A request may include schema or a non-empty tools list, not both. Successful text responses contain content and may contain tool_calls, tokens, finish_reason, output_limit, and the router-authenticated model alias. A schema request succeeds only as structured_output plus optional tokens and model; there is no encoded content duplicate. Normalized tool calls use id, name, and args. Invalid provider arguments may include a bounded args_error classification. Token usage may include input, output, cache_creation, cache_read, and fixed-point total_cost as a USD currency and integer microunits object. When provider pricing is unavailable, total_cost is absent; a present zero-microunit object is a measured zero-cost response.

For non-streaming ReqLLM tool calls, finish_reason uses ReqLLM's normalized vocabulary rather than raw provider stop metadata. A length response carries the effective request output_limit when the adapter can authenticate it after provider normalization; it never claims the provider's actual internal ceiling. Text, structured-output, and streaming responses do not inherit the shipped agent loop's fail-fast truncation policy.

Provider failures remain bounded capability error envelopes so workflow policy can decide whether to fail or recover. After alias resolution, those envelopes include the public model installation alias. Credentials, endpoints, sampling, timeouts, and byte ceilings remain host-owned and are never supplied through the PTC-Lisp configuration map.

Built-in adapters classify expected HTTP and transport failures into the closed ProviderError reasons: retryable timeout, unavailable, rate-limited, and transport-error; permanent authentication-failed, payment-required, denied, not-found, invalid-request, and tool-calling-unsupported. Unclassified requester failures become retryable unavailable. The Dispatcher preserves that classification in the Lisp envelope.

Concurrency and admission

A run has one subordinate-evaluation lease. Agent loops under pcalls can overlap provider calls, then their evaluations queue in FIFO order. The wait is bounded by evaluation_admission_timeout_ms and the run deadline. Exhausted subordinate_evaluations or an expired admission wait fails the workflow without spending another agent turn or model call. When subordinate_evaluations is exhausted, the command error names that limit and its configured ceiling and recommends either raising the applicable manifest or host ceiling, or reducing total subordinate evaluations or agent turns.

Set enough parallel_timeout_ms headroom for the complete parallel phase. The Kernel limit reference, not the agent configuration, defines these ceilings.

Use pcalls for a fixed heterogeneous fan-out of zero-arity tasks and pmap for a runtime-sized homogeneous collection. Both resolve as callable values, so a workflow may store them, pass them through a helper, or invoke them with apply; direct and indirect calls consume the same worker budget and deadline.