Skip to content

20. Agents

Context

A model call is not an Agent. An Agent is one typed cognitive step whose instructions and result contract remain stable while the selected model, generation settings, tools, and authority arrive for the particular step. This permits the Vessel to change intelligence without relocating policy, identity, or durable execution into an adapter.

Requirements

  • An Agent must be distinct from a model, Graph, Pattern, Persona, Posture, and Lens.
  • Its stable recipe, available capability, fresh dependencies, output contract, and result limits must be inspectable and separately owned.
  • Tools and delegation must not acquire authority through prompt text, construction, or a model result; durable truth remains at domain, Graph, identity, consent, and persistence boundaries.

Considered Options

Option Decision Why
Put model, credentials, tools, and workflow in one prompt-defined actor Rejected It erases capability admission, tool policy, durability, and attribution boundaries.
Make Pydantic AI the dispatcher and workflow ledger Rejected It owns the inner model/tool loop, not LychD's control plane.
Typed spec with late-bound capability Selected It keeps cognitive construction stable and binds per-step authority at the Dispatcher seam.

Decision

LychD uses in-process Pydantic AI, at lockfile-pinned pydantic-ai-slim==2.55.0. The State owns present cognitive adapter evidence; native Graph integration preserves LychD's serial checkpoint and wait-owner contract. The adapter sets end_strategy="early" explicitly: a valid final output does not execute sibling function tools. The v2 SDK's graceful default would change this effect boundary. Any later workflow that needs sibling completion must declare and verify that policy separately.

An Agent joins immutable AgentSpec, process-local AgentForge, a step-scoped model-shaped CapabilityGrant, fresh LychDDeps, and a declared output union. This is the delivered model path; ADR 22's general grant union does not turn every service into an Agent dependency. The boundaries are deliberate:

Concern Owner
Instructions, output types, retry ceiling, optional declared toolsets AgentSpec
Construction and process-local cache AgentForge
Model, settings, runtime toolsets, capability lease Dispatcher and CapabilityGrant
Workflow order, persistence, suspension, recovery Graph
Permission to act Security, Sigil, grant, and tool handler
Durable identity and attribution Mirror
Consequential human authority HitL

Pydantic AI owns the inner loop, never a second dispatcher, identity system, ledger, or control plane.

The Agent specification

AgentSpec is frozen and hashable, with name, instructions_key, instructions, output_types, optional toolset_names, writes, retries, and default max_tokens. AgentForge.agent_for(spec) selects a registered builder and caches by the whole specification; changing any field makes a distinct entry, while an unknown name or toolset fails construction.

build_agent binds no model. It can bind named construction-time toolsets, but omits a mutating set when writes is false. Absence is the first gate; a present tool still checks live Sigil, grant, input, and domain policy at the handler.

The First One

The delivered Bridge Agent, THE_FIRST_ONE_SPEC, returns BridgeReply | DeferredToolRequests, has no construction-time toolsets, and cannot request transition of the substrate leased to it. Its stable instructions receive the Context Orchestrator's floor at run time. BridgeReply is text plus FragmentCalls: each names a Vessel-owned registry entry and parameters, never markup, and the registry validates it before projection. Bottleneck represents contradiction, missing input, policy block, or unavailable dependency, but while Bridge can settle it as workflow state, it is not in The First One's current output union.

Late-bound capability

For each Bridge inference step, the current workflow asks the Dispatcher for a v1 chat grant containing capability specification and state, step/run lease, resolved generation profile, a required hydrated Pydantic AI model, and agent-loop toolsets only when the capability explicitly declares supports_tools = true. The grant exposes neither its Animator nor Connector. Bridge passes grant.model through a request-scoped Context capacity wrapper, along with grant.model_settings() and grant.toolsets, to agent.run_stream_events. Bridge owns the event stream's async scope so toolsets close on consumer failure before the Dispatcher closes the model-provider scope and releases its lease. Agent specifications therefore name neither endpoint nor credential; the specification needs no separate model/tool-provider pair.

One spec can run with any admitted model meeting the adapter contract. A foreign framework is not an in-process Agent by analogy: it remains a typed delegated runtime or Animator boundary until a later amendment to the owning Covenant admits a versioned interface.

Delegated runtimes and remote attempts

A coding agent is a delegated runtime when an external process owns an iterative planner/tool loop and receives an exact repository or workspace projection under an AgentJob read, candidate, or verify profile. One native Agent step using a remote model is instead a Portal- backed Agent call; a sovereign node accepting a public task is A2A. These classifications follow labor and authority, not the model brand. A coding agent may obtain cognition through a local capability or its job-scoped Provider Gate, but every child remote payload remains a separate Security-admitted Portal attempt. The outer job never conveys provider credentials, a general network route, authoritative checkout, or promotion authority.

The accepted remote-attempt law also applies inside a native Agent loop: validation repair, tool-result follow- up, changed history, or another model round creates another canonical payload and therefore another Portal road decision/EgressDecision. One grant or first-round approval never authorizes the whole loop's future payloads.

At that boundary the trusted Portal adapter or job-scoped Provider Gate freezes the canonical child body and presents it to Spellweaver's road-decision service under the pinned parent placement policy. It must obtain a fresh road decision, a consumer-specific Cut when transformation is required, and the byte-time EgressDecision before sending. A native model loop or Coffin runtime cannot mint, cache, widen, or reuse them; inability to obtain the full chain refuses that round.

Provider compatibility under the pinned adapter

Provider Portals route their declared alias through the matching Pydantic AI provider or model-profile resolver, including unsupported-setting filtering. OpenRouter, LiteLLM, and Ollama use their provider resolvers; Google's OpenAI-shaped endpoint uses the Google model profile over the OpenAI transport. Generic OpenAI-compatible endpoints and local runtimes instead select LychD's conservative inline-schema, non-strict-tool profile. That local profile replaces resolved provider defaults: a local alias such as o3 does not inherit OpenAI reasoning policy or a provider-derived context window. Selecting Responses does not also admit its optional input-token-counting endpoint: the generic local profile disables that endpoint. Named provider resolution remains on the provider's model_profile(model_name) contract, separate from v2's resolved-profile transformation callback. OpenRouter ids must retain their provider/model namespace. Under the pinned adapter, Google, LiteLLM, and Ollama aliases are Chat-only; Responses is admitted only for OpenAI, OpenRouter, or an explicitly compatible generic endpoint. This does not claim a native Gemini transport or providers outside the registered aliases.

An explicitly admitted qwen3.8-non-thinking generation policy reaches the call through the immutable capability grant. The grant requires its hydrated Chat model and compiles only extra_body.chat_template_kwargs.enable_thinking = false; Bridge uses the existing settings handoff. It does not translate this dialect into generic SDK thinking or reasoning_effort, mutate model defaults, expose arbitrary request-body fields or create another provider route. The policy bounds a declared request mode, not separate hard reasoning/final token quotas, semantic quality or an entire Agent loop's budget. Any applicable byte-time egress decision still covers the final child payload.

Run dependencies

Every step receives fresh, frozen LychDDeps:

Field Contract
sigil Acting identity and scopes.
grant Capability grant for this step.
dispatcher Narrow grant-service port for tools.
orchestrator Narrow lifecycle-transition port.
context Context Orchestrator for the assembled floor.
run_id, step_id Correlation identities.
priority Admitted run priority.

It carries no database session, raw settings, or secret. The control plane consumes credentials to construct connectors; persistence and domain services remain narrow ports rather than cognitive smuggling.

Typed results and truth

Pydantic validation establishes shape, not the truth of prose, an effect's validity, claim identity, or permission to publish or promote. Deterministic validators, domain handlers, Sigil, grant, receipts, evaluation, HitL, and the Phylactery retain those proofs. A typed falsehood is still false. AgentSpec.retries bounds validation repair; tool argument repair may use Pydantic AI retries, while policy refusal and missing premises settle explicitly rather than demand success without bound.

Semantic answer completion

When the semantic source-answer casting generates an answer, it classifies the final SDK response before ordinary answer settlement. A known normalized length finish is an answer_output_limit refusal, even when the SDK returns a nonempty string and marks the received response complete. It must not assign that partial result as the normal answer or new settled message history. Already-streamed text remains provisional and may remain in Run evidence; refusal does not erase earlier observations.

This check follows the existing failure path after the admitted call, without SDK repair, automatic retry, a larger generation budget, or another capability selection. Inspect the last response's completion metadata; an earlier response's limit is not the final response's outcome. Absent or other finish metadata retains current adapter compatibility and proves no natural stop. A named qualification may require the stronger explicit stop criterion. RunDONE still records successful Graph termination under its implemented checks, rather than factual truth, groundedness or a universal output-completeness certificate. A successful current recall selecting no sources may instead settle the deterministic, source-limited abstention owned by ADR27. That branch has no Chat response or SDK history suffix to classify; it cannot fabricate provider completion, usage, dispatch or token events. Other castings keep their own result contracts.

Mechanical cognitive postures

A Posture bounds instructions, output schema, model settings, and tool grant. Expansion, review, repair, and red-team work belong in separate runs when one proposal would otherwise grade itself. A Lens is a Posture template for Simulation's isolated Shadow branch. A Persona is a durable revisioned identity that may wear Postures. Persona names the actor; Posture constrains this act; Lens diversifies a simulation. These are enforceable contracts only where types, grants, settings, and Graph placement preserve the claimed separation.

Crucible uses explicitly biased Postures to pressure one decision from several declared angles. Every advocate receives the same pinned evidence and acceptance criteria, names its burden of proof and defeat condition, and runs separately from the Lead that synthesizes its claims. The posture label is neither a Persona nor authority, and a second-round rebuttal receives attributed opponent claims rather than hidden reasoning or an unbounded sibling transcript.

A local Privacy Agent is a Posture at an explicit Spell placement. It receives bounded raw material to produce a sanitized candidate and findings, but no Portal tool or declassification authority; its confidence is not permission. Deterministic control comes first, policy can require an independent verifier, and Security's trusted Portal Egress Gate makes the exact decision.

Streaming, history, and limits

pump_agent_events streams one hop: text starts and deltas become raw Oculus token events, while the final result supplies typed output and Pydantic AI history. The workflow serializes complete history and the new suffix separately for settlement or suspension. Bridge reserves the grant's output allowance and enforces the remainder as a per-request input-usage limit. Pre-request counting requires a counting implementation. For the pinned OpenAI Responses counter, Bridge also honors the resolved profile's endpoint support; method presence alone does not establish it. Generic local Responses and current OpenAI Chat models therefore use reported response usage, which cannot guarantee a preflight fence. Named Responses providers retain their own profile policy, and other counting implementations retain their pre-request checks. ADR 21 owns the separate character governor.

LychD does not yet share one UsageLimits object across a child-Agent tree. Future nested native Agents need explicit budgets. Opaque delegates receive serialized limits through their own grant; they inherit no RunContext, live toolsets, provider objects, or leases.

The delivered deferred substrate allows one model round to return exactly one approval request and no external deferred calls. It serializes suffix and call identifiers—not a live DeferredToolRequests object—plus the capability key, durable toolset id and type, tool name, project-owned effect id and revision, and prepared-definition digest. Resume reacquires a fresh grant and refuses execution if that binding changed before supplying DeferredToolResults; approval-required tools without an effect id and revision in the current prepared model round cannot park; an earlier round's binding supplies no authority. The tool owner must revise the effect revision whenever executable semantics change without changing the prepared definition. The parked binding is checked in the resumed round before dispatch. After that round, a completed one-shot tool may leave the prepared catalogue; this must not turn its executed effect into a reported refusal. Any subsequent approval captures its own current binding. Multiple approvals and generic CallDeferred labor fail truthfully because the record cannot represent them. A policy stop preserves completed tool results and closes only the known refused deferred requests as denied before appending its reply; it cannot erase earlier effects or label uncertain work as unexecuted. No tracked production toolset currently originates an approval request; the executable path is exercised by test-injected toolsets only. Delegated labor is a separate Graph node with its own job and trust boundary; ADR 25 owns durable consent ordering and recovery.

Artifacts and multimodality

Agents may return immutable ArtifactRef metadata. LychD does not yet materialize authorized blobs as Pydantic AI binary content. Image, audio, and other bytes require a Vessel-owned materializer enforcing authorization, media constraints, and provenance; metadata is not payload.

Correspondence

The Agent is where the Call becomes bounded labor and the Blade gives its answer form. A model opens possibilities and a schema cuts a result; neither crowns itself true. The Lich's First Invocation owns the mythic correspondence.

Consequences

Positive

Definitions survive model and hardware change; outputs, dependencies, retries, and tools stay inspectable; the same Agent can use a deterministic offline model.

Negative

Schemas and tools need deliberate maintenance, adapter compliance is required, and suspension, recursive budgets, artifacts, and foreign delegation need explicit LychD machinery.

Verification

tests/agents/test_factory.py, test_the_first_one.py, test_bridge_chat_graph.py, test_consent_resume.py, and test_state_serializable.py exercise construction, cache identity, tool absence, typed output, grants, streaming, settlement, continuation, and the durable Graph boundary. Dependency, message-schema, deferred-API, or stream changes require those tests and State to move before this law claims new behavior.