| name | intake |
| description | Entry gate: identifies the build domain (any domain, not just software) + complexity before other modules. Use at session start, on a domain change, or /intake. |
Intake
You are the entry gate. Nothing runs before you (I3). Your job is to identify what domain is being
built and how complex the task is โ then hand a clean, locked intake.json to the rest of the
pipeline. You do not lock scope and you do not write the task card; you route.
The v6 mistake was hardcoding a closed list of 11 software platforms. You detect a domain by
reading the domain packs in library/domains/*/domain.yaml, and when no pack fits, you compose or
synthesize one. That is what lets WabbleSpec build a game, a finance model, or a curriculum with the
same engine.
What this does / does not do
Does: scores the active domain from pack detection signals + user intent, runs the 4-path domain
resolution, scores complexity (Low/Medium/High), assesses input quality, verifies cold-start docs
exist, writes intake.json, writes a receipt. Does not: lock scope (scope-frame), write the task
card (specify), or guess between two viable domains (it asks).
When NOT to use
- A fresh, valid
intake.json already exists for this session (matching session_id) โ load it,
write a receipt, stop. Re-detecting is wasteful and can flip a locked domain mid-session.
- The user is mid-task and asking to change scope within the same domain โ that is scope-frame,
not a re-intake. Re-run intake only when the build domain itself changes.
Reference Routing
| Situation | Reference |
|---|
| Domain scoring + 4-path resolution + pack load/scaffold | engine/shared/scripts/domain-loader.py (see script-delegation-contract.md) |
| Path 3 โ no pack exists (pack-synthesis protocol) | references/pack-synthesis.md |
| Receipt write | engine/shared/references/script-delegation-contract.md |
Inputs
- Opening user message
- Project directory (files, config, design documents)
library/domains/*/domain.yaml (the detection contracts)
<workspace>/intake.json (if it exists)
How to do it
Step 1 โ Check for an existing, valid intake
Read <workspace>/intake.json. If it exists and session_id matches the current session: load it,
write a receipt, stop. The domain is already declared and locked for the session.
Step 1b โ Check for session checkpoints
Only when Step 1 did not exit early. Check <workspace>/session/checkpoints/ for checkpoint-wave-*.json.
If any exist, surface the most recent (by timestamp) to the user: which wave completed, when, how
many receipts were written, and that the task was mid-execution. Offer resume (load the existing
intake.json, skip detection, route to executor with checkpoint context) or start fresh (continue
to Step 2; do not delete the checkpoints). If no response is obtainable, default to fresh.
Step 2 โ Detect the domain
Delegate the mechanical scoring + resolution to domain-loader.py score --files <project files> --intent <opening message> โ it enumerates the packs, scores each deterministically, and returns the
4-path resolution (exact / near / synthesize / ambiguous). You own the judgment the loader cannot:
confirm a synthesis (path 3) before it is saved, and ask the user on ambiguity (path 4). The detail
below is what the loader computes โ and your fallback when it is unavailable:
Read every library/domains/*/domain.yaml. For each pack, score its detect: signals against the
project and the opening message:
- files โ glob patterns matched against the working tree (e.g.
package.json, *.urdf, *.xlsx)
- intent_keywords โ terms matched against the user's opening message
Score each pack 0โ1 (fraction of its strongest signals present, file matches weighted over keywords).
Then resolve with the 4-path decision tree โ this is the core of intake:
| Path | Condition | Action |
|---|
| 1 โ exact | one pack scores โฅ 0.8 | load it. domain_resolution: exact, pack_status: loaded |
| 2 โ near | best pack 0.5โ0.8, no exact match | compose a provisional pack from the nearest sibling(s), flag the gaps to the user. domain_resolution: near, pack_status: composed |
| 3 โ none | no pack scores โฅ 0.5 | enter pack-synthesis (references/pack-synthesis.md): bounded research โ draft domain.yaml + vocabulary.md + acceptance/ โ confirm with user โ save the pack so the next user in this domain gets Path 1. domain_resolution: synthesized, pack_status: synthesized |
| 4 โ ambiguous | two or more packs score โฅ 0.6 | ask โ never guess. Present the candidates; lock the user's choice. domain_resolution: ambiguous-resolved |
Record the chosen domain, the sub_target (from the pack's sub_targets if one is implied), the
detection_method, and confidence.
Never auto-select between two domains scoring โฅ 0.6. A wrong domain routes every downstream
module incorrectly. Path 4 (ask) is mandatory, not optional.
Step 2c โ Assess input quality
Independent of the domain, evaluate the opening message (skip on the Step 1 load path):
- input_vague = true when ALL are absent: a named artifact; a measurable outcome; a named
constraint; a specific scope boundary.
- input_broad = true when BOTH hold: two or more interpretations are plausible at โฅ 0.5 each, and
no signal disambiguates them.
Routing: vague-not-broad โ enhance, then scope-frame ยท broad-not-vague โ sharpen, then scope-frame ยท
both โ enhance, then sharpen, then scope-frame ยท neither โ scope-frame directly. Intake writes
intake.json (domain + complexity locked) before invoking enhance/sharpen โ those operate on
input clarity only, never on the domain.
Step 3 โ Score complexity
| Level | Criteria |
|---|
| Low | single artifact, no integration points, no spec hierarchy needed, fully clear |
| Medium | multiple artifacts/components, 1โ2 integration points, mostly clear with minor gaps |
| High | cross-cutting, 3+ integration points, unclear requirements needing interview, or BREAKING changes |
When in doubt between Low and Medium, pick Medium โ an unnecessary review cycle is cheaper than a
skipped gate. Low + single-spec depth โ collapse_eligible: true (I2 gate collapsing may apply).
For each activated module, record {module_id, collapse_eligible, disposition} where disposition =
COLLAPSED if (complexity == Low AND module.collapse_eligible) else ACTIVE. Observational only โ it
does not gate execution.
Step 4 โ Verify cold-start coverage
Before writing intake.json, confirm rules/cold-start.md exists for every selected module and
cold-start.md exists for the resolved domain pack. If any are missing: emit MISSING_COLD_START,
list every missing path in one message, and do not write intake.json. Resolution is to add the
missing file or remove the module from selection. Do not suppress this to unblock a session โ an
undocumented module is an unacknowledged gap.
Step 5 โ Write intake.json, then the receipt
Write <workspace>/intake.json (contract below). Report to the user in one paragraph: domain,
resolution path, detection method, confidence, complexity. If the domain was ambiguous, you already
asked in Step 2 โ do not write before that is resolved. Then write the receipt via the delegation
script.
Output Contract
intake.json (<workspace>/intake.json โ persistence: Spec-First):
{
"domain": "string โ id of the resolved domain pack",
"sub_target": "string|null โ a sub_target from the pack, if implied",
"domain_resolution": "exact|near|synthesized|ambiguous-resolved",
"pack_status": "loaded|composed|synthesized",
"detection_method": "file-signals|user-declaration|pack-synthesis|existing-intake",
"confidence": 0.0,
"complexity": "Low|Medium|High",
"collapse_eligible": false,
"locked_at": "ISO-8601",
"session_id": "string",
"cold_start_verified": true,
"input_quality": { "vague": false, "broad": false, "enhanced": false, "sharpened": false },
"collapse_assessment": [ { "module_id": "string", "collapse_eligible": true, "disposition": "ACTIVE|COLLAPSED" } ]
}
receipt (<workspace>/receipts/intake-receipt.json): base schema + extension fields per
schemas/intake-receipt.schema.json (domain, domain_resolution, pack_status, detection_method,
confidence, complexity, collapse_eligible, cold_start_verified, input_quality,
collapse_assessment).
Proactive Triggers
- Two domain packs score โฅ 0.6: silent guessing routes everything wrong โ stop and ask (Path 4).
- No pack scores โฅ 0.5 and the user wants to proceed: do not force the nearest pack โ enter
pack-synthesis so the domain is actually modeled, not approximated.
- A selected module lacks
rules/cold-start.md: surface MISSING_COLD_START; an undocumented
module activating is an I5 risk โ do not write intake.json.
intake.json exists but session_id differs: stale recipe from a prior session โ re-detect,
do not load silently.
- Resolved domain pack is
sensitive: true: it is opt-in and requires an authorization context
(D5) โ confirm before loading; never ship it by default.
Completion Criteria
Done when: <workspace>/intake.json exists with a resolved domain, complexity, and
cold_start_verified: true; an intake receipt is written.
Not done if: the domain was ambiguous and you picked one without asking; a selected module is
missing cold-start and you wrote intake.json anyway; you re-detected when a valid same-session
intake.json already existed.
Next step: scope-frame (lock boundaries) โ or enhance/sharpen first when input quality
routing requires it.
Related Skills
- scope-frame: Use when boundaries (in/out/assumptions) must be locked after the domain is known.
NOT for identifying the domain โ that is intake.
- specify: Use when a locked scope needs a task card. NOT for domain or complexity detection โ intake.
- enhance / sharpen: Use when intake flags input as vague / broad. NOT for changing the
domain โ they refine input clarity only.