Some of the worst agent experiences begin with a polite clarification question. The assistant asks for an issue ID, a document, or "a little more context." The human supplies it, only to discover later that the agent could not access the tracker, connector, or account in the first place.
That is not conversational friction. It is an integration failure being misreported as missing user input.
- Pattern
- Capability-aware failure
- Output
- Integration contract
- Use when
- An agent depends on private repositories, trackers, documents, cloud accounts, or browser state
A question concealed the real fault
In one of the mined sessions, the task depended on a private tracker and a linked document. The integration could not retrieve them, but the fallback path turned the lookup failure into a vague request for more information. From the user's side, the assistant appeared lazy. From the system side, a missing capability had been swallowed.
The important failure happened before the question was generated. The orchestration layer lost the difference between these states:
- The user did not specify the object.
- The object reference could not be resolved.
- The connector was unavailable.
- The identity was authenticated but lacked permission.
- The source returned data that was too stale to trust.
Collapsing those into needs_clarification makes the interface simpler for the implementation and much worse for everyone operating it.
Use a failure taxonomy the model cannot blur
The agent should receive structured tool failures and be required to preserve their category in its response.
| Code | Meaning | Agent behavior | Human action |
|---|---|---|---|
INPUT_MISSING | Required intent or object was not supplied | Ask one precise question | Provide the missing decision or reference |
REFERENCE_UNRESOLVED | A name or link maps to multiple or zero objects | Show candidates or the failed lookup | Disambiguate only if needed |
CAPABILITY_UNAVAILABLE | The runtime has no connector or tool for the source | Stop claiming it can inspect the source | Enable the capability or change scope |
ACCESS_DENIED | The capability exists but this context lacks permission | Name the denied scope without exposing secrets | Grant bounded access or choose another operator |
SOURCE_UNRELIABLE | The source is stale, partial, or contradictory | Return available evidence with a confidence limit | Decide whether to proceed under uncertainty |
The distinction between CAPABILITY_UNAVAILABLE and ACCESS_DENIED is operationally important. Retrying a connector that does not exist is pointless. Re-authenticating a connector that exists may solve the task. A language model should not be expected to infer that distinction from an arbitrary error string.
The contract belongs below the prompt
Prompt instructions such as "do not ask vague questions" improve tone, not system truth. The tool boundary needs a typed result that preserves what failed.
type ToolFailure = {
code:
| "INPUT_MISSING"
| "REFERENCE_UNRESOLVED"
| "CAPABILITY_UNAVAILABLE"
| "ACCESS_DENIED"
| "SOURCE_UNRELIABLE";
dependency: string;
operation: string;
retryable: boolean;
requiredCapability?: string;
safeFallback?: string;
operatorMessage: string;
};The model can turn operatorMessage into clear prose, but it should not be allowed to relabel the failure. I would also log the code separately from the generated response. Otherwise, a well-written apology can erase the only signal that a connector is broken.
For user-facing output, the minimum useful report is:
I could not read the linked tracker item.
Failure: ACCESS_DENIED
Attempted: read issue metadata and recent comments
Missing capability: tracker.issue:read for this project
What I can still do: inspect the local repository and prepare a provisional plan
What would unblock the full task: grant read access or paste an approved exportThat is longer than "Can you send the details?" It is also actionable, auditable, and honest about scope.
Loud does not mean leaking internals
There is a real tradeoff. Raw provider errors can expose account names, internal object IDs, policy details, or security-sensitive topology. A fail-loud design should surface the category, dependency class, attempted operation, and unblocking action without dumping the connector's exception into the conversation.
It can also create alert fatigue. A transient timeout should not produce the same escalation as a missing connector. The taxonomy therefore needs retry semantics and a bounded retry policy. Retry only failures marked retryable, and stop when the dependency fingerprint has not changed.
What I would require in review
For every connected agent feature, test at least these paths:
- The object is genuinely missing from the request.
- The reference is ambiguous.
- The connector is disabled.
- The identity lacks the required read scope.
- The source times out repeatedly.
- A safe fallback exists, and the fallback is clearly labelled as partial.
The acceptance criterion is not merely that the UI shows an error. It is that each state leads to a different, correct next action.
An agent should ask a human for information only when the human owns that information. When the system owns the missing capability, the system should say so.
