One command to fan a Deep Research prompt out to several external LLMs at once (ChatGPT / Gemini / Grok / others) through the operator's logged-in browser, then collect the reports, archive the originals, and synthesize a consensus. Trigger on "/dr-fanout", "fan out the DR", "distribute the research prompt". Automates the final stage of the Alpha Protocol: no more hand-pasting one prompt into N sites.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add tonydzi/second-brain-starter-kit --skill dr-fanout --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Dr Fanout?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/tonydzi-dr-fanout)More formats (shields.io, HTML) on the badges page.
---
name: dr-fanout
description: >
One command to fan a Deep Research prompt out to several external LLMs at once (ChatGPT /
Gemini / Grok / others) through the operator's logged-in browser, then collect the reports,
archive the originals, and synthesize a consensus. Trigger on "/dr-fanout", "fan out the DR",
"distribute the research prompt". Automates the final stage of the Alpha Protocol: no more
hand-pasting one prompt into N sites.
license: MIT
---
# /dr-fanout — fan a Deep Research prompt out to 3+ LLMs and collect a consensus
> The final stage of the Alpha Protocol, automated. Channels: **ChatGPT** (chatgpt.com) · **Gemini** (gemini.google.com) · **Grok** (grok.com). The mechanism = the Claude-in-Chrome MCP (the operator's live logged-in Chrome). Canon: [[alpha-protocol-recall-plus-dr]], decision-multi-llm-vendor-independence (heterogeneous consensus), [[chrome-autonomy-self-drive]], [[browser-work-on-peers-not-hub]] (strictly local).
## ⭐ What is already decided (the foundation, don't re-research) — updated 2026-07-16
1. **The browser layer = Firefox-first** (Decision Memo `02-Decisions/decision-2026-07-16-browser-automation-layer.md`, DR26-07-16-HUB-01, confidence high). Chrome 127+ encrypts cookies (ABE) and since April 2026 binds sessions to the hardware (DBSC) → external cookie extraction is impossible without malware techniques. **Firefox cookies are open** (plaintext SQLite, DBSC not implemented) → a dedicated per-service Firefox profile + Playwright `launch_persistent_context`; a Chrome CDP attach is only a fallback for strictly-Chromium sites. The hub stack is already up: `firefox_cookies.py` (`~/.claude/scripts/_shared/`), profiles in `D:\AutomationBrowsers\Firefox\`.
2. **The live Chrome MCP (Claude-in-Chrome) remains the deliberate anti-ban path** for actions that must look like "a human in a real session" (fb-post/x-post go that way). For dr-fanout the choice of execution path (live Chrome MCP vs headless Firefox+Playwright) is being clarified by an ongoing DR (see item 4).
3. **A CLI workaround does NOT give you subscription Deep Research** (verified 07-15): the Codex CLI / Gemini CLI only do web search; real consumer-subscription Deep Research cannot be triggered programmatically (Google's DR agent = a separate paid API). ⇒ the browser front end is the only road to subscription DR, so we make it anti-fragile instead of running away from it.
4. **ToS risk, sharpest at Grok:** the xAI AUP explicitly forbids automated/non-human access (risk of suspension/termination). There are no documented bans for automating YOUR OWN account, but the text is explicit → run the Grok channel at human pace; the decision to "keep / slow down / replace with claude.ai" belongs to the owner.
5. **Grok — we simply keep using it** (the owner's decision, 2026-07-16). The xAI AUP formally forbids automation, but enforcement hunts mass scraping (a $15k/1M-requests penalty), and our pace is naturally tiny (a few DRs per day at most) → we are not the target. The value of a third independent voice > the managed risk. Do NOT build limiters/throttling ([[ak47-simplicity]]) — Grok stays in the normal rotation; if the vendor itself pushes back (a captcha/challenge) — report it and skip that channel, no drama.
6. **The execution path is HYBRID, per vendor** (the owner's decision, 2026-07-16, "both ways"). Not one method for everyone:
| Vendor | Path | Why |
|---|---|---|
| **Grok** | live Chrome MCP, human pace | ToS-sensitive → act like a human in a real session (maximum anti-ban, same as fb-post [[chrome-autonomy-self-drive]]); a headless bot here = maximum risk |
| **ChatGPT** | a dedicated headless Firefox profile (unattended) OR the live Chrome MCP | milder ToS; report extraction = backend JSON (works in both); for an unattended schedule → Firefox |
| **Gemini** | a dedicated headless Firefox profile (unattended) | two-phase (plan → Start research), tolerant of automation; candidate #1 for a fully unattended run |
The principle: the harsher a vendor's ToS / ban sensitivity → the closer to "a live human in a session" (Chrome MCP); the more tolerant it is + the more unattended operation matters → the closer to a dedicated Firefox profile (Playwright persistent, the HUB-01 foundation).
7. **✅ The orchestration is CONFIRMED by an external DR** (`DR26-07-14-FLEE-01`, a ChatGPT+Grok synthesis 2026-07-16, digest in `03-Insights/insight-DR-DR26-07-14-FLEE-01-*`). Both vendors independently: the core = a **durable state machine + a job ledger, NOT "a long-lived autonomous browser agent"**; a local ledger FSM is sufficient for a single-owner hub. Both independently advise **NOT to build unattended Grok automation** (the xAI AUP + enforcement is real: >52k bans in 2026, Reuters 07-15) → our matrix in item 6 already honours that (Grok only via live Chrome MCP, never in a daemon). **Start the unattended schedule with 2 vendors: ChatGPT + Gemini.**
8. **Keep the vendor logic THIN and the orchestration SHARED.** UI-specific repos die fast (`chatgpt-automation-mcp` archived 2026-04-27, `browserbase/gemini-browser` archived 2026-05-20). Everything vendor-specific = swappable "adapters" at the bottom; the state machine/ledger above them does not change.
9. **⚠️ OPEN — the official path for Gemini:** the Deep Research Agent / **Interactions API** (`background=True` + polling, collaborative_planning) — production-ready, and it removes the most fragile and slowest browser channel. BUT ⚠️ ADDED COMPLEXITY + unclear billing: is it included in the Ultra subscription or is it separate money (the ChatGPT report says "NOT the same free-with-subscription")? **Check the billing BEFORE building** ([[prefer-included-limits-before-paid-api]]) — if it is paid, don't set it up without the owner's "+".
## Hard boundaries (read these first)
1. **⭐ The owner's mandate (2026-07-14, supersedes the quota gate of 07-05): any number of DRs, WITHOUT asking.** "Run as many deep researches as you like, don't ask me, don't worry about limits" — with an exhausted quota ChatGPT degrades gracefully (a lighter report, not a refusal), and a heterogeneous fan-out across 3 vendors compensates. I launch them myself and report after the fact. The only caveat: don't burn quota on DUPLICATES — the dedup check in Step 0 is mandatory.
2. **The browser stays strictly local** on the machine running the session. Ask nothing of the other machines.
3. **The owner's accounts:** if a site is logged out → log in per [[social-auth-autonomous]] (credentials in the store); a CAPTCHA/2FA wall → call him with one sentence + a screenshot.
4. **We send NOTHING but the prompt** into the DR composer. Don't touch other chats/settings.
5. **ANTI-RECENTS when collecting a report:** while looking for a finished DR chat/report in a web UI (ChatGPT/Gemini/Grok), do NOT conclude "there is no report" from a quick glance at recents — use the built-in SEARCH by keywords/ID + Projects/archive + confirm the active account by email. Registry first (`_DR-Registry.md`, Step 0), then the UI search, and only then the conclusion "not there". Canon: memory [[web-ui-search-not-recents]] (an incident on 2026-07-23).
## Step 0 — the entrance
⭐ **DEDUP BEFORE QUOTA (the owner, 2026-07-14):** before burning DR quota — check whether the report already exists. (1) There is an ID in the prompt → find its line in the registry: `grep "<DR-ID>" "$OBSIDIAN_VAULT/_DR-Registry.md"` — status `collected`/`synthesized` = STOP, no fan-out needed: report "already collected", show the files (`_originals\deep-research\*<DR-ID>*`, `03-Insights\insight-DR-<id>*`), go to Step 4 (synthesis). (2) No ID → grep the registry by the topic's keywords: the same topic sitting in `issued` → reuse THAT ID; in `collected+` → show what exists and ask whether a new run is needed at all. The nightly `dr_collect.py` (hub, 05:05) flips statuses from the exports by itself — the registry is fresher than the session's memory.
⭐ A DR number is mandatory (the owner, 2026-07-03): if the prompt has no `DRYY-MM-DD-MACHINE-NN` ID yet — allocate one: `python $IMPORTS_ROOT/dr_registry.py new "<topic>" --tool chatgpt,gemini,grok` and put the ID as the first line of the prompt (`# DR26-07-03-ZB-01 — <topic>`). The script derives the machine code itself (each computer has its own sequence — no collisions when syncing). Canon: the rulebook entry on DR numbering and the registry.
Where the prompt comes from: the current chat (just emitted by the Alpha Protocol) / a file the owner points to. We do NOT ask for a "+" (the 07-14 mandate) — I announce it with one line, `🚀 DR-fanout: distributing <ID> to ChatGPT+Gemini+Grok`, and go.
## Step 0.5 — tuning the prompt per vendor (mandatory)
Before distributing, append to the prompt body: `§1 THE UNIVERSAL ADD-ON` from [[dr-platform-playbook]] (`08-Templates\dr-platform-playbook.md`) — always, for everyone; **plus the platform block §2 (Grok) / §3 (Gemini) / §4 (ChatGPT)** for each specific vendor. The body (from `deep-research-prompt-template.md`) and §1 are identical for all; the platform block differs DELIBERATELY (it plays to each one's strength: Grok = realtime/X + hard citations, Gemini = plan editing/formality/depth). The `DRYY-MM-DD-MACHINE-NN` ID is the first line for all of them.
## Steps 1-3 — the SUBMIT → PROBE → COLLECT state machine (per vendor)
> The reliability core, v2, ✅ confirmed by an external DR (see item 7). Each vendor runs the machine independently; a break at one step falls into THAT step's fallback without taking the others down. The golden rule: **probe before burn** — no DR quota is spent until it is proven that (a) the mode is on, (b) the prompt is complete, (c) the run actually started.
>
> **States** (the ledger = `_drafts/DR-FANOUT-<DR-ID>-*.md`, key = the DR-ID → resume is idempotent): `staged → mode_verified → submitted → started → waiting → ready → collected → delivered`. Emergency states: `needs_reauth` · `drift_suspected` · `probe_failed` · `aborted`. ⭐ **There are exactly two human gates — `needs_reauth` and `drift_suspected`; everything else is unattended** (the DR consensus).
> **Locators:** ARIA/role first → CSS → and only then LLM/vision. ⛔ A semantic/LLM element search is for dry-run/repair mode ONLY, NEVER on a quota-spending submit (the DR consensus).
Tools in one ToolSearch: `tabs_context, navigate, computer, read_page, tabs_create, form_input, get_page_text, read_console_messages`.
### STATE 1 — SUBMIT (turn the DR mode on + paste the prompt)
A new tab → wait for the load → logged in? (no → [[social-auth-autonomous]]; a 2FA/CAPTCHA wall → screenshot + skip the vendor). Then **find the DR mode BY LOOKING at the page** (not from memory — the UI drifts on all three):
- **ChatGPT** (the work Google account): the model selector first → **Pro, the maximum** (currently GPT-5.6) + the maximum thinking level; then the composer → the tools menu ("+"/"Tools") → "Deep research". ⭐ The owner, 07-21: always the smartest model; the name changes, the principle does not.
- **Gemini** (the personal Google account): the model = **Pro** (currently 3.1), ⛔ NOT Flash (it silently rolls back — check!); enable **Extended thinking** (the checkbox on the right, near the microphone); "+" → "Deep Research" → the prompt → Enter → the plan → **press OK/Start**.
- **Grok** (the vendor account that also holds the owner's Twitter): ⭐ the owner, 07-21 — **Heavy** mode is the DEFAULT (the top subscription was bought; supersedes the earlier "Expert, don't touch Heavy"). There is no separate DR button → add a line to the prompt body, "Search the web extensively and cite sources"; watch that the selector does not fall back to Expert/Fast (a silent downgrade, 07-20). The "DR-grade in Grok Heavy" method (assembled 07-21, DR26-07-21-HUB-02): **Heavy IS Deep Research** — a swarm of 8–16 sub-agents searching in parallel and arguing; Expert is now single-agent and weaker for citation-heavy work; DeepSearch/DeeperSearch were removed and dissolved into Heavy. Without being forced, Heavy answers from memory → the prompt must include: "search the web and X extensively" + 15–25 primary sources + a short quote and an exact URL per claim + "cite ONLY URLs you actually opened" + a final verification pass. Proof of a real run: "Thought for" measured in minutes + an "N sources" counter; an instant answer with no sources = a downgrade, restart. Quotas = a weekly shared pool (Settings → Usage, as a percentage), context 256k–500k, a run takes 5–20+ min. There is no one-click export (select and copy); Share = a PUBLIC link ⛔. The full prompt template is in `_originals\deep-research\DR26-07-21-HUB-02-grok-heavy-playbook-grok.md`, section "(b) Reusable Prompt TEMPLATE"; the synthesis is `insight-DR-DR26-07-21-HUB-02-grok-heavy-as-a-deep-research-engine`.
- Pasting the prompt — ONLY through JS, NEVER `computer type` (every Enter = a send, a truncated fragment flies off and the quota burns). ⭐ **The `paste_verify.js` cascade** (next to the skill, from 07-16, it cures the 07-10 defect "execCommand did not take"; the forever-fix for Grok/ProseMirror is 07-17 FLEE-03): read the file `~/.claude/skills/dr-fanout/paste_verify.js`, execute its body through `javascript_tool`, then call `window.__drPasteVerify(PROMPT, "chatgpt"|"gemini"|"grok")`. It walks the methods itself (⭐ **a synthetic paste event for ProseMirror/TipTap** → execCommand → the native React textarea setter → Quill/CE paragraph nodes → directSet), checks length+head+tail after EACH one (for ProseMirror it reads `el.innerText`, NOT the textarea mirror) and returns `{ok, method, gotLen, wantLen, isPM, ...}`.
- ⚠️ **Grok 4.5 = a TipTap/ProseMirror contenteditable** (`div.tiptap.ProseMirror`), and the visible `<textarea>` is a HIDDEN MIRROR: filling the textarea (nativeSetter) returns `ok:true`, but the form/React do NOT update from the mirror → the Submit button never renders, Enter is a no-op, the run does not start. **The real editor must be filled with a synthetic paste event** (`ClipboardEvent('paste',{clipboardData:DataTransfer})`) → ProseMirror adopts the text normally → `button[type="submit"]` appears enabled → click it. The cascade now targets ProseMirror FIRST and does this by itself; the textarea is left as the last fallback.
- The manual fallback (if the asset is unavailable): for Grok/ChatGPT — `const ed=document.querySelector('.tiptap.ProseMirror')||document.querySelector('.ProseMirror'); ed.focus(); window.getSelection().selectAllChildren(ed); const dt=new DataTransfer(); dt.setData('text/plain',PROMPT); ed.dispatchEvent(new ClipboardEvent('paste',{clipboardData:dt,bubbles:true,cancelable:true}))`; for Gemini (Quill + TrustedHTML CSP) — `<p>` paragraphs via `createElement`/`textContent` + `dispatchEvent(new InputEvent('input'))`.
- The execution path per vendor is hybrid (see item 6 of "what is decided"): **Grok = the live Chrome MCP**; **Gemini/ChatGPT = the live Chrome MCP for now, a dedicated Firefox profile for the unattended schedule** (the HUB-01 foundation).
### STATE 2 — PROBE (prove it before spending quota) ⭐ the heart of v2
Do NOT press Send until three checks are green:
1. **The mode is on:** an active Deep research / Expert chip is visible on the page (confirmed by read_page/screenshot, not by memory).
2. **The prompt is complete:** the verdict is `ok` from `__drPasteVerify` (length + head + tail checked by the cascade). `ok:false` → the cascade walks the methods itself; 2 failures in a row → skip the vendor, the quota is intact, note it in the ledger. (A manual check without the asset: `editor.innerText.length` ≈ the expected length AND the first/last ~60 characters match.)
3. Only now Send/Run.
Then **prove the start** (otherwise "sent" ≠ "running"):
- ChatGPT: a plan / "starting research" / a growing source counter.
- **Gemini is TWO-PHASE:** first it generates a plan and WAITS for the "Start research" button — you MUST press it, or the research never begins. Confirmation: "Great, I'm on it… Researching N websites".
- Grok: "Researching…" / searched web N results.
- The start was not confirmed after 2 attempts with a fresh look → do NOT blindly restart (that burns quota) → mark `probe-failed` in the ledger, skip the vendor, report it.
### STATE 3 — LEDGER (immediately after each vendor starts)
Write/extend `$OBSIDIAN_ROOT/_drafts/DR-FANOUT-<DR-ID>-<slug>.md`: the topic · the full prompt · the start time · per vendor {started|skipped+reason|probe-failed} · the tab URL. This is the only insurance against "we forgot to collect it" on an unattended run — the collector (below) works off this file.
### STATE 3.5 — REPORT TO THE OWNER: the prompt + the LINKS (mandatory, the owner 07-17)
As soon as a vendor reaches `started` — **straight into the chat with him, without waiting for the report**: the full prompt text as a ```text``` block (paste-ready) + a `🔗 <vendor>: <url>` line for EVERY started chat. A live session → in the reply; an unattended run → into the fleet log chat via `bus_send.py`.
- Take the URL from the same tab snapshot as the probe (`tabs_context_mcp` / `page.url`) — it already contains the chat id.
- **The link = the PRIVATE chat URL** (`chatgpt.com/c/…` · `gemini.google.com/app/…` · `grok.com/chat/…`). ⛔ A public share link (`/share/…`, `g.co/gemini/share/…`) is PUBLICATION to the outside world (it has been indexed by Google) — only on his explicit request.
- Failed to capture the URL → say so honestly, with the reason; silently skipping it is not allowed.
- Why: he reads from his phone and collects the result himself through that link — it is his entry point. Canon: the rulebook entry on always putting the prompt and the link in the chat, memory [[dr-prompt-paste-in-chat]].
### STATE 4 — COLLECT (after ~20-40 min, driven by the ledger)
A DR thinks for 10-40 minutes. For each started tab: ready → extract (the ladder below) → save it **verbatim** into `$OBSIDIAN_ROOT/_originals/deep-research/<DR-ID>-<slug>-<vendor>.md` (provenance at the top: source/topic/date/origin) → `dr_registry.py update <DR-ID> --status collected --file "<path>"`. ⭐ **THE PROOF-OF-WORK GATE (07-21, DR26-07-21-HUB-02):** before saving, read the run counter — ChatGPT: the line "Research completed in Xm · N citations · M searches" above the widget; Grok: "N sources" + "Thought for…"; Gemini: the source list. **`0 searches` / `0 citations` / no sources → the status is `dead`, NOT `collected`**, restart with an explicit demand to search, and it does not go into the synthesis. This really happened: ChatGPT Pro "researched" for 9 minutes and produced a beautiful report with tables entirely from memory — outwardly indistinguishable from a real one. Not ready → note it and come back later. Unattended mode: the nightly `dr_collect.py` (hub) picks the DR-ID up from history/Downloads and flips the status itself — manual collection is only needed if the report is wanted NOW.
**The extraction fallback ladder** — the general order (the DR consensus): **the official export/share → backend JSON → DOM scraping → escalate to a human**. The DOM is the lowest priority, not the first. Then per vendor:
- **ChatGPT** (the report lives in a CROSS-DOMAIN sandbox iframe `connector_openai_deep_research` — JS cannot reach in, and synthetic clicks are ignored):
1. **Backend JSON (the main path, no clicks, ✅ 2026-07-14):** page JS `s=await fetch('/api/auth/session').then(r=>r.json()); conv=await fetch('/backend-api/conversation/<id>',{headers:{Authorization:'Bearer '+s.accessToken}}).then(r=>r.json())` → a Blob download of the JSON → then in Python on disk: `mapping[<node>].message.metadata.chatgpt_sdk.widget_state` (a JSON string → `json.loads`) → `report_message.content.parts[0]`. Find the node by walking for "Executive Summary".
2. **No widget_state** (some DRs, ✅ DR26-07-11-HUB-02): the report sits directly in `mapping[*].message.content.parts[0]` — walk the mapping and take the longest string over 3000 characters.
3. ⭐ **The widget's "Export to Markdown" menu — WORKS with a synthetic click, unattended (✅ 2026-07-17, HUB-06):** the new connector DR format does not expose widget_state AT ALL (paths 1-2 are dead) and is invisible to find/the accessibility tree → only a COORDINATE click works: the download icon in the widget header (top right, next to expand) → the menu `Copy contents / Export to Markdown / Export to Word / Export to PDF` → **Export to Markdown** → `deep-research-report.md` lands in Downloads (check with `ls`!) → move it into `_originals` with provenance. No human needed.
4. ⚠️ **The rate limit lies silently:** on "Too many requests" the backend returns the mapping WITHOUT widget_state → it looks like "there is no report", while the report is finished and visible on screen. "Empty from the API" ≠ "no report" → check against the SCREENSHOT before concluding, and wait a couple of minutes.
5. ⚠️ Opening `iframe.src` in a separate tab is POINTLESS — the sandbox is empty, the content arrives over postMessage.
- **Gemini:** the report lives in a `STRUCTURED-CONTENT-CONTAINER` (a canvas, same-origin) → `innerText` of the node with the heading → a Blob download → fallback: render into a `<pre>` + get_page_text.
- **Grok:** the report is in the main stream → `document.querySelector('main').innerText`, then cut off the echoed prompt and the trailing suggestions → a Blob download. ⭐ The trimming anchor: **"Thought for…" is more reliable than the first "Executive Summary"** (verified on the FLEE-01 run — the extract had caught the prompt echo).
- **Common to all:** a large text beyond the display truncation (the JS tool cuts at ~1500, get_page_text at ~50k per pass) → a Blob download → Read it from Downloads. ⚠️ **A Blob download can SILENTLY fail to land** (2026-07-14: grok yes, chatgpt/gemini no; `a.click()` throws nothing, the block is invisible) → ALWAYS `ls Downloads` afterwards; it did not land → fall back to `pre.textContent=txt; document.body.replaceChildren(pre)` (TrustedHTML-safe) → get_page_text in chunks. Clipboard paths (navigator.clipboard hangs; execCommand/Ctrl+C needs a gesture) — do not waste attempts on them.
- ⭐ **Field confirmation 2026-07-21 (finishing the 07-14 fan-out, 5 reports of 16–85 KB — the Blob NEVER landed once, Chrome multiple-downloads):** default straight to `get_page_text`, don't try the Blob. What we learned: (a) output over 50 KB is persisted BY THE HARNESS itself into `tool-results/toolu_*.json` on disk → you read it with Read and burn no context; under 50 KB arrives in-band; (b) the hard 50000-character-per-pass cap is worked around by shifting the window of visible text (padding / swapping nodes) + stitching two slices on an overlap of the last ~300 characters — that is how a 77k report was assembled; (c) the clipboard does not merely "hang", it returns the OLD contents of the operator's buffer = a false success, so always check the length and the first words; (d) **searching for a chat in Grok by title** is not a UI job (Ctrl+K is dead, the consent banner eats clicks) but a same-origin `fetch('/rest/app-chat/conversations?pageSize=50',{credentials:'include'})` → `{conversations:[{conversationId,title}]}`; (e) **Gemini is an SPA**: a fresh `navigate` to the chat URL loads the report more reliably than a synthetic click in the sidebar (the sidebar lazily renders empty text); (f) returning raw chat titles out of JS trips the content filter (`BLOCKED: Cookie/query string`) → return a short status token, not the data.
- ⭐ **Field confirmation, the EVENING of 2026-07-21 (collecting HUB-02):** (a) **your OWN button injected into the page does not bypass the gesture requirement** — a synthetic/coordinate click on it does not count as a user gesture and the blob still never downloads (the onclick counter = 0; don't reinvent this); (b) **extension clicks and keystrokes may not reach the page at all** (an unfocused window): the symptom is Send being pressed while the input field does not clear; the cure is a programmatic `document.querySelector('button[data-testid="send-button"]').click()` from JS, which React accepts as genuine (verified: the submit went through, the composer emptied); (c) a large text out of the DOM — first into a stash `window.__x`, look for the offsets in a second call, and slice it in Python from the persisted `tool-results\`; (d) selecting a node with Range+addRange and pressing Ctrl+C on top leaves the clipboard EMPTY (not to be confused with "the old contents": both happen — which is why you always verify with `Get-Clipboard`).
- ⚠️ **A vendor may REFUSE to answer** (Gemini, on an anti-ban outreach topic — it judged that to be circumventing protections). A refusal is a valid result: save it verbatim into `_originals` as a normal file, status `collected`, and in the Decision Memo state explicitly "no consensus, the base is N vendors" instead of passing a single vendor off as a fan-out.
## Step 4 — the synthesis consensus (Alpha Protocol step 4)
Read all the collected reports → a table of "agreed / disagreed / unique to each" → a Decision Memo (problem · what we know · the DR · options · risks · recommendation). The heterogeneous principle: do NOT smooth over the disagreements — show them to the owner as forks. The memo goes to `02-Decisions/` (if it is a decision) or into the chat as a report. Close the ledger (`status: collected`).
⭐ **Step 5 — CLOSE THE RECONNAISSANCE (the owner, 07-27, mandatory).** `synthesized` is not the finish: a report was written, but the system did not change. Every DR is driven to one of two terminal statuses, and both require a `--note`:
- `dr_registry.py update <ID> --status applied --note "<what actually changed: a Decision Memo / a production edit / a new routine>"`
- `dr_registry.py update <ID> --status parked --note "<why we're not taking it and what would change the decision>"`
`parked` is a legal and honest ending, not a defeat ([[gate-implement-critical-only]]: not everything researched must be implemented). The gate in `dr_registry.py` v3.3 rejects an attempt with no reason (exit 1). Why the rule exists: on 07-27 the registry had 184 of 246 researches hanging at `synthesized`, while only ~20 reached `02-Decisions` — there was no way to tell "this feeds the system" from "this just lies there". Canon: memory [[dr-finish-applied-or-parked]], CLAUDE.md §9.1.
## ⛔ FIELD DATA FROM THE NIGHT OF 2026-07-26 (batch ZB-01…ZB-05, laptop) — read before launching
1. **🔴 ChatGPT Deep Research = `dead` as of 07-26, the channel is excluded from the fan-out.** Three independent confirmations in one night: ZB-01 had two consecutive runs of "Research completed in 6m · **0 citations · 0 searches**" (the second one already AFTER an explicit follow-up "at least 20 searches / 20 sources" in the body), plus the same symptom on ZB-05 in a parallel session. The prompt arrived in full, the Deep research chip was on, the run started and "thought" for 6 minutes — it simply DID NOT SEARCH, handing back a pretty table from the model's memory. **Not a paste defect on our side.** Before putting ChatGPT back into the fan-out — do one probe run and read the counter; `0 searches` → `dead`, we go with 2 vendors and write that explicitly in the synthesis. Don't burn more than two Retries (a third Retry only cures `Error in message stream`, not an empty search).
2. **🔑 Gemini: enter ONLY by account name, the `u/N` index is NOT STABLE.** ⛔ Correction 07-27 (supersedes "always `u/7`"): `https://gemini.google.com/u/7/app` opened under a different account — free tier, the Flash model, an Upgrade button. Indices move between sessions; you cannot memorise "my number". **The working entrance:** `https://gemini.google.com/app?authuser=<the personal account>` (it redirects to the right `u/N` by itself). Detect the account without clicks, MANDATORY before pasting the prompt: `document.querySelector('a[aria-label*="Google Account"]').getAttribute('aria-label')` + a check of `document.body.innerText.includes('Upgrade')` (true = a free account). Without that check the report silently goes out on Flash and just looks "a bit weak". The account switcher lives in a cross-domain iframe — `find`/scrolling cannot reach it.
3. **Gemini silently rolls the model back to Flash-Lite** in every new tab. Look at the selector WITH YOUR EYES before every launch; Deep research moved into "+" → **More tools** → "Deep research" (it is no longer on the first level of the menu).
4. **ChatGPT: the order is "chip → prompt", NOT the other way round** (supersedes the earlier 07-20 note). The "Deep research" chip is an inline node INSIDE ProseMirror that lands at the caret position: click in the middle of the text and it splits the sentence (caught as `chipIdx:470` in the middle of a phrase). The working order: (a) enable Deep research in an EMPTY composer — the chip becomes the first node; (b) put the caret at the END (`range.selectNodeContents(ed); range.collapse(false)`); (c) the synthetic paste event — pasting into a collapsed caret replaces nothing and the chip survives. ⚠️ A repeat paste with `selectNodeContents` does NOT replace the text, it APPENDS (you get a duplicate) → clean it only with real keystrokes `ctrl+a` + `Delete`.
5. **Gemini collection: `querySelector('.markdown-main-panel')` takes the FIRST panel = the research plan, not the report.** You will save 1.7 KB of plan instead of 33.8 KB of report and never notice. The right way: anchor on the report's heading → `h2('<the plan heading>').closest('.markdown-main-panel').innerText`.
6. **Getting a large text out:** `javascript_tool` truncates output at ~1000-1500 characters. The only working path is to stash it in `window.__R`, IMMEDIATELY replace the body with a `<pre>` and IMMEDIATELY call `get_page_text` (cap ~50k; a 33.8k report went through in one pass). Replacing the body breaks the page → `window.__R` is lost on a reload, so the order cannot be changed. Don't try the Blob download (per the 07-21 field notes: it does not land).
7. **Press "Start research" TWICE, with a fresh look between:** the first `find` locates the button, but the click arrives before it is mounted. A second `find` gives a new ref and the second click works. Proof of the start is the canvas "Starting research…" / "Great, you can close this chat", not the fact that you clicked.
8. **🔁 DEDUP — at EVERY vendor, not only in the registry.** The 03:0x incident: the registry showed ZB-03 = `issued`, the dedup over Grok's history came back "clean" (a similar chat turned out to be from 22:49 UTC = BEFORE the batch's prompts were created) → I launched it. But Gemini's Recents already had its own `DR26-07-26-ZB-03` from a parallel session → one wasted Gemini DR run. The registry = the first filter, the history of EVERY vendor you send to = the second, mandatory one. This is [[check-all-places-not-one]] applied to vendors instead of disks.
9. **The root ChatGPT composer is SHARED across all tabs — your own draft follows you around.** 07-27: I opened a second tab for ZB-04 — ZB-03 from the first tab was sitting there; the paste event APPENDED (8303 characters instead of 4175), and `execCommand selectAll+delete` cleared it visually, but ProseMirror restored the state and appended again (12450). The only cure is a real keyboard: click into the composer → `ctrl+a` → `Delete` → verify `innerText.length===0` → and only then paste.
10. **`ctrl+a`+`Delete` also removes the MODE CHIP** (Web search / Deep research) — the run goes out with no search, and that is invisible in the prompt text. The order for ChatGPT: clear → paste the prompt → **turn the chip back on** → verify → Send. Also: the chip's text lands in the composer's `innerText` sometimes as the prefix `Web search\n` and sometimes as the suffix `\nWeb search` → when checking the length, strip it from BOTH ends, otherwise you get a false mismatch and start "fixing" a perfectly good prompt.
## ⭐ FIELD NOTES 2026-07-28 (ZB-04-2349, laptop) — prompt transport and report collection solved
1. **🔑 PROMPT TRANSPORT = `window.name`, not inline in javascript_tool.** A 12 KB prompt inlined for three vendors = ~37 KB of context plus an escaping risk. The working path, 0 tokens: `navigate` the tab to a local file holding the prompt → `window.name = await fetch(location.href).then(r=>r.text())` → `navigate` THE SAME tab to the vendor's site → `window.name` arrives intact (verified at 12,224 / 12,279 / 12,467 characters). Serving: a tiny HTTP server from the scratchpad.
⚠️ **The exception is Gemini:** COOP on `google.com` breaks the browsing context, and navigating AWAY from gemini to localhost zeroes `window.name`. The reverse direction (localhost → gemini) works.
2. **🔑 REPORT COLLECTION = a POST to the local server, so the text never enters my context.** The same trick in reverse: inside the vendor's chat stash the text in `window.name` → `navigate` to `http://127.0.0.1:<port>/sink` → from there `fetch('/save?name=raw-<vendor>.txt',{method:'POST',body:window.name})`. The server writes the file, and then I check completeness with Python (sections, URL count) for pennies. Reports of 16 KB / 34 KB / 83 KB were collected that way.
⚠️ **For Gemini** (COOP kills window.name) the **URL fragment** works: `location.href='http://127.0.0.1:<port>/sink#'+encodeURIComponent(txt)` → on the sink page `decodeURIComponent(location.hash.slice(1))` → POST. The fragment is never sent to the server, so privacy is intact.
3. **What does NOT work, don't waste attempts:** a `fetch` from the vendor's page to 127.0.0.1 (CSP `connect-src` — both chatgpt.com and gemini.google.com block it) · submitting a form to localhost (CSP `form-action`) · a CDP `ctrl+v` (the key arrives, the paste does not) · the "Copy contents" button in the UI (it needs window focus, and the window is in the background — the clipboard silently does not change; always compare `Get-Clipboard` against a sentinel).
4. **🔴 ChatGPT Deep Research — a FIFTH consecutive `0 citations · 0 searches`** (4 times on 07-26 + once on 07-28, on the work account; it "thought" for 15 minutes and produced a beautiful report from memory). Web search on the same account and the same model searches fine (90+ queries, 80+ pages, 168 URLs in the report). ⇒ **Don't spend the DR chip on this account: run the ChatGPT channel through Web search directly** + add a line to the prompt body, "list the queries you ran and the URLs you actually opened before the final report". That saves 15 minutes and one run.
5. **ChatGPT DR became TWO-PHASE, like Gemini:** after Send it shows a plan card with `Edit / Cancel / Start` and an auto-start on a ~30s timer. Press the button anyway.
6. **ChatGPT: `execCommand insertText` took, the synthetic paste event did NOT** — exactly the opposite of Grok (where the paste event works first time). Both need the cascade, and the method order differs per vendor. Set the mode chip in an EMPTY composer, then put the caret at the end, then paste — the chip survives.
7. **Gemini: the "Start research" button stays visible and enabled after a successful click.** Proof of the start is not the button disappearing but the canvas "Great, you can close this chat" + "Researching N websites". And Gemini's `bodyLen` jumps by an order of magnitude (327,887 → 14,403 on a re-render), so it is useless as a start signal.
8. **Gemini collection: take the panel with the MAXIMUM length**, not the first one. On a finished report there were three `.markdown-main-panel` elements: 1861 (the plan), 144, and 34,444 (the report). The report panel renders lazily — if it is absent, click the report card in the feed.
9. **Grok Heavy: 485 sources and ZERO inline links.** The sources are handed back as a paragraph listing domains, with 2 anchors on the page. `innerText` loses nothing, but the verifiability of Grok's facts is low — check contested figures by hand (in this run Grok was wrong: it claimed there are no paid boosts, while a $159.99 Story Boost exists).
10. **Two Chromes connected to the MCP.** One of them can be a clean profile, logged out everywhere. Check the account (`/api/auth/session` on ChatGPT, the `aria-label` on Gemini, the word `Sign in` in the text on Grok) BEFORE concluding "logged out, call the owner".
11. **⭐ The Blob download on chatgpt.com DOES WORK — and it is the only way to take the whole report (07-27, supersedes the 07-21 field note "the Blob does not land").** Measured: a 73,144-character report arrived as a 97,992-byte file, a second one at 60,623 characters, both on the first attempt. `get_page_text` is UNUSABLE at that size: it cuts at 50,000 characters **and cuts off exactly the tail — the Proof of work section**, i.e. the very evidence that the vendor searched. The order: collect the text of the last `[data-message-author-role="assistant"]` into `window.__R` → Blob + `a.click()` → **mandatory `ls Downloads`** → move it with a script, with provenance. The session's context is not spent at all — the text goes disk to disk. ⚠️ The trick of "padding it past 50 KB so that get_page_text persists it to disk" does NOT work: the tool sometimes persists and sometimes truncates inline — the behaviour is unpredictable, don't rely on it.
12. **`exit 0` from `brain_embed_update.py` while the lock is held is NOT a bug and NOT a lie.** It is deliberate in the code: `busy_code = 3 if index_age_hours() > STALE_HOURS else 0` (a 26h threshold). The exit code answers "is the index stale by the clock?", NOT "did my request run?". After writing new notes those are different questions: an index that is fresh by the clock still does not contain them. ⇒ After a reindex, verify FUNCTIONALLY — query `brain_ask.py` with a phrase that exists nowhere but in the new files. The `harari ×N` counter in `_brain_e5_meta.pkl` is not proof: it holds file paths, not chunk text.
## Errors/pitfalls (things that did not make it into the state machine)
- ⚠️ **A background/minimized Chrome tab (verified 2026-07-17):** viewport 0×0, the layout is frozen → synthetic JS clicks and Radix/Angular buttons (Gemini's "Start research", ChatGPT's "+" menu) do NOT fire, and `computer` clicks land in the wrong place. The cure: `tabs_create_mcp` → a new tab (it gets a layout even in a background window) → open the same conversation URL → `find` → `computer left_click ref` (CDP-trusted). The prompt is already sent by then — you only lose "press the button", not the quota.
- ⭐ **"started" in the ledger ≠ actually running (incident HUB-06, 07-16→17):** three vendors were marked started while in fact ONE was working. Gemini was stuck on an unpressed "Start research" button (the page held only the prompt, panel=0), and Grok was never submitted at all (the path stayed `/`, the prompt sat in the composer). A night was lost. → **the probe-after-start is mandatory and must be evidential:** the path changed to /chat/ · a progress indicator is visible · the page's volume grew. Pasting ≠ sending; sending ≠ starting. A `submitted` state without a confirmed `started` is not started.
- ⭐ **ChatGPT DR stops at the research plan (verified 2026-07-20, ZB-02):** the first run (~12 min, "0 citations · 0 searches") can return ONLY a research plan + an output structure, WITHOUT any real research, and finish (`finished_successfully`, end_turn). This is NOT a paste/start bug — the run really happened, it just degenerated into a plan. The cure: a follow-up in the same chat, "Proceed now: execute this plan and write the FULL final report… Do not stop at the plan" → the second run (65 min in our case) produces a full report with live URLs. When collecting: if the backend JSON's longest part starts with "Research Plan:" and the mapping holds only one widget with the plan — that is the plan, not the report; nudge it, don't save it as collected. (Gemini/Grok don't suffer from this — in Gemini the plan is a separate phase with a button.)
- ⚠️ **ChatGPT's DR mode chip lives INSIDE the editor (ZB-02, 07-20):** the `paste_verify` cascade (directSet/execCommand) clears the editor and ERASES the "Deep research" chip → the probe will show `chip:false`. The order: paste the prompt first, THEN turn Deep research on over it (the "+" menu → Deep research), then check the chip and Send. Otherwise an ordinary chat message flies off — the DR quota is intact but the answer is the wrong kind.
- ✅ **Grok, the textarea/contenteditable mirror — the ROOT CAUSE was found and cured (FLEE-03, 07-17):** Grok 4.5 = a TipTap/ProseMirror contenteditable (`div.tiptap.ProseMirror`); the visible `<textarea>` is a HIDDEN mirror. Filling the textarea (nativeSetter / value + InputEvent) returns `ok:true`, but the React/PM state does not update from the mirror → Submit never renders, Enter is a no-op (exactly what burned time on 07-10 and 07-17). **The fix in the cascade:** target `.tiptap.ProseMirror` FIRST + a synthetic paste event (`ClipboardEvent('paste',{clipboardData:DataTransfer})`) → PM adopts the text → `button[type="submit"]` appears enabled → click. Verified live on 07-17 (FLEE-03 Grok `/c/e621f761`). If the UI drifts again and the paste event stops working → a new chat + a probe, don't burn time.
- ⭐ **ChatGPT DR silently does not search — and a prompt does NOT cure it (2026-07-26, ZB-02 + ZB-05 independently).** The symptom: "Research completed in Xm · **0 citations · 0 searches**", a beautiful report entirely from memory. Measured: 4 runs, 2 different chats, 2 independent sessions, one Pro account; the second run of each pair went out WITH an explicit demand to search — it did not help. ⛔ The rule: **two `dead` results in a row = do not try a third time the same way**, close the channel, build the synthesis on the remaining vendors and state explicitly "the base is N vendors". The discriminating test (cheap, run it BEFORE drawing conclusions about the cause): the same question in an ordinary chat with the **Web search** tool — that goes down a different code path. If Web search works fine (in our case: 24+ queries, "Searched N websites" blocks) → it is the DR path that is broken, and 🤔 most likely the DR quota is exhausted and the vendor degrades silently instead of erroring honestly. A side benefit: Web search + a hard prompt ("list the queries and the URLs you opened before answering") produces a usable, citable report — a working workaround while DR is down.
- ⭐ **The root composer at `chatgpt.com/` is shared per account and parallel sessions tear it apart (2026-07-26).** The draft syncs between sessions: in one run someone else's `ZB-05` and `ZB-06` passed through it, a neighbouring session rolled my pastes back with its own React state, and once my line stuck to the tail of SOMEONE ELSE'S prompt (it could not be cleaned — the edits would not hold). ⛔ The rule: when working in parallel, **do not touch the root composer**; run inside YOUR OWN `/c/<id>` (the composer is per-chat and uncontested), and create a new chat only when the root one is empty. Always read the composer before pasting: non-empty + someone else's ID = stop, not "I'll overwrite it".
- A logout in the middle of the research → the report may be lost: after logging back in, check the vendor's chat history, a DR is usually saved there.
- A long report gets truncated in get_page_text → scroll/expand ("Show more") before reading; compare the end of the text with the end of the report.
- ⛔ Never set up a PAID API path as a "fallback" without approval ([[prefer-included-limits-before-paid-api]]).
- The same prompt goes to every vendor VERBATIM, except the deliberate platform block §2/§3/§4 (otherwise the consensus is dirty).
- ⚠️ **An account can flag automation:** on the FLEE-01 run ChatGPT showed a "Suspicious activity detected" banner when a DR was started from automation (it did NOT block it, the research ran). That is exactly the failure mode both DR vendors predicted. Saw the banner → don't panic and don't hammer retries; note it in the ledger and continue. If it repeats systematically → `drift_suspected`, report to the owner.
- ⚠️ **Nobody publishes session longevity** (the DR consensus) → treat a forced re-auth as INEVITABLE, don't build on a timer. `needs_reauth` = a first-class state + an out-of-band ping to the human (the approval channel).
- ⏳ **A fully unattended LAUNCH** is the next build step (the architecture is known from the DR: a ledger FSM + a dedicated Firefox profile + cron/systemd; start with ChatGPT+Gemini). For now only COLLECTION is unattended (the nightly `dr_collect.py`); launching requires a live session with a browser. The hole is acknowledged and closes after the owner decides on a "standby browser session on the hub".
## /tt — how to test v2 (after FLEE-01 lands, or when editing)
Don't run a live DR just to test (it burns quota). Test in parts: (1) the probe logic — on an already-open tab, verify that mode + length detection works WITHOUT pressing Send; (2) extraction — run the parser over an ALREADY collected past DR (backend JSON / innerText) and compare against the saved `_originals`; (3) the ledger + dr_collect — run them against an existing DR-ID. A live end-to-end run only when the research is genuinely needed.
---
<!-- CONTACT-FOOTER -->
## About & contact
Built and battle-tested at **Palo Alto AI Research Lab** — a fleet of Claude Code machines
running 24/7 as a second brain and synthetic cofounder. Every skill here survived real
production use before publication.
- 📦 All 101 skills: https://github.com/tonydzi/second-brain-starter-kit
- 👤 Author: **Anton Dziatkovskii** — Telegram [@tonydzi](https://t.me/tonydzi) · WhatsApp [+1 341 222 9178](https://wa.me/13412229178) · X [@Tony_Stef_](https://x.com/Tony_Stef_)
- 🧪 **Engineers: want to test-drive this setup?** Message me — I hand out free starter seeds to engineers who test and report back. Custom skill requests welcome.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!