{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "description": "A multi-agent deep search system that researches questions by clarifying intent, planning queries, evaluating results, and synthesizing answers.",
  "files": [
    {
      "content": "import { agentConfig } from '@mastra/core/agent'\nimport { deepSearch } from './workflows/deep-search'\n\nexport default agentConfig({\n  model: 'openai/gpt-5.2',\n  description: 'A deep search agent that researches questions by clarifying intent, planning queries, evaluating results, and synthesizing answers.',\n  workflows: { deepSearch },\n})\n",
      "path": "config.ts",
      "target": "config.ts",
      "type": "registry:file"
    },
    {
      "content": "You are a deep research assistant that evaluates its own work.\n\nGiven a research question:\n\n1. Break it into the specific sub-questions you need to answer.\n2. Use `web_search` to find sources for each sub-question.\n3. After each round, critique your own findings: what is still missing,\n   unsupported, or contradictory? If gaps remain, search again with sharper\n   queries. Iterate until the question is fully answered.\n4. Write a final answer grounded only in what you found. Cite every claim with\n   its source URL. If something could not be verified, say so explicitly.\n\nNever fabricate sources or facts. Prefer primary sources and recent results.\n",
      "path": "instructions.md",
      "target": "instructions.md",
      "type": "registry:file"
    },
    {
      "content": "import { agentConfig } from '@mastra/core/agent'\n\nexport default agentConfig({\n  model: 'openai/gpt-5-mini',\n  description: 'Analyzes user queries and generates clarifying questions to better understand intent.',\n})\n",
      "path": "subagents/intent-clarifier/config.ts",
      "target": "subagents/intent-clarifier/config.ts",
      "type": "registry:file"
    },
    {
      "content": "You are an expert at understanding user intent and generating clarifying questions.\n\nYour task is to analyze the user's query and generate exactly 3 follow-up questions that will help you provide a comprehensive, personalized answer.\n\nGuidelines for generating questions:\n- Make questions specific and actionable\n- Focus on key decision factors that would change your recommendation\n- Avoid generic questions - each should reveal important context\n- Questions should be answerable in a brief response\n\nExamples:\n- Query: \"best commuter bike\"\n  Questions: What's your budget range? What type of terrain will you ride on (flat, hilly, mixed)? Do you need to store it indoors or carry it on public transport?\n\n- Query: \"learn python\"\n  Questions: What's your current programming experience level? What do you want to build (web apps, data analysis, automation, AI)? How many hours per week can you dedicate to learning?\n\n- Query: \"best laptop for work\"\n  Questions: What type of work will you primarily use it for? Do you need portability or is a larger screen acceptable? What's your budget?\n\nWhen responding, provide exactly 3 questions that would most significantly impact your final recommendation.\n",
      "path": "subagents/intent-clarifier/instructions.md",
      "target": "subagents/intent-clarifier/instructions.md",
      "type": "registry:file"
    },
    {
      "content": "import { agentConfig } from '@mastra/core/agent'\n\nexport default agentConfig({\n  model: 'openai/gpt-5.2',\n  description: 'Writes search queries for a research workflow.',\n})\n",
      "path": "subagents/research-planner/config.ts",
      "target": "subagents/research-planner/config.ts",
      "type": "registry:file"
    },
    {
      "content": "You write search queries for a research workflow.\n\nOutput 3–5 queries per request.\n\nFirst pass (no gaps provided):\n- Be exploratory and broad; map the topic.\n- Do NOT do model-to-model comparisons yet.\n- Do NOT list multiple specific models in a single query.\n- Favor general queries that surface reviews, buying guides, expert recommendations.\n- Keep queries short to mid-length (avoid long-tail).\n\nFollow-up (gaps provided):\n- Each query should directly address a specific gap.\n- Still avoid overly long, ultra-specific strings.\n- Avoid repeating any \"previous queries\" listed in the prompt.\n- Only use comparisons or specific models if a gap explicitly requires it.\n\nAlways:\n- Use natural language a real person would type.\n- Avoid meta phrasing like \"diverse sources\".\n- Include the current year (e.g., \"2025\") in at least one query to favor recent results.\n\nReturn only the list of queries.\n",
      "path": "subagents/research-planner/instructions.md",
      "target": "subagents/research-planner/instructions.md",
      "type": "registry:file"
    },
    {
      "content": "import { agentConfig } from '@mastra/core/agent'\n\nexport default agentConfig({\n  model: 'openai/gpt-5.2',\n  description: 'Evaluates research quality and completeness.',\n})\n",
      "path": "subagents/search-result-evaluator/config.ts",
      "target": "subagents/search-result-evaluator/config.ts",
      "type": "registry:file"
    },
    {
      "content": "You are an expert at evaluating research quality and completeness.\n\nYour task is to decide whether the current results are good enough to answer the user's initial query and their clarified context.\n\nBe thorough. Err toward \"insufficient\" when uncertain. It is better to search once more than to produce a shallow answer.\n\nEvaluation criteria:\n1. **Relevance** - Do the results address the user's question and context?\n2. **Coverage** - Each major aspect of the query should have at least 2 distinct sources. If any aspect has only 1 source or none, mark insufficient.\n3. **Recency** - For time-sensitive topics, at least some sources should be from the current year.\n4. **Depth** - For recommendation or comparison queries, require concrete comparative data (prices, specs, pros/cons). Vague overviews are insufficient.\n\nIf insufficient, list the most important gaps (max 3).\n",
      "path": "subagents/search-result-evaluator/instructions.md",
      "target": "subagents/search-result-evaluator/instructions.md",
      "type": "registry:file"
    },
    {
      "content": "import { agentConfig } from '@mastra/core/agent'\n\nexport default agentConfig({\n  model: 'openai/gpt-5.2',\n  description: 'Answers questions based on web search results.',\n})\n",
      "path": "subagents/answerer/config.ts",
      "target": "subagents/answerer/config.ts",
      "type": "registry:file"
    },
    {
      "content": "You answer questions based on web search results.\n\nFocus on what the user actually asked for. If they clarified their needs (e.g., \"works for both espresso and pourover\"), answer that specific question — don't split into separate recommendations unless they asked for that.\n\nWhen answering:\n- Write in Markdown with clear headings and bullet points\n- Cite sources using markdown links [title](url)\n- Be direct — lead with the answer, then support it\n",
      "path": "subagents/answerer/instructions.md",
      "target": "subagents/answerer/instructions.md",
      "type": "registry:file"
    },
    {
      "content": "---\nname: research\ndescription: Iterative, self-evaluating web research. Use when answering open-ended questions that require gathering and verifying information from multiple sources.\n---\n\n## Research Process\n\n1. Decompose the question into concrete sub-questions.\n2. Search each sub-question with `web_search`; collect URLs and snippets.\n3. Self-evaluate: list gaps, weak evidence, and contradictions.\n4. Re-search to close gaps until coverage is sufficient.\n5. Synthesize an answer citing every source. Flag anything unverified.\n",
      "path": "skills/research/SKILL.md",
      "target": "skills/research/SKILL.md",
      "type": "registry:file"
    },
    {
      "content": "import { createStep, createWorkflow } from '@mastra/core/workflows'\nimport { z } from 'zod'\n\nconst stateSchema = z.object({\n  initialQuery: z.string().optional(),\n  clarifiedIntent: z.string().optional(),\n  expandedQueries: z.array(z.string()).optional(),\n  searchResults: z\n    .array(\n      z.object({\n        query: z.string(),\n        results: z.array(\n          z.object({\n            title: z.string().nullable(),\n            url: z.string(),\n            summary: z.string(),\n            publishedDate: z.string().optional(),\n            author: z.string().nullable().optional(),\n          }),\n        ),\n      }),\n    )\n    .optional(),\n  gaps: z.array(z.string()).optional(),\n  answerIsSatisfactory: z.boolean().optional(),\n  answer: z.string().optional(),\n})\n\nconst clarifyIntent = createStep({\n  id: 'clarify-intent',\n  inputSchema: z.object({ initialQuery: z.string() }),\n  outputSchema: z.object({}),\n  stateSchema,\n  resumeSchema: z.object({ clarifiedIntent: z.string() }),\n  suspendSchema: z.object({ assistantMessage: z.string() }),\n  execute: async ({ inputData, resumeData, suspend, setState, mastra }) => {\n    const log = mastra!.getLogger()\n\n    if (!resumeData) {\n      log.info('clarifyIntent start', {\n        step: 'clarifyIntent',\n        initialQuery: inputData.initialQuery,\n      })\n      await setState({ initialQuery: inputData.initialQuery })\n\n      const intentAgent = mastra!.getAgent('intent-clarifier')\n      const response = await intentAgent.generate(\n        `User query: \"${inputData.initialQuery}\"\\n\\nGenerate exactly 3 clarifying questions to better understand the user's intent.`,\n        {\n          structuredOutput: {\n            schema: z.object({ questions: z.array(z.string()).length(3) }),\n          },\n        },\n      )\n\n      const questions = response.object.questions\n      const formatted = questions.map((q: string, i: number) => `${i + 1}. ${q}`).join('\\n')\n      const assistantMessage = `To help you better, I have a few questions:\\n\\n${formatted}`\n\n      log.info('clarifyIntent suspend', {\n        step: 'clarifyIntent',\n        questionCount: questions.length,\n        assistantMessage,\n      })\n      return suspend({ assistantMessage })\n    }\n\n    log.info('clarifyIntent resume', {\n      step: 'clarifyIntent',\n      clarifiedIntent: resumeData.clarifiedIntent,\n    })\n    await setState({\n      initialQuery: inputData.initialQuery,\n      clarifiedIntent: resumeData.clarifiedIntent,\n    })\n    return {}\n  },\n})\n\nconst generateQueries = createStep({\n  id: 'generate-queries',\n  inputSchema: z.object({}),\n  outputSchema: z.object({}),\n  stateSchema,\n  execute: async ({ state, setState, mastra }) => {\n    const log = mastra!.getLogger()\n    log.info('generateQueries start', {\n      step: 'generateQueries',\n      initialQuery: state.initialQuery,\n      clarifiedIntent: state.clarifiedIntent,\n      gaps: state.gaps?.length ?? 0,\n    })\n\n    const priorQueries = state.searchResults?.map((r) => r.query) ?? []\n    const uniquePrior = [...new Set(priorQueries)]\n\n    const priorText =\n      uniquePrior.length > 0\n        ? `\\nPrevious queries (avoid repeating):\\n- ${uniquePrior.join('\\n- ')}`\n        : ''\n\n    const gapsText = state.gaps?.length\n      ? `\\nKnown gaps:\\n- ${state.gaps.join('\\n- ')}`\n      : ''\n\n    const plannerAgent = mastra!.getAgent('research-planner')\n    const response = await plannerAgent.generate(\n      `User initial query: \"${state.initialQuery}\"\nAdditional context: \"${state.clarifiedIntent}\"\n${priorText}\n${gapsText}\n\nGenerate 3-5 focused search queries.`,\n      {\n        structuredOutput: {\n          schema: z.object({ queries: z.array(z.string()).min(3).max(5) }),\n        },\n      },\n    )\n\n    const expandedQueries = response.object.queries\n    log.info('generateQueries state update', {\n      step: 'generateQueries',\n      expandedQueries,\n    })\n\n    await setState({ ...state, expandedQueries })\n    return {}\n  },\n})\n\nconst search = createStep({\n  id: 'search',\n  inputSchema: z.object({}),\n  outputSchema: z.object({}),\n  stateSchema,\n  execute: async ({ state, setState }) => {\n    const previousResults = state.searchResults ?? []\n\n    const searchResults = await Promise.all(\n      state.expandedQueries!.map(async (query) => {\n        // Use fetch to call a search API or implement custom search logic\n        // This is a placeholder - in production, you'd use a real search provider\n        const results = await performSearch(query)\n        return {\n          query,\n          results,\n        }\n      }),\n    )\n\n    await setState({\n      ...state,\n      searchResults: [...previousResults, ...searchResults],\n    })\n    return {}\n  },\n})\n\nconst evaluateResults = createStep({\n  id: 'evaluate-results',\n  inputSchema: z.object({}),\n  outputSchema: z.object({}),\n  stateSchema,\n  execute: async ({ state, setState, mastra }) => {\n    const log = mastra!.getLogger()\n    log.info('evaluateResults start', {\n      step: 'evaluateResults',\n      queriesSearched: state.searchResults?.length ?? 0,\n    })\n\n    const evaluatorAgent = mastra!.getAgent('search-result-evaluator')\n    const response = await evaluatorAgent.generate(\n      `User query: \"${state.initialQuery}\"\nClarified intent: \"${state.clarifiedIntent}\"\nSearch results:\n${JSON.stringify(state.searchResults, null, 2)}\n\nDetermine if the results are sufficient.`,\n      {\n        structuredOutput: {\n          schema: z.object({\n            answerIsSatisfactory: z.boolean(),\n            gaps: z.array(z.string()),\n          }),\n        },\n      },\n    )\n\n    const { answerIsSatisfactory, gaps } = response.object\n    log.info('evaluateResults state update', {\n      step: 'evaluateResults',\n      answerIsSatisfactory,\n      gaps,\n    })\n\n    await setState({\n      ...state,\n      answerIsSatisfactory,\n      gaps,\n    })\n    return {}\n  },\n})\n\nconst finalizeAnswer = createStep({\n  id: 'finalize-answer',\n  inputSchema: z.object({}),\n  outputSchema: z.object({ answer: z.string() }),\n  stateSchema,\n  execute: async ({ state, mastra }) => {\n    const log = mastra!.getLogger()\n    const queries = state.searchResults?.map((r) => r.query)\n    log.info('finalizeAnswer queries', { step: 'finalizeAnswer', queries })\n\n    const exhausted = !state.answerIsSatisfactory\n    log.info('finalizeAnswer exhausted', { step: 'finalizeAnswer', exhausted })\n\n    const answerAgent = mastra!.getAgent('answerer')\n    const exhaustionNote = exhausted\n      ? 'Note: We may not have all the information needed. Please provide your best attempt based on available information.\\n'\n      : ''\n\n    const stream = await answerAgent.stream(\n      `Query: \"${state.initialQuery}\"\nClarified needs: \"${state.clarifiedIntent}\"\n${exhaustionNote}\nBased on the following search results, answer the user's question:\n${JSON.stringify(state.searchResults, null, 2)}`,\n    )\n\n    const answer = await stream.text\n    log.info('finalizeAnswer output', { step: 'finalizeAnswer', answer })\n\n    return { answer }\n  },\n})\n\nconst searchPass = createWorkflow({\n  id: 'search-pass',\n  inputSchema: z.object({}),\n  outputSchema: z.object({\n    answerIsSatisfactory: z.boolean(),\n    gaps: z.array(z.string()),\n  }),\n  stateSchema,\n})\n  .then(generateQueries)\n  .then(search)\n  .then(evaluateResults)\n  .commit()\n\nexport const deepSearch = createWorkflow({\n  id: 'deep-search',\n  inputSchema: z.object({ initialQuery: z.string() }),\n  outputSchema: z.object({ answer: z.string() }),\n  stateSchema,\n})\n  .then(clarifyIntent)\n  .dountil(searchPass, async ({ iterationCount, state }) => {\n    return iterationCount >= 4 || !!state.answerIsSatisfactory\n  })\n  .then(finalizeAnswer)\n  .commit()\n\ntype SerpResult = { title?: string; link?: string; description?: string }\n\n// Bright Data SERP API search function\nasync function performSearch(query: string, numResults = 10) {\n  const target = new URL('https://www.google.com/search')\n  target.searchParams.set('q', query)\n  target.searchParams.set('num', String(numResults))\n  target.searchParams.set('brd_json', '1')\n\n  const res = await fetch('https://api.brightdata.com/request', {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      Authorization: `Bearer ${process.env.BRIGHTDATA_API_KEY ?? ''}`,\n    },\n    body: JSON.stringify({\n      zone: process.env.BRIGHTDATA_SERP_ZONE ?? 'serp_api',\n      url: target.toString(),\n      format: 'raw',\n    }),\n  })\n\n  const data = (await res.json()) as { organic?: SerpResult[] }\n  return (data.organic ?? []).slice(0, numResults).map((r) => ({\n    title: r.title ?? null,\n    url: r.link ?? '',\n    summary: r.description ?? '',\n    publishedDate: new Date().toISOString(),\n    author: null,\n  }))\n}\n",
      "path": "workflows/deep-search.ts",
      "target": "workflows/deep-search.ts",
      "type": "registry:file"
    }
  ],
  "name": "deep-search",
  "title": "Deep Search Agent",
  "type": "registry:block",
  "dependencies": [
    "@mastra/core",
    "@mastra/libsql"
  ]
}
