ADR-105: Typed user-input suspension (WAITING_FOR_INPUT)
- Status
-
Accepted (the approval+input registration ban is widened by ADR-134: A builtin's declared write effect implies human approval)
- Amended
-
2026-08-09 by ADR-134
- Date
-
2026-07-22
- Authors
-
Netresearch DTT GmbH
Context
Human-in-the-loop so far means approval (ADR-084): a tool
opts into a verdict, the run suspends WAITING_FOR_APPROVAL, and approve
continues it with approve/deny. But some tools need the human to supply
typed data, not a verdict — a target the model cannot know, a value only a
person can decide. The P1 roadmap's final queue-epic slice asks for exactly
this: a tool that suspends the run to collect schema-validated input and
resumes with it fed back into the tool's arguments.
Decision
A new suspend kind, routed by the persisted status discriminator:
WAITING_FOR_APPROVAL → approve, WAITING_FOR_INPUT → ``submitInput()``.
The two never share a code path after the row is loaded, so the persisted state
needs no "kind" field.
- Tool signal. A tool opts in with the
Requiresmarker (the sibling ofInput Interface Requires), declaring an input schema viaApproval Interface get. When the model calls an offered such tool, the loop throwsInput Schema () Toolcarrying aInput Required Exception Suspendedwhose newRun State input/Tool Name inputfields name the target and its schema. The runtime's ladder catches it right after the approval arm (before the guardrail pair and the genericSchema Throwable) and persists WAITING_FOR_INPUT. - Reuse. The status is stored in the existing
suspended_column (no new column), the guarded suspend/claim transitions mirror ADR-084 (state suspend/Run For Input claim), the lease is cleared so the reaper (ADR-104) ignores a waiting run, and the MAX(sequence)+1 resume-position resolution is unchanged.For Resume From Input - Divergence — validate before claim.
submitvalidates the submission against the declared schema (via the existing structure-onlyInput () Json, ADR-082) before probing or claiming the run. An invalid submission is rejected without consuming the claim, so the run stays WAITING_FOR_INPUT and the user can resubmit — a flow approve/deny has no analogue for.Schema Validator - Overlay. On resume, the human's validated values are overlaid onto the target call's arguments bounded to the schema-declared keys: the model's own values for those keys are stripped and only declared keys from the human are merged, so neither side can smuggle a value into the other's field.
Fail-closed rules
- A degenerate schema is corruption, never accept-all. An empty/shapeless
input(against whichSchema validatereturns true for anything) is rejected at both the capture-time gate (() Logic) and the rehydrate gate (Exception Corrupt) — one shared predicate (Suspended State Exception Input) is the single authority.Schema:: is Usable () -
A tool may not be both approval- and input-gated. The approval-resume path carries no input and would silently drop the mandatory data; the combination is rejected at tool registration, and — defence in depth —
resumerefuses an input-requiring pending call rather than fail-open executing it.() ADR-134: A builtin's declared write effect implies human approval widened the ban without changing this reasoning: a declared write effect became a second way to be approval-bound, so a non-remote, write-declaring tool may not implement
Requireseither. TheInput Interface resumerefusal named above is what makes that combination permanently unexecutable, not what handles it. Read ADR-134 for the ban in force.() - An unstorable suspension fails closed as
SUSPEND_(as approval does): promising an input flow that cannot be resumed would strand the client.FAILED - Submitted values are untrusted content entering the model context; the submit entry point is admin-gated, which is the injection mitigation (structure-only validation does not sanitise content).
Consequences
AgentgainedRun Outcome AWAITING_;INPUT AgentgainedEvent Kind INPUT(payload{submittedonly, never the values, ADR-064);By} Agentalready existed and needed no change. All are the documented minor-release growth path — consumers match with a default arm.Run Status:: WAITING_ FOR_ INPUT AgentgainedRuntime Interface submit(an interface method, not a new public service — the audited public-service count is unchanged);Input () AgentgainedRun Repository Interface suspendandRun For Input () claim;For Resume From Input () ToolgainedLoop Service Interface resume.With Input () - Accepted limitations: validation is structure-only (no min/max/enum/pattern —
a tool needing those re-checks in
execute, as() completedoes); a single turn requesting two tool inputs is fail-closed-refused on the second, not collected; the schema is delivered in the suspend response, not viaStructured status(which strips() suspended_), so a client reloading mid-wait loses the form — matching the existing approvalstate pendinglimitation.Tools