--- name: intent-router description: >- Converges an underspecified request into a typed IntentSpec before planning or acting. Use when the user asks to implement, add, change, refactor, fix, migrate, configure, handle, triage, sort out, look into or decide something and the request leaves decisions open (which objects, which approach, what happens on failure, which trade-off), contradicts itself, admits two readings, or rests on an approach its sources may rule out — in a codebase, a ticket queue, a research brief or a runbook — or when the user says "clarify the intent", "what do you need from me", or invokes intent-router. Looks up what its sources hold (code, history, decision records, a ticket log, an order record, the policy in force) before asking; asks only preference or irreversible questions, one at a time, with a recommended default; halts instead of guessing; checks the delivered work against the spec. Silent on a fully stated task its sources do not contradict. Not for explaining existing state ("what does X do", "why is Y slow"). license: MIT metadata: version: "1.3.0" author: angel291592 homepage: https://github.com/angel291592/Intent-Router --- # Intent-Router A compiler does not guess the address of an undefined symbol. Do not guess the user's intent. This runs one layer *before* planning, routing or coding. It converges a request into an `IntentSpec` — a typed, machine-readable statement of what is actually being asked — and hands that off. It does not implement anything itself. ## 1. Purpose and when this fires Three passes, in compiler order: **Parse** the request into a draft spec, **Resolve** every open question by looking it up or asking, then **Typecheck and emit** — route, ask, or halt. Start when **both** hold: 1. The request is a *do-something* request: implement, add, change, refactor, fix, migrate, configure, handle, triage, sort out, look into, decide, write, set up, wire up, rename, remove, upgrade, optimise. 2. A quick scan finds **at least one decision-bearing unknown** — an answer that would change which files are touched, which approach is taken, how failures behave, or what counts as done. Do not start when: the request asks you to explain existing state ("what does X do", "why is Y slow") rather than to find something out and produce a deliverable; the silence check below passes and its premise check finds no contradiction — every decision-bearing item is already stated, so there is nothing to converge; or the request is trivially scoped and reversible (fix a typo). **Look for a saved contract first.** Before probing, look for earlier work on this same request: `/.intent/.intent.yaml`, stem = the `intent` slug, matching on `request`/`objects`. Its `verification_status` decides: - `asked` — its `source: asked` constraints are settled decisions: carry them in and never ask those questions again; only answers received now count in `resolution.asked`. - `routed` — premise-check it (below), carry it out, then verify it. `verified` — carried out already: do not reopen unless the user asks for a redo, which marks the old file `superseded` and starts a new `intent`. `superseded`, or absent on an older file — ignore it, or use it only to know what was asked and done. Inherit by source, never wholesale: the file is not a probe surface (section 2). `asked` carries over; `probed` is re-checked at its `evidence` pointer and drops to `unknown` (`kind: probe`) when it no longer holds; `inferred` is never inherited — decide it again, and if it is still an inference keep `source: inferred` and mark it visibly again. Never relabel an inherited value `explicit` or `probed`. **The silence check.** Run it once, against the request text alone, before probing anything. The request is fully specified when all four hold: 1. **Objects** — it names what is acted on, precisely enough to enumerate: endpoints, files, records, tickets. A collective noun ("the user API", "this customer") does not qualify. 2. **Approach** — it names the method or dependency to use, or rules the alternatives out. 3. **Failure behaviour** — for every step it asks for that can fail, a rule is stated. **A rule stated once covers the cases it subsumes**: "on invalidation failure serve uncached" is stated — you do not reopen it for read failures, write failures or timeouts the request did not separately enumerate. A step whose failure the request is *silent* about is unstated. 4. **Acceptance** — it names what counts as done: an observable end state one can check to declare the work complete ("done when both endpoints serve from cache and the suite passes"). **A parameter the work must use is not a done condition** — a TTL, a limit or a response shape bounds the work without saying when it is done, and naming one does not close this item. If all four hold, make **one premise check** before staying out of the way: at most two lookups at the sources most likely to rule out what the request states — a decision record or history entry about the named approach, the manifest entry for a named dependency, the definition of a named object. If nothing you open contradicts the request, **do not run**: emit no spec, no fence, no announcement that you considered this skill — carry the request out as ordinary. A spec emitted after a passed silence check is a false positive, costing more than this skill saves. If a source does contradict it (tried and reverted, pinned or removed, does not exist), run, with that contradiction as a `premise` issue (section 3); an approach you merely prefer is never one. If any one of the four is unstated, run — and do not downgrade an unstated item to "inferable" because a plausible default exists: if the user had to be trusted with the outcome, or the spec could be wrong without contradicting the request, it is unstated. **No-ask mode.** If the user says "no questions", "just do it", "don't ask me anything", keep looking things up but never ask: whatever stays open is recorded with `source: inferred` and its reasoning. If an inferred item touches an irreversible boundary, halt and name the field rather than guessing it — the one thing worse than a question is a silent irreversible choice. Inference may fill in **how** something is done; it may never invent **what** is being asked for. If the request itself is unclear — it names neither the object nor an observable outcome ("make it better" with nothing to make better) — no-ask mode halts with `cause: underspecified` and the open fields, rather than inferring three concrete improvements and routing them as the user's intent. This halt is specific to no-ask mode: when asking is allowed, the same unclear request is answered with a question, not a halt. **Explicit invocation.** When the user names this skill in any way, start unconditionally and treat the text after the name as the request. ## 2. Vocabulary - **unknown** — a field of the spec whose value is not yet established. - **decision-bearing** — an unknown whose answer changes the files touched, the approach, the failure behaviour, or the acceptance criteria. Everything else is an implementation detail and is left to whoever executes. - **required field** — a field the spec cannot be emitted without: `intent`, `objects`, and every constraint needed to act without guessing. - **inferred** — a value the model supplied itself. Always visible, always evidenced, always cheap for the user to veto in one line. - **evidence** — a pointer to where a value came from: one **token with no whitespace**, naming something you actually opened, in one of these forms only: `path`, `path:line`, `path:line-line`, `path#heading`, `git:`, `git:#`, `user:delegated` (a decision handed back), `record:/`, `doc:#
`. Not evidence: the repository root (`.`), anything under `.git/`, `.claude/`, `.agents/` or `.intent/`, the harness config file, and above all a reasoning sentence — that goes in `text`. Anything else is a defect, even if it exists. - **probe surface** — a place in the workspace where objective answers live: manifests, route definitions, configuration, tests, CI, version history, decision records. - **ASK budget** — the hard cap on questions for one request. Default **3**. - **irreversible** — a decision that cannot be walked back once shipped, because clients, data or users will depend on it. ## 3. Pass 1 — Parse Produce a draft spec. Do not emit it, do not act on it. 1. Record the user's own words in `request`, truncated to 500 characters. Never paraphrase: the point is that a reviewer can compare the spec against what was actually said. 2. Normalise the action into `intent`, a snake_case verb-object identifier: `add_caching`, `migrate_auth`, `rate_limit_signup`. 3. List what the intent acts on in `objects` — files, endpoints, modules, jobs, records. If the request does not name them, leave it empty for now; this is an unknown, not a licence to pick. 4. Copy every constraint the user stated into `constraints` with `source: explicit`. A stated constraint is never re-derived, and is questioned for one of two reasons only — it collides with another stated constraint, or a source you opened contradicts it (the `conflict` and `premise` issues below). 5. Enumerate the unknowns. Each gets a stable `field` name, a `category` from the table below, and a judgement: **decision-bearing or not**. In the emitted spec an unknown item carries exactly `field`, `kind`, and optionally `category`, `issue` and `note` — never a `decision_bearing` flag, a free-form `detail`, or any other key; the judgement itself is expressed by keeping the item out of `unknown` when it is not decision-bearing. | category | the question it asks | example in code | example outside code | |---|---|---|---| | `scope` | what is acted on, and what is explicitly out | which endpoints get cached | which orders in this account are in scope | | `approach` | which method or dependency; what is mandatory or forbidden | existing Redis client or a new in-process cache | goodwill credit, or a carrier claim | | `data_compatibility` | data shape, interface contract, backward compatibility | may the response shape change | may the reply change the date already promised | | `failure_behavior` | behaviour on error, degradation or empty state | on invalidation failure, serve stale or uncached | if the refund is declined, hold the ticket or escalate | | `acceptance` | what counts as done, measurably | which TTL matches the repository's convention | what closes the ticket — customer confirmation, or the SLA timer | | `non_goals_constraints` | explicit exclusions, hard limits on time, cost, compliance | no new dependencies | no commitment beyond the policy in force | When the request touches any step that can fail, be rejected, or half-complete — a cache write, a refund, a backfill, a notification, an approval — **and states no rule for that failure**, enumerate the failure path as its own unknown: not "what should the feature do" but "what should happen when the feature's own step fails" — a cache write that errors, an invalidation that misses, a dependency that times out. Skipping it while emitting a happy-path spec is the guess this pass exists to prevent; and when the request **does** state the failure rule, that is an `explicit` constraint, never an unknown — re-opening it to split sub-cases the user did not distinguish is the over-asking this skill exists to remove. **Three defects that filling in cannot fix.** Check the request for these before resolving anything; each one found becomes an `ask` unknown carrying `issue`, asked before any other unknown, because these are what the user would veto the work over. - `ambiguous` — the request reads two ways that change the deliverable, and no source settles which ("make it cheaper for mobile": fewer bytes, or fewer requests). Look first — an inventory often settles it; if it does not, the options are the readings, not ways to carry out one. - `conflict` — two things the user stated cannot both hold ("cache it", "always serve the latest write", "add no invalidation"). The options say which one yields. - `premise` — a source you opened contradicts something the user stated: the approach was tried and reverted, a named dependency is pinned or removed, a named object does not exist. It surfaces while probing. Keep the user's words as the `explicit` constraint, add the contradicting fact as a `probed` constraint with its evidence, and ask whether to go ahead as stated or as the source says. In no-ask mode an `ambiguous` or `conflict` issue halts with `cause: underspecified`: choosing a reading is choosing *what*, which inference never may. A `premise` issue does not halt: the user's words decide, the unknown closes as an `inferred` constraint whose evidence is the contradicting source, and one sentence after the fence names the contradiction — unless going ahead as stated crosses an irreversible boundary, which halts. Unknowns that are **not** decision-bearing do not enter Pass 2. Note them in `trace` and move on; resolving them is the executor's job, not a reason to spend a question. ## 4. Pass 2 — Resolve This pass is the whole point. Everything else is bookkeeping. ### 4.1 The iron law > If an objective answer exists and you have any means to reach it, PROBE. Never ASK. > ASK is reserved for answers that live in a person's head (preferences, priorities) or > decisions that cannot be walked back. Classify every decision-bearing unknown as `probe` or `ask` **before** saying anything to the user. A question whose answer was sitting in the workspace is a defect, not a courtesy. ### 4.2 PROBE Name the sources first. A code workspace → the surfaces below; anything else (a ticket queue, a records system, a policy archive, a notes collection, a candidate registry) → load `references/domains.md` and use that domain's table. Use whatever file-reading, search, or shell capability your environment provides, version-control history included; if it exposes none, see *degraded* below: an absent capability is a fact about the environment, never a gap in the request. Work the probe surfaces in this order, stopping as soon as the unknown is settled: 1. **Dependency and package manifests** — what is already available, and what was deliberately pinned or removed. 2. **Entry points, route, command and job definitions** — the real inventory of what exists. For any scope unknown ("which endpoints / commands / jobs"), this surface is authoritative: read the definition file itself — an inventory reconstructed from commit messages, docs or another module's imports is not evidence of what exists today. 3. **Existing implementations of the same kind** — the pattern the repository already chose. 4. **Configuration, constants and environment templates** — values that are conventions, not opinions. 5. **Tests and CI configuration** — the contract that is already enforced. 6. **Version history** — commits, reverts and pull-request numbers carry reasons no current file shows. Through a shell, try the command once before calling it unavailable and record the try in `trace`; with no shell, read the history files on disk (reference log, stored commit message). Evidence is `git:` or `git:#`, never a path in the history store. Decision records, changelogs, and any agent instruction file the project ships are covered in `references/probe-surfaces.md`, together with the surfaces for other ecosystems. **Budget: at most 3 probe actions per unknown**, plus the one reserved history query below, which does not count against it. Do not read the whole repository. If an unknown survives its budget, escalate it: to `ask` if a person could answer it, otherwise leave it in `unknown` with `kind: probe` so the halt names it. **A dangling reference is not a settled unknown.** A *what*-only answer — a changelog line, comment, config value or record field naming a pull-request number, "revert", "pin" or "workaround" with no reason — has not settled it. Follow the reference once as the **reserved history query**, outside the 3-action budget, then stop; if the reason is still missing, reclassify it `ask`. **Every probed value carries evidence.** A `probed` or `inferred` constraint needs an `evidence` pointer — one per field, never two comma-joined (the second gets its own constraint or trace entry). Record facts you were not looking for when they constrain the work, and point at every probed object. **Degraded.** When a probe fails for an environmental reason — no capability, a command error, a timeout — record it in `trace` as a failed lookup and keep the field's `kind: probe`. If the run ends without enough information and the cause is failed lookups rather than an underspecified request, halt with `cause: degraded` and an `error` that names what failed. Never present a broken environment as a vague request, or the reverse. **An empty probe surface is not a failed lookup.** `degraded` means a lookup was *attempted and failed*; an empty repository, or a request naming no existing code, failed nothing. Reclassify the affected unknown as `kind: ask` and ASK: a person can still say what this should become. ### 4.3 ASK Only for answers that live in a person's head, or decisions that cannot be walked back. **Ask order.** When several unknowns are askable, ask the one whose answer the user would veto the work over first — an observable behaviour or contract (what happens on failure, what the response looks like, what is in scope) before internal placement (which layer, which file, which module), because internal placement is the executor's call and may dissolve once the behavioural answer is known. Never pick a question for being easy to answer. The section 3 issues come before all of these. **What is never worth a question** — internal structure: which layer or file hosts the logic, which function names to use, how to organise the code. These are reversible implementation details; whoever executes decides them. If the only remaining unknown is internal, the spec is sufficient — infer it visibly and route. **Eliminating options is not resolving the unknown.** If only one option remains *because you ruled the others out by reasoning* — "stale reads are unacceptable, so fall through is the only choice" — the surviving option is itself the preference the user should confirm (fail fast with 5xx, serve uncached, queue and retry…). Infer it only when the user's own words or the workspace state it; otherwise ASK. - **Budget: 3 questions per request** by default. The user may override ("ask up to 5"); record whatever cap is in force as `resolution.ask_budget`. In no-ask mode the budget is `0`. - **One question at a time.** Wait for the answer before asking the next; do not preview what else you might ask, and do not batch — a list puts the sorting work back on the user. - Each question has exactly this structure: - **Question** — the full question, answerable without scrolling back. - **Why you, not me** — one sentence on why this could not be looked up: a preference, a priority, or a decision that cannot be undone. - **Recommended: `