Skip to content
Development
Skill

/research-workflow

Dynamic Workflow asset for /lets:research --workflow. Not auto-triggered - a workflow script invoked via scriptPath by /lets:research. Holds the off-context per-sub-question web research -> per-claim skeptic cross-check -> synthesize chain.

From plugin
lets-workflow
1719 skills15 agents25 commands
Install
$ npx -y skills add restarter/lets-workflow --skill research-workflow --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/research-workflow

Context preview

The summary Claude sees to decide when to auto-load this skill.

Dynamic Workflow asset for /lets:research --workflow. Not auto-triggered - a workflow script invoked via scriptPath by /lets:research. Holds the off-context per-sub-question web research -> per-claim skeptic cross-check -> synthesize chain.

SKILL.md

research-workflow.SKILL.md
name: research-workflow
description: Dynamic Workflow asset for /lets:research --workflow. Not auto-triggered - a workflow script invoked via scriptPath by /lets:research. Holds the off-context per-sub-question web research -> per-claim skeptic cross-check -> synthesize chain.
user-invocable: false

research-workflow (Dynamic Workflow asset)

A Dynamic Workflow asset (see `CLAUDE.md` -> "Dynamic Workflow Assets"; `review-workflow` is the reference example). `research.workflow.js` is executed by the `Workflow` tool, invoked from `/lets:research`'s Workflow Mode via:

Workflow({ scriptPath: "${CLAUDE_PLUGIN_ROOT}/skills/research-workflow/research.workflow.js", args })

`${CLAUDE_PLUGIN_ROOT}` is substituted at command-load time, so `research.md` carries the literal absolute path. Treat `research.workflow.js` as a template the command points at, not a script to reproduce inline.

Why this is a transparent performance lever (not autonomous)

`/lets:research --workflow` runs the SAME stages as the standard Task path - only the per-sub-question search dumps and per-claim verdicts stay off-context. The decompose (break the question into 3-6 sub-questions) stays IN-CONTEXT in `research.md` so the user can steer the angles; the per-sub-question web research and the per-claim cross-check move into the workflow (off-context); the final synthesis happens in-context after the aggregate returns. The standard path runs the equivalent stages in-context: per-sub-question research fanned out across multiple DEFAULT web Task subagents, per-claim `lets:skeptic` via Task. **Crucially:** the verify stage is a **cross-check** (single-source / contradicted / low-confidence flags), never a claim that the fact is confirmed correct - the skeptic has no web tools and cannot re-fetch URLs.

What it does (off-context)

1. **Research** - per-sub-question fan-out via the DEFAULT web subagent (no `agentType`; the default workflow subagent CAN WebSearch/WebFetch - `lets:*` agents have `tools: Read, Grep, Glob, Bash` and CANNOT). Each subagent returns its 2-5 strongest claims (`FINDING_SCHEMA`: claim, evidence, sources, confidence) with `evidence` carrying QUOTED/paraphrased source material (the downstream skeptic judges "unsupported" against this string and has no web tool), and self-reports `used_web_search=false` rather than fabricate. `mergeClaims` dedupes by normalized claim text, unioning sources and keeping the highest confidence. 2. **Verify** - per-claim `lets:skeptic` RESEARCH-VERIFY pass over the merged claims. The skeptic is handed each claim's evidence + its siblings (same sub-question) and flags STRUCTURAL weakness: **unsupported** (evidence does not back the claim) or **contradicted** (conflicts with a sibling). Single-source / low-confidence are computed deterministically by the script (those claims skip the skeptic - the deterministic flag already fires). `applyVerdicts` attaches a `flagged[]` array per claim - **ADDITIVE, never drops a claim** (research facts accumulate; departure from review's drop rule). Contradiction flagging is the skeptic's job comparing siblings - there is deliberately NO deterministic contradiction pass.

`args` contract (built by `/lets:research`, passed as a JSON value)

| key | type | meaning | |---|---|---| | `question` | string | the original research question | | `subQuestions` | `[string]` | 3-6 sub-questions from the in-context decompose (a narrow question is a single entry) | | `projectEnabled` | boolean | `--project` was passed - ground findings against this repo | | `projectContext` | string (only if `projectEnabled`) | the in-context Step 2.5 repo grounding block | | `projectRoot` | string | absolute project root (the read boundary; fetched content never widens it) | | `claudeMd` | string (only if `projectEnabled`) | CLAUDE.md context | | `asOf` | string `YYYY-MM-DD` | REQUIRED - the as-of date; the workflow can't compute the date (no `Date.now`) |

Returns

`{ claims[], sources[], as_of, web{status}, counts{sub_questions, sub_questions_failed, claims, flagged, verify_failed} }`. `claims[]` are merged + per-claim `flagged[]` (empty `flagged` == cross-checked and clean); the command synthesizes the prose answer from these (there is **no `answer` key**). `sources` is the deduped `[{title,url}]` list to surface. `web.status` is `ok` | `empty` | `no_tool` | `failed`: anything other than `ok` means the command MUST open with the NO-LIVE-SOURCES banner (`NO LIVE SOURCES - model knowledge as of <cutoff>`) and NEVER fabricate URLs. **`counts.verify_failed` is an INTEGER** - the number of claims whose cross-check was attempted but errored; those claims carry `unverified (cross-check errored)` and are kept UNVERIFIED (not validated) - anti-silent-fail, the command should tell the user "N of M claims kept unverified". `counts.sub_questions_failed` flags partial coverage. Anti-silent-fail: `counts.claims === 0` means every sub-question errored / no web data - the command must offer the standard path, never render a fabricated result.

Constraints (Dynamic Workflow runtime)

  • No filesystem - the script returns data; `/lets:research` renders + records.
  • No sibling `import` - all logic stays inline.
  • No `Date.now()` / `Math.random()` / `new Date()` - the as-of date comes via `args.asOf`.
  • Top-level `await`/`return` are used, so the file is NOT Node-importable - no committed unit test; the pure logic (`mergeClaims`, `applyVerdicts`, `collectSources`) is deterministically checkable on a copy, and the chain is kept in sync with `research.md` (Step 3 / Step 4 / W4) prose by discipline + validated by the live smoke test.

Keep-in-sync notes (do NOT "simplify" these away)

  • **Single `lets:skeptic` per claim is INTENTIONAL** - do NOT collapse to one holistic batch skeptic with an array schema; that breaks `skeptic.md`'s `{real, confidence, reason}` single-object contract.
  • **No deterministic contradiction pass** - contradiction flagging is the skept
Read more
Ships withlets-workflow

A development workflow plugin for Claude Code Stop babysitting your AI. Start shipping with it.

Get the whole plugin

Other skills on lets-workflow.