ADR 0005: add structured input without reinterpreting Agent¶
- Status: accepted
- Date: 2026-08-29
- Owners: Tesserix maintainers
- Tracks: GitHub issue #161
Context¶
Release 0.52.0 defines Agent[OutputT], AgentDefinition[OutputT], and string input on
AgentRunner.run, stream, and their synchronous variants. Changing those classes to
Agent[InputT, OutputT] changes what an existing Agent[TripPlan] annotation means even
when runtime calls still happen to work. The release compatibility gate found 23 changed
public aliases or signatures, all without a completed deprecation window.
Applications also need a statically checked Pydantic request boundary. That request must be validated before policy or provider work and must have a serializable JSON Schema for registry, function, and MCP exports.
Decision¶
Keep the released contracts unchanged and add separately named contracts:
TypedAgent[InputT, OutputT]extendsAgent[OutputT]withinput_typeand canonical request rendering;TypedAgentDefinition[InputT, OutputT]records the input schema in its reviewed revision;run_typed,stream_typed,run_typed_sync, andstream_typed_syncaccept the typed request while delegating to the same private execution loop;estimate_run_typed,PromptDefinition.instruct_typed,load_typed_config, andresolve_typed_configare additive companions where narrowing an existing parameter would break static consumers.
No compatibility allowlist or release-check exception is added. Issue #161 stays open for any future unification. Such a change must first publish a deprecation path and retain the old surface for its declared window; a major version remains the clean fallback.
The runtime target is unchanged: no additional network hop, provider call, retry, or tool dispatch. Typed input adds one bounded validation and canonical JSON serialization before the existing input hook and guardrail boundary. Invalid input makes zero provider calls.
Consequences¶
- Existing
Agent[OutputT]consumers retain their static and runtime meaning. - New consumers get strict input and output inference under mypy and Pyright.
- Providers, Groq/xAI/OpenRouter presets, custom gateways, tools, A2A, budgets, identity, tracing, cancellation, and terminal outcomes remain shared rather than forked.
- Function and MCP exports require a typed agent because their descriptors need a request
schema. Official A2A text ingress continues to accept
AgentDefinition[OutputT]. - The public vocabulary is larger during the compatibility window, but each name has one unambiguous contract.
When a provider, gateway, registry, or policy dependency is unavailable, behavior is the same on both input surfaces: the existing typed failure and fail-closed policy apply. A process crash after input rendering is recovered from the same checkpoints and idempotency records as a text-input run.
Migration and rollback¶
Migrate one call site by replacing its local declaration with TypedAgent, passing its
Pydantic request to run_typed, and running the same evaluation, cost, latency, and safety
suite. Roll back by restoring the application's prior text serialization and run call;
no provider, tool, checkpoint, registry, or persisted run format has to change.
Rejected alternatives¶
Reinterpret Agent[T] immediately. Existing annotations silently acquire a different
meaning and the compatibility guard correctly blocks the release.
Weaken the release snapshot. This makes CI green by deleting the evidence of the break; it gives consumers no migration path.
Accept object on run. Runtime validation could work, but static input errors would
move from the authoring line to production and every string-input consumer would receive
a weaker contract.
Duplicate the runtime. A second loop would drift on guardrails, budgets, identity, telemetry, cancellation, and recovery. The additive methods therefore converge before any execution decision.
Verification¶
- strict mypy and strict Pyright prove both
Agent[TripPlan]plusrun, andTypedAgent[TripRequest, TripPlan]plusrun_typed, inferRun[TripPlan]; - runtime tests prove canonical input rendering, pre-provider mismatch refusal, sync and streaming parity, and typed export behavior;
- the release check compares 0.53.0 with 0.52.0 and permits no removed or changed released signature.