# Postmortem — Autonomy "1-page AI-news research report" run (partial completion)

**Date:** 2026-05-29
**Task as queued:** *"1-page research doc of latest AI news using browser with browser relay, add to InfiniBot notes, open in browser."*
**Outcome:** Ran to "completion" in the autonomy queue, but only **partially** satisfied the acceptance criteria. Navigation happened, a note was produced, and the browser opened — but the *research-via-relay* step degraded silently and the note was written **three times**.

---

## Root Causes

1. **No Brave Search API key configured in the gateway.** `mcp__infinibot__web_search` returned `{ error: "missing_brave_api_key" }`. The primary web-search path was unavailable; the agent had to improvise a fallback search route, weakening the "latest AI news" sourcing.

2. **clawd browser-relay content service was DOWN.** Only the CDP-direct relay actions worked (`status`, `tabs`, `navigate`, `focus`). The content-extraction actions (`snapshot`, `screenshot`, `exec`, `act`) returned **HTTP 404 / "tab not found."** The agent could drive the browser (open/navigate pages) but could **not read page content through the relay**, so the "research *using the browser relay*" criterion was only half-met — there was no graceful degraded-mode signal; the failure looked like a transient 404.

3. **`ui_control dispatch view=notes op=create` with no tab/instance target broadcast to every open tab.** Three InfiniBot tabs were open, so the create fanned out and produced **3 duplicate notes** that had to be manually deduped. (Matches the known `ui_control_notes_broadcast_dupes` memory.)

## Evidence

- `web_search` response payload: `{"error":"missing_brave_api_key"}` (gateway has no Brave key in config).
- Relay probe during the run: `status`/`tabs`/`navigate`/`focus` → 200; `snapshot`/`screenshot`/`exec`/`act` → 404 "tab not found." Confirms the clawd content service (not raw CDP) was the failing layer.
- Notes view showed 3 identical rows immediately after a single `op=create` dispatch with no `instanceId`/tab target.
- **Completion semantics (`src/autonomy/manager.ts`):** `completeCurrentTask()` (line ~367) calls `queue.complete(task.id, outcome)` and `failCurrentTask()` (line ~385) calls `queue.fail(task.id, error)`. There is **no per-criterion verification** between the agent declaring done and the queue flipping the task to `completed` — the optional `outcome` string is the only record of *how* it finished. So a run that only partially satisfied its criteria is still recorded as **`completed`**, indistinguishable in queue state from a fully-successful run. The dispatch path (lines ~191/198) only rolls back to `fail` when the orchestrator declines to *start* or the spawn throws — not when in-run criteria are unmet. **This run was therefore recorded as complete, not partial.**

## Remediations

1. **Web search** — *(a)* configure a Brave Search API key in the gateway config so `web_search` works first-try; **and/or** *(b)* make `web_search` gracefully fall back to an alternate provider (and surface a one-line "degraded: Brave key missing, used fallback" note) instead of returning a bare `{error}` the caller must special-case.

2. **Relay degraded-mode signal** — have the relay/clawd client detect "content service down" (content actions 404 while CDP-direct actions 200) and **return a structured `degraded: { reason: "clawd-content-service-down" }`** rather than an opaque 404. Agents (and the focus/activity UI) should render a clear *"browser relay degraded — navigation only, page-content extraction unavailable"* warning so the operator knows the research was navigation-only.

3. **Notes create dedupe** — `ui_control dispatch view=notes op=create` should **target a single tab/instance** (require `instanceId`, or pick the focused tab) instead of broadcasting; alternatively dedupe by an idempotency key on `(name, body-hash)` server-side so concurrent fan-out can't create duplicates. (Mirrors `notes_store_split` guidance: prefer one targeted dispatch.)

4. **Completion gating (the real fix)** — record *partial* completion. Add a lightweight acceptance check (or have the orchestrator/judge emit a per-criterion verdict) so `completeCurrentTask` can mark a task `completed` **vs** `completed_partial` with the unmet criteria attached, instead of collapsing every non-throwing finish into `completed`. At minimum, persist the degraded-mode signals from (1)–(3) into the task `outcome` so the queue record reflects that research-via-relay and note-uniqueness were not fully met.

---

*Scope note:* (1)–(3) are environment/tooling fixes; (4) is the durable fix — without completion gating, future degraded runs will keep being logged as clean successes.
