# Client-Finder Query Expansion Spec (MVP) ## Goal Implement query expansion inside `client-finder` before calling `/ecom/cold-outreach/run-flow`. ## Scope - Only changes inside `agent-sandbox/agent_app/skills/client-finder`. - No new backend API. - Expansion failure should fallback to original raw query A. ## Input - `client_key` (required, from workflow input_data) - `query` (required) - `country` (optional, default `us`) - `QUERY_EXPANSION_JSON` (optional, must be valid JSON if provided) ## Expansion Flow 1. Normalize raw query. - trim spaces - remove optional prefix `cold-outreach:` 2. Resolve expansion source. - If `QUERY_EXPANSION_JSON` is provided: - accept object or array - must produce non-empty `expandedQueries` + non-empty `primaryQuery` - source = `llm` - Else use built-in rule expansion - source = `rule` 3. Validate expansion. - `expandedQueries.length >= 1` - `primaryQuery` non-empty - dedupe candidates case-insensitively 4. Execute workflow with `primaryQuery`. - exchange `client_key` first via `POST /auth/skill-credit/session` - `POST /auth/skill-credit/session` - `POST /ecom/cold-outreach/run-flow` - return accepted immediately after `workflowId` is received ## Fallback Policy - If expansion parsing/validation fails, use normalized raw query A as `primaryQuery`. - Set: - `expansionStatus = "failed"` - `expansionSource = "raw_query"` - `usedFallbackQuery = true` - `expansionError` with failure reason - Continue to run cold-outreach. ## Output Contract Always output strict JSON following `../output_schema.json`. Success example: ```json { "status": "success", "error": null, "inputQuery": "coffee", "expandedQueries": ["coffee shop US", "coffee roastery US"], "primaryQuery": "coffee shop US", "expansionStatus": "success", "expansionSource": "llm", "expansionError": null, "usedFallbackQuery": false, "runId": "", "workflowId": "outreach_xxx", "workflowStatus": "accepted", "businessesCount": 0, "contactsCount": 0, "uniqueContactDomains": 0, "billingReserveStatus": "SKIPPED", "billingFinalizeStatus": "SKIPPED" } ``` Expansion fallback example: ```json { "status": "success", "error": null, "inputQuery": "coffee", "expandedQueries": ["coffee"], "primaryQuery": "coffee", "expansionStatus": "failed", "expansionSource": "raw_query", "expansionError": "query expansion failed: expandedQueries is empty; fallback to raw query", "usedFallbackQuery": true, "runId": "", "workflowId": "outreach_xxx", "workflowStatus": "accepted", "businessesCount": 0, "contactsCount": 0, "uniqueContactDomains": 0, "billingReserveStatus": "SKIPPED", "billingFinalizeStatus": "SKIPPED" } ``` ## Acceptance 1. Expansion success -> call cold-outreach using `primaryQuery` (webhook is key-bound). 2. Expansion failure -> fallback to raw query and continue run when raw query is available. 3. If normalized raw query is empty, return failed JSON and exit non-zero. 4. On start success, return immediate `workflowStatus = "accepted"` (no local polling). 5. No prose output mixed with JSON.