# Query types

> QUERY_TYPE classification (GENERAL, NEWS, PROMPTING, RECOMMENDATIONS, COMPARISON), intent parsing, and engine pre-flight refusal paths.

- Repository: mvanhorn/last30days-skill
- GitHub: https://github.com/mvanhorn/last30days-skill
- Human docs: https://grok-wiki.com/public/docs/mvanhorn-last30days-skill-50b5421a8cca
- Complete Markdown: https://grok-wiki.com/public/docs/mvanhorn-last30days-skill-50b5421a8cca/llms-full.txt

## Source Files

- `skills/last30days/SKILL.md`
- `skills/last30days/scripts/lib/preflight.py`
- `skills/last30days/scripts/lib/planner.py`
- `skills/last30days/scripts/lib/categories.py`
- `skills/last30days/scripts/lib/query.py`

---

---
title: "Query types"
description: "QUERY_TYPE classification (GENERAL, NEWS, PROMPTING, RECOMMENDATIONS, COMPARISON), intent parsing, and engine pre-flight refusal paths."
---

`/last30days` classifies each user topic twice: the hosting model assigns a **QUERY_TYPE** for synthesis shape and WebSearch supplements (`skills/last30days/SKILL.md`), while the Python engine assigns a planner **intent** for sub-queries, source weights, freshness, and clustering (`skills/last30days/scripts/lib/planner.py`). A separate engine refuse-gate in `preflight.py` blocks Class 1 demographic-shopping topics before any pipeline work runs.

## Two classification layers

| Layer | Where it runs | Identifier set | Primary effect |
|-------|---------------|----------------|----------------|
| Skill / harness | Model before engine (Step “Parse User Intent”) | `GENERAL`, `NEWS`, `PROMPTING`, `RECOMMENDATIONS`, `COMPARISON` | Branded status line, WebSearch supplement queries, synthesis template (LAWs 2/4), invitation copy |
| Engine planner | `plan_query()` in `planner.py` (or `--plan` JSON from Step 0.75) | `factual`, `product`, `concept`, `opinion`, `how_to`, `comparison`, `breaking_news`, `prediction` | Sub-query fan-out, `freshness_mode`, `cluster_mode`, per-source weights and quick-depth caps |

The skill layer and engine layer are related but not identical. A `RECOMMENDATIONS` slash query often becomes a `product` or `opinion` plan intent; `PROMPTING` queries align with `how_to` planner behavior (YouTube/tutorial weighting). `COMPARISON` is the one type where both layers share the same name and force a deterministic per-entity plan when two or more entities parse from `vs` / `versus` / `/` splits.

```mermaid
flowchart LR
  subgraph harness["Harness (SKILL.md)"]
    U[User topic] --> PI[Parse intent → QUERY_TYPE]
    PI --> PF45[Step 0.45 keyword-trap check]
    PF45 --> PF5[Step 0.5 / 0.55 flags]
    PF5 --> P75[Step 0.75 → QueryPlan JSON]
  end
  subgraph engine["Engine (last30days.py)"]
    P75 -->|"--plan"| RUN[Pipeline]
    PI -->|positional topic| PRE[preflight.check_class_1_trap]
    PRE -->|REFUSE exit 2| STOP[No pipeline]
    PRE -->|pass| RUN
    RUN --> PLAN[plan_query / _infer_intent]
    PLAN --> SRC[Source fan-out + fusion]
  end
  SRC --> SYN[Synthesis by QUERY_TYPE template]
```

## QUERY_TYPE (skill contract)

The model parses the user message **before** engine invocation and stores `TOPIC`, optional `TARGET_TOOL`, `QUERY_TYPE`, and for comparisons `TOPIC_A` / `TOPIC_B`. Do not emit a multi-line “Parsed intent” block; show the branded one-liner instead.

| QUERY_TYPE | Trigger patterns (from user text) | Status line shape | Synthesis shape |
|------------|-----------------------------------|-------------------|-----------------|
| **PROMPTING** | “X prompts”, “prompting for X”, “X best practices” | `searching … for what people are saying about {TOPIC}` | Badge → `What I learned:` → KEY PATTERNS; invitation offers copy-paste prompt help for `TARGET_TOOL` |
| **RECOMMENDATIONS** | “best X”, “top X”, “what X should I use”, “recommended X” | Same as non-comparison types | Signal-weighted picks (not raw mention counts); invitation suggests compare/explain picks |
| **NEWS** | “what’s happening with X”, “X news”, “latest on X” | Same | Current-events narrative; WebSearch targets news/announcements |
| **COMPARISON** | “X vs Y”, “X versus Y”, “compare X and Y” (split on spaced ` vs ` / ` versus `) | `comparing {TOPIC_A} vs {TOPIC_B} across …` | Required `# {A} vs {B}` title and fixed `##` sections (`Quick Verdict`, per-entity, `Head-to-Head`, etc.); skips `What I learned:` |
| **GENERAL** | Default when no other pattern matches | Same as non-comparison types | Badge → `What I learned:` → KEY PATTERNS; broad understanding |

<Note>
The stored-variable line in SKILL.md lists `HOW-TO` among allowed `QUERY_TYPE` values, but classification rules and LAWs throughout the file use **PROMPTING** for technique-and-prompt workflows. Treat **PROMPTING** as the operative label for those queries.
</Note>

**TARGET_TOOL** is parsed from `[topic] for [tool]` or `[topic] prompts for [tool]`. The skill explicitly does **not** ask for a missing tool before research; it runs first, then may ask after results.

**Common pattern shortcuts:**

- `best [topic]` / `top [topic]` → `RECOMMENDATIONS`
- `X vs Y` (with spaces) → `COMPARISON` with entity split
- `[topic] for [tool]` → tool captured, type otherwise inferred

## Engine planner intents

When no `--plan` JSON is supplied, `plan_query()` calls `_infer_intent()` with ordered regex guards, then builds a `QueryPlan` (`schema.QueryPlan`: `intent`, `freshness_mode`, `cluster_mode`, `raw_topic`, `subqueries`, `source_weights`, `notes`).

| Intent | Detection signals (topic, lowercased) | Default `freshness_mode` | Default `cluster_mode` | Source bias (examples) |
|--------|--------------------------------------|--------------------------|------------------------|-------------------------|
| `comparison` | `vs`, `versus`, `compare`, slash-separated proper nouns (`React/Vue`) | `balanced_recent` | `debate` | Reddit, X, HN, YouTube |
| `prediction` | odds, predict, forecast, probability | `strict_recent` | `market` | Polymarket +2.5 weight |
| `how_to` | how to, tutorial, guide, setup, deploy, install | `evergreen_ok` | `workflow` | YouTube +2.0 |
| `factual` | what is/are, who is, release date, parameter count | `balanced_recent` | `none` | HN, Reddit, X |
| `opinion` | thoughts on, worth it, should I, review | `balanced_recent` | `debate` | Reddit, X, YouTube |
| `breaking_news` | latest, news, announced, launched; sports/ceremony keywords; trending/this week | `strict_recent` | `story` | X +1.5, Reddit +1.3 |
| `product` | pricing, feature(s), best/top … for | `balanced_recent` | `none` | YouTube, Reddit, TikTok |
| `concept` | explain, protocol, architecture; **default fallback** | `evergreen_ok` | `none` | HN, Reddit, X |

Comparison topics with ≥2 extracted entities skip LLM planning and use `_fallback_plan()` with per-entity sub-queries (`_should_force_deterministic_plan`).

Allowed intents are validated in `ALLOWED_INTENTS`; invalid LLM output is coerced or replaced by the deterministic fallback. Quick depth applies `SOURCE_LIMITS` caps (typically two sources per intent); default depth searches all configured sources.

## Query preprocessing (`query.py`)

`query.py` does not assign QUERY_TYPE. It supplies shared preprocessing for search modules:

- **`PREFIXES` / `SUFFIXES` / `NOISE_WORDS`** — strip meta-phrasing (“what are the best”, “prompting techniques”, “vs”, “news”, etc.) from platform queries.
- **`extract_core_subject()`** — compact keyword core after prefix/suffix/noise removal.
- **`extract_compound_terms()`** — hyphenated and Title Case multi-word phrases for quoting in planner `_keyword_query()`.

Planner and Reddit modules call these helpers so literal user phrasing does not get echoed verbatim into APIs (the documented “Hermes Agent use cases” failure mode).

## Category peers (`categories.py`)

`detect_category()` and `peer_subs_for()` are orthogonal to QUERY_TYPE. They map product-shaped topics to curated category-peer subreddits (AI image gen, coding agents, prediction markets, etc.) for Step 0.55 Reddit targeting. First pattern match wins; this is code-reviewed data, not user-editable config.

## Pre-flight: skill vs engine

Pre-flight runs at two boundaries with different coverage.

### Step 0.45 (skill — model-side)

Mandatory before Step 0.5. The model classifies keyword-trap **classes** and may stop for a clarifying question:

| Class | Pattern | Action |
|-------|---------|--------|
| **1** Demographic shopping | gift/present for age/gender/relationship; best X for men/women/kids | Ask hobbies / relationship / budget **or** reframe + gift subreddits; do not run engine on literal phrase without user OK |
| **2** Numeric / age trap | Bare numbers that collide (42, 40, 50) | Strip number from engine query unless semantically load-bearing |
| **3** Overly-literal tutorial | “how to use X”, “tutorial for Z” | Reframe to discussion vocabulary (“Docker tips workflows”) |
| **4** Generic single noun | `bread`, `sneakers` without hook | Ask which facet before running |

Emit `Pre-Flight: …` before the Resolved block. If the action is a clarifying question, **stop** until the user responds. Inline qualifiers on Class 1 (budget, hobbies, “cooking-obsessed”) skip the question and proceed with reframe.

### Engine refuse-gate (`preflight.py`)

Only **Class 1** is enforced in Python at `main()` before `ProgressDisplay` starts:

```text
topic → check_class_1_trap()
  ├─ no Class 1 match → continue pipeline
  ├─ Class 1 + qualifier (budget, hobbies, $, activity noun) → continue
  └─ Class 1 bare demographic → stderr REFUSE, exit code 2
```

<ParamField body="LAST30DAYS_SKIP_PREFLIGHT" type="env (optional)">
Set to any non-empty value to bypass the engine gate. Use only when the user insists on running the literal demographic-shopping phrase anyway.
</ParamField>

The REFUSE message includes `Class 1`, the echoed topic, and instructions to ask for hobbies, relationship, or budget, then re-run. Tests in `tests/test_preflight.py` lock match/skip behavior (e.g. `birthday gift for 40 year old` refuses; `gift for my cooking-obsessed husband` passes).

Classes 2–4 exist only in SKILL.md; the engine does not regex-block them. The hosting model must reframe before invocation.

## WebSearch supplements by QUERY_TYPE

After the engine returns (Step 2), supplement queries differ by skill QUERY_TYPE (2–3 searches, exclude reddit.com/x.com):

| QUERY_TYPE | Supplement focus |
|------------|------------------|
| RECOMMENDATIONS | `best {TOPIC} recommendations`, lists, popular examples |
| NEWS | `{TOPIC} news 2026`, announcements |
| PROMPTING | `{TOPIC} prompts examples`, techniques/tips |
| GENERAL | `{TOPIC} 2026`, discussion |
| COMPARISON | Follow comparison-mode workflow; per-entity engine runs may apply |

Use the user’s exact terminology; do not substitute model-known aliases unless disambiguation is required.

## Output contract differences

LAWs 2 and 4 split on QUERY_TYPE:

- **GENERAL / NEWS / PROMPTING / RECOMMENDATIONS:** No invented title; no `##` body headers; body opens with `What I learned:` after the version badge.
- **COMPARISON:** Required markdown title and mandated `##` sections; engine may emit a `## Head-to-Head` table scaffold to fill verbatim.

RECOMMENDATIONS synthesis must rank by signal quality, not mention frequency. PROMPTING invitations reference concrete techniques from research and offer paste-ready prompts when `TARGET_TOOL` is known.

## Passing intent into the engine

On WebSearch-capable harnesses, Step 0.75 expects the model to emit JSON aligned with `QueryPlan` and pass `--plan`. Intent in that JSON should reflect the topic (often mirroring QUERY_TYPE, e.g. `breaking_news` for NEWS, `how_to` for PROMPTING). Without `--plan`, stderr warns (LAW 7) and `_infer_intent()` supplies a deterministic plan—acceptable for headless/cron, weaker for agent-hosted runs.

Engine-internal competitor fan-out sets `internal_subrun=True` on `plan_query()` to suppress the false-positive “No --plan passed” warning.

## Verification signals

| Check | Expected signal |
|-------|-----------------|
| Class 1 trap (bare) | `[last30days] REFUSE: … Class 1` on stderr; exit `2`; no emoji-tree footer |
| Class 1 with hobby/budget | Engine runs normally |
| Comparison topic | Status line uses “comparing A vs B”; synthesis uses comparison `##` template |
| Skill pre-flight only | `Pre-Flight: topic matches Class N` in model output before bash |
| Planner fallback | `[Planner] No --plan passed` on stderr when hosting model skipped Step 0.75 |

## Related pages

<CardGroup>
  <Card title="Skill contract" href="/skill-contract">
    STEP 0 stale-clone guard, pre-flight protocol, engine invocation, and LAW anchors that depend on QUERY_TYPE.
  </Card>
  <Card title="Research pipeline" href="/research-pipeline">
    How planner intents drive sub-query fan-out, fusion, clustering, and depth profiles after classification.
  </Card>
  <Card title="Comparison mode" href="/comparison-mode">
    Competitor discovery, per-entity targeting, and comparison synthesis when QUERY_TYPE is COMPARISON.
  </Card>
  <Card title="Prompting recipes" href="/prompting-recipes">
    PROMPTING workflows, KEY PATTERNS shape, and copy-paste prompt patterns.
  </Card>
  <Card title="Output contract" href="/output-contract">
    Badge line, LAWs voice rules, emit modes, and footer pass-through by query type.
  </Card>
</CardGroup>
