Research (QRSPI Step 3)

SkillAI & models

Use when questions.md is approved and the QRSPI pipeline needs objective codebase and web research, dispatches parallel specialist subagents per question, collates per-question findings into research/summary.md

Then ask your AI: use the Research (QRSPI Step 3) skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add it as a step in a pipeline you build on Ahel, or connect Ahel to Claude, ChatGPT, Cursor, Claude Code or Codex and ask your AI to use it.

What this skill tells your AI

The instructions your AI receives, as published by dfrysinger/qrspi-plus in skills/research/SKILL.md and read by Ahel’s review.

PRECONDITION: Invoke qrspi:using-qrspi skill to ensure global pipeline rules are in context. (Idempotent on session re-entry. Subagents are exempt — SUBAGENT-STOP in using-qrspi handles that.)

Announce at start: "I'm using the QRSPI Research skill to investigate the research questions."

Overview

Objective exploration driven by the research questions. Gathers facts, not opinions. Each question gets a focused specialist agent with the right tools for its research type. Research isolation is structural — no research subagent ever sees goals.md.

Artifact Gating

Required inputs:

  • questions.md with status: approved

If questions.md doesn't exist or isn't approved, refuse to run and tell the user to complete the Questions step first.

Read config.md from the artifact directory to determine whether second-model reviews are enabled. If config.md doesn't exist, default to second_reviewer: false.

Execution Model

Parallel specialist subagents (ISOLATED — structurally enforced). One subagent per question (or per small group of related questions). A collation subagent assembles the per-question ## Summary blocks into _collated.md at the end; the orchestrator then renames _collated.md to summary.md via a single mv Bash call.

CRITICAL: goals.md is deliberately withheld from ALL research subagents. This is enforced structurally — subagent prompts contain only the question(s) assigned to them. goals.md is never passed to any research subagent, including the collation subagent. This prevents confirmation bias.

Process

Dispatch

  1. Parse questions.md — extract each numbered question with its research type tag
  2. Group related questions (e.g., two questions about the same subsystem) to avoid redundant exploration
  3. Dispatch specialist subagents based on research type tags:
Research TypeAgent ToolsFocus
[codebase]File read, grep, globRead code, trace logic flows, map architecture. Report file:line references.
[web]Web search, web fetchSearch competitors, libraries, best practices, docs. Report URLs and sources.
[hybrid]All toolsCompare local implementation against external standards/alternatives.
  1. Independent questions run in parallel subagents (use concurrent dispatches in a single orchestrator response)
  2. Each subagent writes its own report directly to {ABS_RESEARCH_DIR}/q{NN}-{type}.md using the Write tool. The orchestrator passes the absolute path into the subagent prompt; subagents do NOT return findings as text. The q*.md filename pattern is intentionally outside the Claude Code 2.1.x subagent-guardrail blocklist (filenames whose stem starts (case-insensitive) with report, summary, findings, or analysis), so direct write succeeds.

Per-Researcher Subagent

!cat skills/_shared/evergreen-output-rule.md

Inputs: Only the assigned question(s) from questions.md. NO goals.md. NO raw feedback/research-round-*.md files (raw feedback may carry user goals/intent — forwarding it to a research subagent breaks the research-isolation invariant). The orchestrator also passes the absolute output path ({ABS_RESEARCH_DIR}/q{NN}-{type}.md) and, for grouped questions, the full set of question IDs the report should cover. On re-dispatch via Rejection path 2, the orchestrator passes a sanitized defect summary it authors itself from the user's feedback — defect-only bullet points (e.g., "missed the auth module", "TL;DR is missing", "broken file:line citation"). Goal-bearing or intent-bearing language is stripped before the summary reaches the subagent.

Dispatch — for each question (or grouped set of related questions), dispatch(subagent_type: qrspi-research-specialist, model: sonnet) in parallel via concurrent dispatches in a single orchestrator response. The agent body (loaded by the runtime) carries the full research-agent rules, output-format template, and contract; the dispatch prompt carries only the parameters below.

Dispatch parameters (per specialist):

  • question_body: wrapped body of the assigned research/q*.md question(s) between <<<UNTRUSTED-ARTIFACT-START id=question>>> and <<<UNTRUSTED-ARTIFACT-END id=question>>> markers; for grouped questions, all assigned question texts concatenated within the wrapper
  • output_path: absolute path the specialist writes its report to (<ABS_RESEARCH_DIR>/q{NN}-{type}.md)
  • question_ids: comma-separated numeric IDs the report should cover (e.g., 3 or 3,7)
  • defect_summary (re-dispatch via Rejection path 2 only): orchestrator-authored sanitized defect summary; goal-bearing/intent-bearing language stripped

Direct-write contract (unambiguous default). Per-researcher subagents write their q*.md report directly to output_path via the Write tool. They do not return report content as text. Text-return is not used anywhere in the research pipeline — the collation subagent also direct-writes (to the research/_collated.md staging filename, which the orchestrator then renames to research/summary.md). The staging-filename pattern exists precisely to avoid text-return through main chat. If the orchestrator ever omits output_path from a per-researcher dispatch, that is a dispatch defect — fix the orchestrator, do not fall back to text-return.

Summary-last authoring order. The per-question report template places the structured summary block (TL;DR / key findings / surprises / caveats) at the top of the file — that is the consumer-facing reading order. Authoring order is the inverse: investigate first, draft the full report body, then author the summary block last, then place it at the top of the file. Do not generate the summary from intent before the body is complete.

Research-isolation invariant — the specialist dispatch carries NO companion_goals, NO other-question content, and NO feedback/research-round-*.md files. This is structurally enforced — the agent body refuses goals.md / cross-question content if it ever appears in the dispatch prompt. Research isolation prevents confirmation bias.

Citation-Density Post-Validation Hook (trusted-model re-run path)

Every qrspi-research-specialist dispatch is observed at the dispatch boundary by a post-output citation-density validator. The validator runs AFTER the specialist's q*.md report is written and BEFORE the report is read by the collation subagent or any downstream consumer. The floor that the validator measures against is the validators.citation_density_floor: key in config.md (default 0.05 — see the schema documentation in skills/using-qrspi/SKILL.md for the key's full semantics, the citation-counting rule, and the rationale for the default value).

Above-floor result. The report proceeds unchanged. The collation subagent reads it on the same schedule as any other specialist output. No re-run, no diagnostic, no telemetry rerun-count increment.

Below-floor result (first occurrence). The dispatch re-runs the specialist EXACTLY ONCE on the trusted model (the role's trusted_path: route from config.md's model_routing: table, OR the trusted-tier default — see skills/_shared/model-routing-defaults.md). The re-run carries identical question_body and question_ids parameters as the original dispatch — only the (provider, model) pair changes. The orchestrator increments the per-task citation_density_rerun_count field in this task's telemetry record (see skills/implement/SKILL.md § Per-Task Telemetry Emission).

Below-floor result (re-run also below floor). The validator emits a loud diagnostic naming the below-floor density value, exits non-zero (propagating the failure signal to the Implement orchestrator), and does NOT silently forward the below-floor output to downstream consumers. The non-zero exit is observably distinct from the success path — it is NOT a zero-exit-with-empty-body. The Implement orchestrator treats the non-zero exit as a specialist-dispatch failure and may retry on a different topic angle, escalate to opus, or proceed with degraded output per its BLOCKED escape hatch; the validator never silently degrades.

Implement's consumer-side wrap (telemetry rerun-count increment, non-zero-exit handling, BLOCKED escape) consumes this hook; pointer in skills/implement/SKILL.md § Specialist citation-density wrap.

Collation Subagent (verbatim extraction, not synthesis)

After all per-question research completes, dispatch a lightweight collation subagent whose ONLY job is to extract the ## Summary block from each q*.md file verbatim and assemble them into the staging file research/_collated.md (which the orchestrator subsequently renames to research/summary.md via a single mv Bash call). This is mechanical extraction — not synthesis, not re-prose. Each per-question report already carries a structured TL;DR / Key findings / Surprises / Caveats block at its head (see Per-Researcher Subagent template above); the assembled output is just those blocks stitched together in question order, plus a short Cross-References section.

Why a collation subagent (not orchestrator-direct, not synthesis):

  • Context hygiene. The collation subagent reads all per-question q*.md files into ITS context, then exits. Main chat never loads the full report bodies. If main chat did the collation directly, all q*.md contents would persist in main chat's conversation history and slow every downstream stage — Design proposes better architecture on a lean context.
  • No re-synthesis. Verbatim extraction is bounded mechanical work; re-prosing the per-question reports into one risks interpretive spin not present in the originals. The per-question ## Summary blocks are the canonical at-a-glance summary by contract.
  • Guardrail-compatible direct write via staging filename. summary.md matches the Claude Code 2.1.x subagent-guardrail blocklist (filenames whose stem starts (case-insensitive) with report, summary, findings, or analysis), so the subagent cannot Write to it directly. To avoid text-return (which would route the assembled content through main chat's context, defeating the hygiene goal), the subagent instead writes to a staging filename outside the blocklist — research/_collated.md — and the orchestrator then renames it to research/summary.md with a single mv Bash call. The mv adds only the command string and a tiny confirmation to main chat's context, not the file body. The public artifact name (summary.md) is unchanged, preserving all downstream references in other QRSPI skills.

Inputs to the collation subagent: All research/q*.md files. NO goals.md. NO questions.md. NO raw feedback/research-round-*.md files (raw feedback may carry user goals/intent — forwarding it breaks research isolation). On re-dispatch via Rejection path 1, the orchestrator passes a sanitized defect summary it authors itself from the user's feedback — bullet points covering collation-output defects in either dimension collation owns: extraction fidelity (e.g., "Q5 TL;DR was misquoted in the prior _collated.md") OR Cross-References authoring (e.g., "missing link between Q3 and Q7 findings"). Goal/intent-bearing language is stripped. The verbatim-extraction contract still binds — extraction-fidelity defects are fixed by re-extracting per the Procedure, NOT by paraphrasing.

Dispatch — dispatch(subagent_type: qrspi-research-collator, model: sonnet). The agent body (loaded by the runtime) carries the verbatim-extraction rules, the procedure, the output-file shape, and the contract-violation list. The dispatch prompt carries only the parameters below.

Dispatch parameters:

  • qfile_paths: list of absolute paths to research/q*.md files (passed as paths, not bodies — the collator Reads each file itself; this is required by the staging-filename + verbatim-extraction contract and keeps research bodies out of main chat's context)
  • output_path: absolute path to the staging file (<ABS_RESEARCH_DIR>/_collated.md) — NOT summary.md (the Claude Code 2.1.x subagent-guardrail blocks summary.md direct write; the orchestrator renames _collated.md → summary.md after the subagent returns, per the staging-rename pattern documented above)
  • defect_summary (re-dispatch via Rejection path 1 only): orchestrator-authored sanitized defect summary scoped to either dimension collation owns (extraction fidelity OR Cross-References authoring); goal-bearing/intent-bearing language stripped

Research-isolation invariant — the collator dispatch carries NO companion_goals and NO companion_questions. NO raw feedback/research-round-*.md files. The agent body refuses any of those if they appear in the dispatch prompt.

Orchestrator handling: When the collation subagent returns confirmation, run a single Bash call to rename the staging file to its final name: mv {ABS_RESEARCH_DIR}/_collated.md {ABS_RESEARCH_DIR}/summary.md. If the subagent returned a contract-violation report instead of writing _collated.md, re-dispatch the offending researcher (per the specialist dispatch above) with the orchestrator-authored sanitized defect summary, then re-dispatch collation. Isolation-violation handling is separate — see § Isolation-Violation Orchestrator Handling below.

Isolation-Violation Orchestrator Handling

All three research subagents (specialist, collator, reviewer) run a Pre-Flight Isolation Check on their incoming dispatch prompts (see the ## Pre-Flight Isolation Check (FAIL-LOUD) section in each agent body). If a goals-content or cross-question pattern is detected, the subagent does NOT write its expected output — it returns a single-line text response with the load-bearing prefix RESEARCH-ISOLATION-VIOLATION: followed by the pattern name and short evidence (≤80 chars).

Orchestrator detection: when any research subagent returns text instead of writing its expected file, inspect the first line for the prefix RESEARCH-ISOLATION-VIOLATION:.

Orchestrator response (fail-loud, not retry-with-same-leak):

  1. STOP the affected research dispatch — do NOT silently re-run with the same prompt (that produces an infinite refusal loop).
  2. Identify which dispatch parameter carried the leak. The violation message names the pattern: field-name-leakage ⇒ a forbidden parameter name (companion_goals, companion_questions, etc.) was attached; filename-leakage ⇒ a goals.md / questions.md payload was wrapped into the prompt; goals-heading-leakage / goal-framing-triplet ⇒ goals body content was smuggled into question_body, companion_qfiles, or defect_summary; cross-question-leakage ⇒ q*.md payloads from outside the assigned question_ids reached the specialist; questions-compendium-leakage ⇒ questions.md reached collator/reviewer; sanitization-bypass ⇒ the orchestrator-authored defect_summary still carried goal/intent prose.
  3. Repair the dispatch:
    • Field-name / filename / heading / triplet leakage: remove the offending parameter or strip the offending wrapped block; re-emit the dispatch.
    • Cross-question leakage: re-emit the specialist dispatch with question_body containing only the assigned IDs.
    • Sanitization-bypass: re-author the defect_summary from the raw feedback, stripping goal/intent prose more aggressively. If the raw feedback is entirely goal-bearing, surface the issue to the user (per Rejection Behavior step 3 edge case) rather than re-dispatching with an empty summary.
  4. Re-dispatch only after the prompt has been repaired.

Why this matters: the prior prose-only "report violation in your final confirmation" instruction relied on the subagent voluntarily noticing and surfacing the leak. The Pre-Flight check is structural — refusal happens before any goals-influenced research output can be produced. Pinned by tests/unit/test-research-isolation-fail-loud.bats.

Review Round

Compaction checkpoint: pre-fanout. Reviewer dispatch reads research/summary.md + every research/q*.md file + the agent-embedded reviewer protocol; saturated context produces shallow findings. See using-qrspi ## Compaction Checkpoints for the iron-rule contract.

Surface a todo: title Recommend /compact (pre-fanout) — research, description pre-fanout: reviewer dispatch reads research/summary.md + all q*.md files. User decides whether to /compact..

Apply the Standard Review Loop from using-qrspi/SKILL.md. Research has no scope-reviewer per canonical artifact-tree topology — only the quality reviewer runs (one Claude dispatch + one Codex dispatch when second_reviewer: true).

Dispatch the round through dispatch-agent's high-level entry. Run scripts/dispatch-agent.sh --step research --round ${ROUND} --artifact-dir <ABS_ARTIFACT_DIR> (plus the per-skill --output-dir/--artifact/--agents flags below). High-level mode invokes scripts/review-prep.sh to emit <ABS_ARTIFACT_DIR>/reviews/research/round-${ROUND}.diff and threads diff_file_path: into the reviewer prompt; the orchestrator runs no git diff Bash redirect of its own. Reviewer diff narrowing targets research/summary.md (the canonical review artifact); per-question research/q*.md files are read by reviewers as companion data via Read calls, not diff-narrowed. When the artifact directory is not inside a git repository, review-prep skips diff emission and diff_file_path: is omitted. For round 01 pass --base-ref <base-branch>; on round >= 2 review-prep auto-narrows by reading reviews/research/round-$((ROUND-1))-commit.txt (named diagnostics anchor-file-missing: / sha-format-invalid: halt before the SHA reaches git diff). Scope-tag narrowing (when active) reaches the reviewer as scope_hint: wrapped between <<<UNTRUSTED-SCOPE-HINT-START id=scope_hint>>> / <<<UNTRUSTED-SCOPE-HINT-END id=scope_hint>>> markers per the reviewer-protocol Reviewer Dispatch Contract.

The round's reviewers dispatch through the universal dispatch chain (scripts/dispatch-agent.sh → subagent fan-out → scripts/await-round.sh). Set the per-skill dispatch parameters below, then include the shared reviewer-dispatch prose. Include the *-codex peer tags in REVIEW_AGENTS only when second_reviewer: true; otherwise list only the *-claude tags.

REVIEW_STEP="research"
REVIEW_ROUND="${ROUND}"                                  # current review round (NN)
REVIEW_OUTPUT_DIR="<ABS_ARTIFACT_DIR>/reviews/research/round-${ROUND}/"
REVIEW_ARTIFACT="research.md"
REVIEW_AGENTS="quality-claude=qrspi-research-reviewer,quality-codex=qrspi-research-reviewer"

!cat skills/_shared/reviewer-dispatch-prose.md

Rejection Behavior

Because Research involves multiple subagents, rejection has two paths depending on user feedback. In both cases:

  1. The orchestrator writes the user's raw feedback to feedback/research-round-{NN}.md (see using-qrspi Feedback File Format) — this is the durable record. The raw feedback file is NEVER passed to a research subagent — that would break research isolation, since user feedback can carry goals or design intent.
  2. The orchestrator reads the feedback and authors a sanitized defect summary for subagent consumption: a bullet list of defects only, with all goal-bearing or intent-bearing language stripped. Each bullet states a defect ("X is missing", "Y is malformed", "Z citation is broken") — never a goal ("we need to X" / "for our auth refactor").
  3. The orchestrator passes the defect summary (not the raw feedback file) to the re-dispatched subagent(s) per the path below. Edge case: if after stripping goal/intent language the defect summary is empty (the user's feedback was entirely goal-bearing with no concrete defect cited), do NOT re-dispatch with an empty summary — surface the issue back to the user and ask them to reformulate their feedback as concrete defects.

Rejection path 1 — Collation problem ("the Cross-References miss an important link", "Q3's summary block is being misquoted in summary.md"):

  • Re-dispatch the collation subagent with the existing q*.md files + the orchestrator-authored defect summary scoped to either dimension collation owns: extraction fidelity OR Cross-References authoring. The subagent re-extracts ## Summary blocks verbatim (fixing any extraction-fidelity defects by re-extracting, not paraphrasing) and re-authors Cross-References to address the cited defects.

Rejection path 2 — Underlying research problem ("Q3's findings are incomplete — the researcher missed the auth module", "Q5's summary block doesn't match the contract template"):

  • Re-run only the specific researcher(s) whose findings or summary blocks were problematic, passing the orchestrator-authored defect summary scoped to those defects. Researchers re-write their q*.md directly per the Per-Researcher template.
  • Then re-dispatch the collation subagent to rebuild summary.md from the updated per-question reports.

Ask the user which path applies when they reject.

Human Gate

Present research/summary.md to the user. Note that this is ~200 lines — much easier to review than code. Always state the review status when presenting: either "Reviews passed clean in round N" or "Reviews found issues in round N which were fixed but not re-verified."

On approval, if reviews have not passed clean, note this and ask if they'd like a review loop before finalizing. Then write status: approved in frontmatter.

Quick-Fix Auto-Approve Branch

When config.md carries pipeline: quick, the human-approval gate is skipped after any review round (initial or post-fix) that produces zero kept findings. When this branch fires, status: approved is written to research/summary.md frontmatter automatically without waiting for user input.

Verifier-gate precondition. "Zero kept findings" is satisfied only when the verifier has affirmatively confirmed the count — a vacuously-zero count from an undispatched verifier does NOT satisfy the gate and surfaces the round to the user as unverified (matching the HARD-GATE contract in skills/implement/SKILL.md). If config.md is missing or unreadable when this branch is evaluated, the auto-approve branch does NOT fire — the orchestrator surfaces a named diagnostic and falls through to the standard human-approval gate (fail-loud, not silent fallback to either pipeline mode). The gate passes when ANY of the following hold for the current round's directory (reviews/research/round-NN/):

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
38
Forks
4
Last commit
Sep 2026
Hacker News mentions
20
Advanced
Item type
skill
Key
research-dfrysinger
Source
github.com/dfrysinger/qrspi-plus