Agent library reference

Copy Markdown View Source

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.feedback, agent.native, agent.prompt, agent.retry, kernel, llm, result, and workflow.event.

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.

agent.core/run-value

(agent.core/run-value task config)

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

agent.core/run-outcome

(agent.core/run-outcome task config)

Represents model-attributable completion or failure as data:

{: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.

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.

ObservationEnvelope kindEnvelope reasonTypical policy
Transient transport or provider failureprovider-errortimeout, unavailable, rate-limited, or transport-error with retryable? trueretry the same alias
Permanent per-alias provider refusalprovider-errorauthentication-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 exhaustionlimit-exceededcapability-quota with details.limit max-callsanother alias may still run
Global LLM capability quotalimit-exceededcapability-quota with a workflow or mission capability-call limitno selected alias can run
Invalid or unknown aliasprotocol-errorunknown-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.

agent.core/run-result-value

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

Runs the same loop, validates every model-authored terminal candidate against the manifest result contract, and returns the contract-valid value to its PTC-Lisp caller. 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.

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.
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.
result_envelopetruebooleanChanges only agent.core/run; false returns the raw value.

For the four max_* options, an omitted or nil value selects the documented default. 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 and its inclusive range, and 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.

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: configured, adapter_default, model_output_limit, and remaining_context. Every tied constraint is retained. The remedy follows the binding: raise the host installation's params.max_tokens when 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 fixed built-ins and the bounded Java surface. None enumerate data values or direct tool capabilities. 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; agent.main first validates 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 validation unavailableFails the workflow as capability-unavailable/input-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 only the string cap and no explicit string ceiling was supplied, one bounded greedy pass may replace the preview with an exact rendering, but only when the complete compact representation fits every active ceiling. An incomplete retry is discarded in favor of the original shape-preserving preview.

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 agent.main 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.

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
cacheoptional boolean preference; host-fixed policy wins
modeloptional manifest installation alias

Successful responses contain content and may contain tool_calls, tokens, finish_reason, output_limit, and the router-authenticated model alias. 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 total_cost. When provider pricing is unavailable, total_cost is absent; a present zero 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.