AgentStack
SKILL unreviewed MIT Self-run

Deep Research

skill-billy-enrizky-openbrowser-ai-deep-research · by billy-enrizky

|

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-billy-enrizky-openbrowser-ai-deep-research

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Pipes remote content directly into a shell (remote code execution).

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution Used

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Deep Research? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Deep Research

Drive openbrowser-ai to investigate a topic across multiple web sources and produce a cited markdown report plus structured JSON. Two modes:

  • flat synthesis (default) -- decompose query into 3-7 sub-questions, dispatch one parallel sub-agent per sub-question (each owns one tab), merge into one cited report.
  • drilldown (auto-detected from prompt phrasing: "deep dive", "exhaustive", "recursive", "drilldown", "thorough") -- same as flat, plus a second wave of parallel sub-agents on findings flagged needs_depth=true. Hard cap depth=2, max 3 follow-up sub-agents per parent.

Output paths (relative to current project root):

  • local_docs/research/YYYY-MM-DD-.md
  • local_docs/research/YYYY-MM-DD-.json

Architecture (mandatory): the orchestrating Claude session (the one running this skill) MUST dispatch parallel sub-agents via /dispatching-parallel-agents, one sub-agent per sub-question. Each sub-agent owns exactly ONE tab. Sub-agents do not open additional tabs. The orchestrator merges per-agent findings into one report.

Why one tab per sub-agent and not asyncio.gather over tabs in a single -c call: a single Python coroutine driving N tabs through one daemon serializes navigation events at the CDP layer, contends for the LLM-extraction worker, and cannot make independent decisions about pagination or follow-up clicks per tab. Dispatching real Claude sub-agents (each with its own context window and its own browser tab) gives true parallelism, independent reasoning per tab, and isolates failures so one bad page doesn't poison the rest.

Hard rules:

  • One sub-agent = one tab. Sub-agents must NOT call navigate(url, new_tab=True) to spawn additional tabs.
  • All sub-agents share the same daemon (and so the same Chrome process). Tabs are isolated; navigation in one tab does not affect another.
  • Each sub-agent writes its findings to its own JSON file under local_docs/research/_partial/-NN.json. The orchestrator reads and merges these.
  • The orchestrator never drives tabs itself. It only plans, dispatches, merges, renders, verifies, cleans up.

If a first-wave sub-agent returns &1 | grep -qi 'running\|listening\|pid'; then echo "Reusing existing daemon -- will work in new tabs" export DEEPRESEARCHREUSED=1 else echo "No daemon running -- will start fresh headless session" export DEEPRESEARCHREUSED=0 fi


Snapshot existing tabs so cleanup leaves them untouched:

```bash
openbrowser-ai -c - """  # paste exact user query here

# Daemon CWD often != shell CWD. Hard-code the absolute project root.
# Set this to the shell CWD at the start of the run; do NOT rely on os.getcwd().
PROJECT_ROOT = ""  # e.g. /Users/foo/myproject

# Auto-detect mode
DRILL_RE = re.compile(r"\b(deep ?dive|exhaustive|recursive|drill ?down|thorough)\b", re.I)
mode = "drilldown" if DRILL_RE.search(QUERY) else "flat"

# Slug = first 60 chars, lowercase, non-alnum -> '-', collapse repeats
def slugify(s):
    s = re.sub(r"[^a-z0-9]+", "-", s.lower())[:60]
    return s.strip("-") or "research"

today = datetime.date.today().isoformat()
slug = slugify(QUERY)
research_dir = os.path.join(PROJECT_ROOT, "local_docs", "research")
os.makedirs(research_dir, exist_ok=True)
base = os.path.join(research_dir, f"{today}-{slug}")
md_path, json_path = f"{base}.md", f"{base}.json"

# Bump suffix if collision
n = 2
while os.path.exists(md_path):
    md_path, json_path = f"{base}-{n}.md", f"{base}-{n}.json"
    n += 1

# Decompose: 3-7 sub-questions. Keep tight, non-overlapping, each answerable from web.
# This is a heuristic split; replace with your own decomposition for the actual query.
sub_questions = [
    # e.g.  "What is X?",
    #       "Who are the main actors / vendors / authors?",
    #       "What are recent (last 12 months) developments?",
    #       "What are the trade-offs / criticisms?",
    #       "What concrete numbers / benchmarks exist?",
]

assert 3 
Agent index: 
Project root: 

Hard constraints:

- You own ONE tab. The tab is the one you open at the start of this task.
- NEVER pass new_tab=True to navigate(). Reuse your one tab for every page.
- Do not switch to other tabs.
- Do not call openbrowser-ai daemon stop. The orchestrator owns daemon lifecycle.
- Visit at least 3 result URLs from at least 3 different domains.
- For each URL, extract one finding with: claim (one sentence), exact url,
  supporting quote (verbatim, max 40 words), domain, confidence (low|medium|high),
  needs_depth (bool, true if a deeper follow-up would meaningfully sharpen the claim).
- Return at least 2 findings; 3 is ideal.
- Write the findings JSON array to:
  /local_docs/research/_partial/agent-.json

Workflow (run via openbrowser-ai -c - heredocs, all in this one bash session):

1. Open exactly one tab on Google search:
   openbrowser-ai -c - ", new_tab=True)
   await wait(2)
   state = await browser.get_browser_state_summary()
   tab_id = state.tabs[-1].target_id
   _MY_TAB = tab_id[-4:]
   print(f"my tab: {_MY_TAB}")
   EOF

2. Scrape the SERP for top 5 result URLs.

3. For each of the top 3 results, navigate IN THE SAME TAB (no new_tab=True),
   wait 2s, capture body innerText, pick the sentence with the most word-overlap
   vs the sub-question (40-280 chars, >3-letter keyword overlap >= 1).

4. Write the findings JSON array to the partial file.

5. Print a one-line summary: "agent :  findings written".

Return: a one-line confirmation that the partial JSON file was written, and
its absolute path. Do not include the findings in your text reply -- the
orchestrator will read them from disk.

The orchestrator's pre-step (run once before dispatching agents):

openbrowser-ai -c - "
partial_dir = os.path.join(PROJECT_ROOT, "local_docs", "research", "_partial")
os.makedirs(partial_dir, exist_ok=True)
# Wipe stale partials from prior runs of the same query
for f in os.listdir(partial_dir):
    if f.startswith("agent-") and f.endswith(".json"):
        os.remove(os.path.join(partial_dir, f))
print(f"partial dir ready: {partial_dir}")
EOF

After all sub-agents return, the orchestrator collects findings:

openbrowser-ai -c - "
partial_dir = os.path.join(PROJECT_ROOT, "local_docs", "research", "_partial")

_findings = []
for fname in sorted(os.listdir(partial_dir)):
    if not (fname.startswith("agent-") and fname.endswith(".json")):
        continue
    path = os.path.join(partial_dir, fname)
    try:
        with open(path) as fh:
            arr = json.load(fh)
        if isinstance(arr, list):
            # normalize exact_url -> url (sub-agents may use either key)
            for item in arr:
                if "exact_url" in item and "url" not in item:
                    item["url"] = item.pop("exact_url")
            _findings.extend(arr)
    except Exception as e:
        print(f"skip {path}: {e}")

print(f"merged {len(_findings)} findings from {len(os.listdir(partial_dir))} partials")
EOF

If any sub-question's partial file is missing or has Agent index: (filename agent-retry-.json) Project root:

Hard constraints:

  • You own ONE tab. NEVER pass new_tab=True to navigate() after the first call.
  • Use openbrowser-ai -c only. NEVER use openbrowser-ai -p (it requires an LLM

API key and we explicitly avoid that path).

  • The first-wave attempt failed: heuristic sentence picker returned = 1 word.
  1. For factual sub-questions ("when was X released"), check Wikipedia

directly: https://en.wikipedia.org/wiki/Special:Search?search=...

  • Same JSON shape as Step 2a sub-agents.
  • Write to: /localdocs/research/partial/agent-retry-.json
  • Return at least 2 findings. If still "

partialdir = os.path.join(PROJECTROOT, "localdocs", "research", "partial") extra = [] for fname in sorted(os.listdir(partialdir)): if not (fname.startswith("agent-retry-") and fname.endswith(".json")): continue path = os.path.join(partialdir, fname) try: with open(path) as fh: arr = json.load(fh) if isinstance(arr, list): for item in arr: if "exacturl" in item and "url" not in item: item["url"] = item.pop("exacturl") extra.extend(arr) except Exception as e: print(f"skip {path}: {e}") findings.extend(extra) print(f"Retry added {len(extra)} findings -> total {len(findings)}") EOF


`_findings` lives in the daemon namespace for the next steps.

### Step 3 -- Drilldown (drilldown mode only): second wave of parallel sub-agents

Skip this step in flat mode. In drilldown mode, dispatch a second wave of `/dispatching-parallel-agents` -- one sub-agent per `needs_depth=true` finding, max 3 follow-ups per parent sub-question. Each follow-up sub-agent owns ONE tab, exactly like Step 2a.

The orchestrator first picks targets:

```bash
openbrowser-ai -c - 
Original source URL: 
Agent index:  (use prefix "drill-" -> filename agent-drill-.json)
Project root: 

Hard constraints:

- You own ONE tab. NEVER pass new_tab=True to navigate() after the first call.
- Find at least 2 additional sources from at least 2 different domains
  (different from the original source URL above).
- needs_depth must be false on every finding you return (depth cap reached).
- Same JSON shape as Step 2a sub-agents: claim, url, quote, domain, confidence,
  needs_depth, plus add sub_question = "".
- Write to: /local_docs/research/_partial/agent-drill-.json

After all drilldown sub-agents return, merge their partials into _findings:

openbrowser-ai -c - "
partial_dir = os.path.join(PROJECT_ROOT, "local_docs", "research", "_partial")
extra = []
for fname in sorted(os.listdir(partial_dir)):
    if not (fname.startswith("agent-drill-") and fname.endswith(".json")):
        continue
    path = os.path.join(partial_dir, fname)
    try:
        with open(path) as fh:
            arr = json.load(fh)
        if isinstance(arr, list):
            for f in arr:
                f["needs_depth"] = False  # cap reached
                if "exact_url" in f and "url" not in f:
                    f["url"] = f.pop("exact_url")
            extra.extend(arr)
    except Exception as e:
        print(f"skip {path}: {e}")
_findings.extend(extra)
print(f"Drilldown added {len(extra)} findings -> total {len(_findings)}")
EOF

Step 4 -- Aggregate and dedup

Merge findings, dedup by URL, build the source index that footnote numbers will point to.

openbrowser-ai -c -  url (sub-agents may use either key)
for f in _findings:
    if "exact_url" in f and "url" not in f:
        f["url"] = f.pop("exact_url")

# Dedup by URL, keep first occurrence
seen_urls = {}
deduped = []
for f in _findings:
    u = (f.get("url") or "").strip()
    if not u:
        continue  # drop uncited findings entirely
    if u in seen_urls:
        continue
    seen_urls[u] = True
    deduped.append(f)

# Build sources index, 1-based ids
_sources = []
url_to_id = {}
for f in deduped:
    u = f["url"]
    if u not in url_to_id:
        sid = len(_sources) + 1
        url_to_id[u] = sid
        domain = f.get("domain") or urlparse(u).netloc
        _sources.append({"id": sid, "url": u, "domain": domain, "title": f.get("title", "")})
    f["source_id"] = url_to_id[u]

_findings = deduped
print(f"After dedup: {len(_findings)} findings, {len(_sources)} unique sources")
EOF

Step 5 -- Render report

Write paired markdown + JSON. Every claim line in markdown ends with [N] cite. Summary cites top 3-5 sources.

openbrowser-ai -c - = 5:
        break
lines.append(" ".join(summary_sents) or "_No findings._")
lines.append("")

# Per sub-question section
for sq in _plan["sub_questions"]:
    lines.append(f"## {sq}")
    lines.append("")
    fs = groups.get(sq, [])
    if not fs:
        lines.append("_No findings for this sub-question._")
        lines.append("")
        continue
    for f in fs:
        claim = " ".join(f["claim"].split()).rstrip(".")  # collapse all whitespace incl newlines
        lines.append(f"- {claim}[{f['source_id']}].")
        q = " ".join((f.get("quote") or "").split()).strip()
        if q:
            lines.append(f"  > {q}")
    lines.append("")

# Sources
lines.append("## Sources")
lines.append("")
for s in _sources:
    title = s.get("title") or s["domain"]
    lines.append(f"{s['id']}. [{title}]({s['url']})")
lines.append("")

md = "\n".join(lines)

with open(_plan["md_path"], "w") as fh:
    fh.write(md)

payload = {
    "query": _plan["query"],
    "mode": _plan["mode"],
    "generated_at": _plan["generated_at"],
    "sub_questions": _plan["sub_questions"],
    "findings": _findings,
    "sources": _sources,
}
with open(_plan["json_path"], "w") as fh:
    json.dump(payload, fh, indent=2, ensure_ascii=False)

print(f"Wrote {_plan['md_path']}")
print(f"Wrote {_plan['json_path']}")
EOF

Step 6 -- Verify citations

Fail loud if any prose sentence outside headings, quotes, code, or source list lacks a [N] cite. Fix by adding the missing source then rerendering, never by deleting prose silently.

Do the verify in plain shell python3 (not the daemon). sys.exit inside the daemon namespace kills the daemon.

python3 - "$PROJECT_ROOT"  1 else os.getcwd()
research_dir = os.path.join(project_root, "local_docs", "research")

# Find latest report file -- use os.listdir + absolute path (glob fails on non-shell CWD)
files = sorted(
    [os.path.join(research_dir, f) for f in os.listdir(research_dir) if f.endswith(".md")],
    key=os.path.getmtime, reverse=True
)
if not files:
    print("No report found"); sys.exit(1)
md_path = files[0]

with open(md_path) as fh: md = fh.read()
md_no_code = re.sub(r"```.*?```", "", md, flags=re.S)

violations = []
in_sources = False
for i, line in enumerate(md_no_code.splitlines(), 1):
    s = line.strip()
    if not s: continue
    if s.startswith("## Sources"):
        in_sources = True
        continue
    if in_sources: continue
    if s.startswith("#"): continue
    if s.startswith(">"): continue
    if re.match(r"^\d+\.\s+\[", s): continue
    if s.startswith("Generated:"): continue
    if s in ("_No findings._", "_No findings for this sub-question._"): continue
    if not re.search(r"\[\d+\]", s):
        violations.append((i, s[:120]))

if violations:
    print("CITATION VIOLATIONS:")
    for ln, txt in violations:
        print(f"  L{ln}: {txt}")
    sys.exit(1)
print(f"OK: all prose cited in {md_path}")
EOF

Step 7 -- Cleanup

Two cases:

Case A: daemon was already running before the skill started (DEEP_RESEARCH_REUSED=1). Close ONLY the research tabs and leave the daemon + pre-existing tabs alone:

if [ "$DEEP_RESEARCH_REUSED" = "1" ]; then
openbrowser-ai -c - /local_docs/research/_partial" 2>/dev/null || true

Tips

  • Headless by default: OPENBROWSER_HEADLESS=true is set in Step 0 so the daemon always starts without a visible browser window. The daemon default in daemon/server.py is already headless: True, but a user config file can override it. The env var wins over config. If you need a visible window for debugging, unset or override: export OPENBROWSER_HEADLESS=false.
  • One tab per sub-agent: the orchestrator dispatches via /dispatching-parallel-agents and each sub-agent must own exactly one tab. Multiple-tab sub-agents serialize navigation at the CDP layer and lose the parallelism benefit. Enforce in the agent prompt: "NEVER pass new_tab=True to navigate() after the first call."
  • Single message, multiple Agent calls: to actually parallelize, the orchestrator must put all N Agent tool calls in ONE message. Sequential Agent invocations across messages run serially.
  • Heredoc quoting: always use `" appended to its prompt.
  • Drilldown cost: drilldown dispatches up to len(sub_questions) * 3 extra parallel sub-agents. Reserve for genuinely deep topics.
  • Rerun semantics: rerunning Step 5 alone re-renders from current _findings, useful after manual edits.
  • **Slug coll

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.