diff --git a/docs/development/agent-experience-audit.md b/docs/development/agent-experience-audit.md index 5dad1b18c..feaff3769 100644 --- a/docs/development/agent-experience-audit.md +++ b/docs/development/agent-experience-audit.md @@ -4273,3 +4273,23 @@ Two different things went into that 191 s, and neither was a 120 s window. First **Lesson:** a default in a shared package is not the value its consumer runs. Read the consumer's override, or the log line that names the number, before measuring against it. + +## 91. "Upstream no longer lists X" named a cause the check cannot see (2026-11-02, cto) + +*Origin: the first draft of #3283, on 2026-11-01, said a vendor had removed a hosted-MCP tool. It had not.* + +When a pinned hosted-MCP tool was missing from a connection's `tools/list`, the `tool_unavailable` detail read `upstream no longer lists X`. That reads as a vendor removal. But the check only sees one connection's list, and the list depends on what that row's owner consented to. Sentry appears to offer `update_issue` only with its Triage skill (inferred from a captured list, not recorded). The Linear smoke row lists 26 pins as absent because its scope is read-only. The tool was there; that connection had not been granted it. + +**Repair:** #3286 rewords the detail to `this connection's tools/list does not include X`. For the same reason an absent tool files no drift card. Only a schema that moved under its pin does. + +**Lesson:** a status message should name what was observed, not a cause the check cannot observe. A reader takes the named cause as established. + +## 92. A cost gate read a label the caller writes, not the field that decides where the agent runs (2026-11-02, cto) + +*Origin: a code read during the entitlement-gate work (F51). Fixed in #3287; this entry was held until that deploy was live.* + +`isCloudRuntime` (`backend/services/agentIdentityService.ts`) decides which installs need the `cloudAgents` entitlement. It returned "BYO" for any install whose `config.runtime.host` was `'byo'`, before it looked at `runtimeType`. The install route stores the caller's `config.runtime` as given, `host` included. Dispatch decides where a turn runs from `runtimeType` alone (`inProcessInstallationFrom` in `agentEventService.ts`) and never reads `host`. So the gate and the runtime read two different fields, and the gate trusted the one the caller writes. A read of every active install after the fix deployed found none that had used the gap. + +**Repair:** #3287. The four types only Commonly runs (`moltbot`, `internal`, `native`, `managed-agents`) are cloud whatever `host` says. `host: 'byo'` still makes a codex install BYO; webhook, claude-code and unknown types are BYO either way. + +**Lesson:** a gate must decide on the field the enforcing path reads. A label the caller supplies may narrow a decision, never override it. Before trusting a field in a gate, read what the consumer, here dispatch, actually reads.