20. Agents

In plain words: Design rule 20: Agents

Available 11 min read 21 glossary terms 21 connected pages
The short version written for the Snayu team

Defines an Agent as one typed thinking step with fixed instructions and output contract, while the model, settings, tools and permissions are attached fresh for each step. Built on Pydantic AI.

Why read it: Read this before creating an agent, giving it tools, or changing how model calls are made.
  1. 1An Agent is not a model, workflow or persona; its recipe and output contract are separately inspectable.
  2. 2Tools and delegation never gain authority from prompt text or model output.
  3. 3The model and permissions are bound late, per step, through the Traffic Controller (Dispatcher).

Available The Pydantic AI adapter works; shared usage limits and binary media are not yet.

Words on this page, decoded click one for its card

The original text Underlined words open their plain-English card.

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 VesselEngine Room to change intelligence without relocating policy, identity, or durable execution into an adapter.

Requirements

  • An Agent must be distinct from a model, GraphStep Map, PatternWorkflow, 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, GraphStep Map, 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 DispatcherTraffic Controller 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, SigilID Badge, 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 SigilID Badge, grant, input, and domain policy at the handler.

The First One

The delivered BridgeChat 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 ContextBriefing Pack OrchestratorStage Manager'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 BridgeChat can settle it as workflow state, it is not in The First One's current output union.

Late-bound capability

For each BridgeChat inference step, the current workflow asks the DispatcherTraffic Controller 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 AnimatorModel Runtime 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 DispatcherTraffic Controller 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 AnimatorModel Runtime boundary until a later amendment to the owning CovenantDesign Rule (ADR) 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 A2AAgent-to-Agent Line. 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 PortalCloud Model 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 PortalCloud Model road decision/EgressDecision. One grant or first-round approval never authorizes the whole loop's future payloads.

At that boundary the trusted PortalCloud Model adapter or job-scoped Provider Gate freezes the canonical child body and presents it to SpellweaverWorkflow Engine'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 CoffinSandbox runtime cannot mint, cache, widen, or reuse them; inability to obtain the full chain refuses that round.

Provider compatibility under the pinned adapter

Provider PortalsCloud Models 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; BridgeChat 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 OrchestratorStage Manager 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 a typed result's shape. Its prose can still be false, and its type establishes neither an effect's validity, claim identity, nor permission to publish or promote. Those proofs remain with deterministic validators, domain handlers, SigilID Badge, grant, receipts, evaluation, HitLApproval Step, and the PhylacteryMemory Vault. AgentSpec.retries bounds validation repair; tool arguments may use Pydantic AI retries. Policy refusal and missing premises settle explicitly instead of demanding 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 RunJob Record 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 GraphStep Map 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 constrains an act through instructions, output schema, model settings, and tool grant. A durable revisioned Persona names the actor and may wear different Postures. A Lens supplies a Posture template to diversify Simulation's isolated ShadowWhat-if Simulator branch. Expansion, review, repair, and red-team work belong in separate runs when a proposal would otherwise grade itself. These distinctions are enforceable only where types, grants, settings, and GraphStep Map 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 SpellStep placement. It receives bounded raw material to produce a sanitized candidate and findings, but no PortalCloud Model tool or declassification authority; its confidence is not permission. Deterministic control comes first, policy can require an independent verifier, and Security's trusted PortalCloud Model Egress GatePrivacy Filter 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, BridgeChat 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 GraphStep Map node with its own job and trust boundary; ADR 25 owns durable consent ordering and recovery.

Artifacts and multimodality

An Agent can return immutable ArtifactRef metadata, but metadata does not supply the payload. LychD does not yet materialize authorized blobs as Pydantic AI binary content. Image, audio, and other bytes need a Vessel-owned materializer that enforces authorization, media constraints, and provenance.

Correspondence

The Agent is where the Call becomes bounded labor and the Blademind office 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 GraphStep Map boundary. Dependency, message-schema, deferred-API, or stream changes require those tests and State to move before this law claims new behavior.