Previously using ChatGPT Plus to build the frontend for the agent platform, I found it to be a bit slow with lot of back and forth. I then decided to try out Windsurf IDE.

While testing the Agent I encountered 3 problems

  1. Agent was not able to remember the context e.g.
User: Who was Bill Clinton?
Agent: He was the 42nd President of USA
User: Where was he born?
Agent: Who are you referring to when you say "he"
  1. Agent was not invoking skills properly like Whenever I ask to look for news and anything which requires a search, it doesn’t. Even though the agent has access to web_search skill. E.g.
User: Any recent news about OpenAI and Jony Ive
Agent: Recently, there was an announcement that Jony Ive's design firm, LoveFrom, has partnered with Airbnb to work on designing futuristic homes. However, I couldn't find any recent news about Jony Ive being involved with OpenAI
  1. Skills are not easy to configure and needed lot of hardcoding.

One Solution to all problems - Invocation Pattern System

We built a configuration-driven system that lets skills declare their own activation patterns. Think of it like skills raising their hands saying “Pick me! Pick me!” when they see a query they can handle.

graph TD A[User Query] --> B[Pattern Matcher] B --> C{Match Found?} C -->|Yes| D[Extract Parameters] C -->|No| E[Use Default Skill] D --> F[Execute Skill] E --> F F --> G[Return Response]

Windsurf was able to think and rethink and came up with a clever configuration based system

1. Architecture

from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional

class InvocationPattern(BaseModel):
    """The secret sauce that makes skills smart!"""
    
    pattern: str = Field(..., description="What to look for in user queries")
    pattern_type: str = Field(default="keyword", description="How to match: 'keyword', 'regex', 'startswith', 'contains'")
    description: str = Field(..., description="Human-readable explanation")
    priority: int = Field(default=1, description="Higher = more important (1-10 scale)")
    sample_queries: List[str] = Field(default_factory=list, description="Example queries that trigger this pattern")
    parameter_extraction: Optional[Dict[str, Dict[str, Any]]] = Field(
        default=None, 
        description="The magic parameter extraction rules"
    )

2. Usage

pattern = InvocationPattern(
    pattern="news about OpenAI and Jony Ive",
    pattern_type="keyword",
    description="Looks for news about OpenAI and Jony Ive",
    priority=5,
    sample_queries=["news about OpenAI and Jony Ive", "latest news about OpenAI and Jony Ive"],
    parameter_extraction={
        "company": {
            "type": "keyword",
            "key": "company"
        }
    }
)

3. Parameter Extraction - The Smart Part

web_search_patterns = [
    InvocationPattern(
        pattern="latest|recent|news",
        pattern_type="keyword",
        description="Catches queries about current events",
        priority=5,
        sample_queries=[
            "What's the latest on quantum computing?",
            "Recent breakthroughs in AI",
            "Show me news about Mars exploration"
        ],
        parameter_extraction={
            "query": {
                "type": "content",  # Use entire user query
                "description": "The full search query"
            },
            .
            .

A Sample Skill Defintion

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
"""
Web Search skill implementation using SerpAPI.
"""

import logging
import os
from typing import Any, Dict, List, Optional

from serpapi import GoogleSearch

from shared.models.skill import (
    Skill,
    SkillParameter,
    ParameterType,
    ResponseFormat,
    InvocationPattern
)

logger = logging.getLogger(__name__)

# API key for SerpAPI
SERPAPI_API_KEY = os.environ.get("SERPAPI_API_KEY", "MY_SERP_API_KEY")

# Skill definition
SKILL_DEFINITION = Skill(
    skill_id="web-search",
    name="Web Search",
    description="Search the web for information using Google search via SerpAPI",
    parameters=[
        SkillParameter(
            name="query",
            type=ParameterType.STRING,
            description="The search query",
            required=True
        ),
        SkillParameter(
            name="num_results",
            type=ParameterType.INTEGER,
            description="Number of results to return",
            required=False,
            default=5
        ),
        SkillParameter(
            name="include_images",
            type=ParameterType.BOOLEAN,
            description="Whether to include image results",
            required=False,
            default=False
        ),
        SkillParameter(
            name="search_type",
            type=ParameterType.STRING,
            description="Type of search to perform",
            required=False,
            default="web",
            enum=["web", "news", "videos", "shopping"]
        )
    ],
    response_format=ResponseFormat(
        schema={
            "type": "object",
            "properties": {
                "results": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "title": {"type": "string"},
                            "link": {"type": "string"},
                            "snippet": {"type": "string"},
                            "displayedLink": {"type": "string"}
                        }
                    }
                },
                "image_results": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "thumbnail": {"type": "string"},
                            "source": {"type": "string"},
                            "title": {"type": "string"},
                            "link": {"type": "string"}
                        }
                    }
                }
            }
        },
        description="List of search results with titles, links, and snippets"
    ),
    tags=["search", "web", "external-api", "serpapi"],
    invocation_patterns=[
        # News and recent information pattern
        InvocationPattern(
            pattern="recent",
            pattern_type="keyword",
            description="Matches queries about recent news or current events",
            priority=5,  # Highest priority for news queries
            sample_queries=["any recent news about AI", "latest updates on climate change"],
            parameter_extraction={
                "query": {"type": "content"},
                "search_type": {"type": "constant", "value": "news"},
                "num_results": {"type": "constant", "value": 5}
            }
        ),
        InvocationPattern(
            pattern="latest",
            pattern_type="keyword",
            description="Matches queries about latest news or updates",
            priority=5,
            sample_queries=["latest developments in quantum computing", "what are the latest AI breakthroughs"],
            parameter_extraction={
                "query": {"type": "content"},
                "search_type": {"type": "constant", "value": "news"},
                "num_results": {"type": "constant", "value": 5}
            }
        ),
        InvocationPattern(
            pattern="news",
            pattern_type="keyword",
            description="Matches queries explicitly asking for news",
            priority=5,
            sample_queries=["news about OpenAI", "technology news this week"],
            parameter_extraction={
                "query": {"type": "content"},
                "search_type": {"type": "constant", "value": "news"},
                "num_results": {"type": "constant", "value": 5}
            }
        ),
        # Factual question patterns
        InvocationPattern(
            pattern="what",
            pattern_type="startswith",
            description="Matches factual questions starting with 'what'",
            priority=2,
            sample_queries=["what is quantum computing", "what are the effects of climate change"],
            parameter_extraction={
                "query": {"type": "content"},
                "search_type": {"type": "constant", "value": "web"},
                "num_results": {"type": "constant", "value": 5}
            }
        ),
        InvocationPattern(
            pattern="who",
            pattern_type="startswith",
            description="Matches factual questions starting with 'who'",
            priority=2,
            sample_queries=["who is the CEO of OpenAI", "who won the 2024 election"],
            parameter_extraction={
                "query": {"type": "content"},
                "search_type": {"type": "constant", "value": "web"},
                "num_results": {"type": "constant", "value": 5}
            }
        ),
        InvocationPattern(
            pattern="when",
            pattern_type="startswith",
            description="Matches factual questions starting with 'when'",
            priority=2,
            sample_queries=["when was Bitcoin created", "when is the next solar eclipse"],
            parameter_extraction={
                "query": {"type": "content"},
                "search_type": {"type": "constant", "value": "web"},
                "num_results": {"type": "constant", "value": 5}
            }
        ),
        InvocationPattern(
            pattern="where",
            pattern_type="startswith",
            description="Matches factual questions starting with 'where'",
            priority=2,
            sample_queries=["where is the Great Barrier Reef", "where can I find affordable housing"],
            parameter_extraction={
                "query": {"type": "content"},
                "search_type": {"type": "constant", "value": "web"},
                "num_results": {"type": "constant", "value": 5}
            }
        ),
        # Explicit search requests
        InvocationPattern(
            pattern="search for",
            pattern_type="contains",
            description="Matches explicit search requests",
            priority=4,
            sample_queries=["search for best restaurants in San Francisco", "please search for electric car reviews"],
            parameter_extraction={
                "query": {"type": "keyword_after", "keyword": "search for"},
                "search_type": {"type": "constant", "value": "web"},
                "num_results": {"type": "constant", "value": 5}
            }
        ),
        InvocationPattern(
            pattern="find information",
            pattern_type="contains",
            description="Matches requests to find information",
            priority=4,
            sample_queries=["find information about solar panels", "please find information on machine learning"],
            parameter_extraction={
                "query": {"type": "keyword_after", "keyword": "find information"},
                "search_type": {"type": "constant", "value": "web"},
                "num_results": {"type": "constant", "value": 5}
            }
        ),
        # Current events fallback pattern (lower priority)
        InvocationPattern(
            pattern="current",
            pattern_type="keyword",
            description="Matches queries about current events",
            priority=3,
            sample_queries=["current situation in Ukraine", "what is the current state of AI regulation"],
            parameter_extraction={
                "query": {"type": "content"},
                "search_type": {"type": "constant", "value": "news"},
                "num_results": {"type": "constant", "value": 5}
            }
        )
    ]
)


async def execute(
    parameters: Dict[str, Any],
    skill: Optional[Skill] = None,
    agent_id: Optional[str] = None,
    conversation_id: Optional[str] = None
) -> Dict[str, Any]:
    """Execute the web search skill.
    
    Args:
        parameters: The validated parameters for the skill.
        skill: The skill definition.
        agent_id: Optional ID of the agent executing the skill.
        conversation_id: Optional ID of the conversation context.
        
    Returns:
        Dict[str, Any]: The search results.
    """
    query = parameters["query"]
    num_results = parameters.get("num_results", 5)
    include_images = parameters.get("include_images", False)
    search_type = parameters.get("search_type", "web")
    
    logger.info(f"Executing {search_type} search via SerpAPI for query: {query}")
    
    try:
        # Build search parameters
        search_params = {
            "q": query,
            "api_key": SERPAPI_API_KEY,
            "num": num_results,
            "hl": "en",
            "gl": "us"
        }
        
        # Handle different search types
        if search_type == "news":
            search_params["tbm"] = "nws"
        elif search_type == "videos":
            search_params["tbm"] = "vid"
        elif search_type == "shopping":
            search_params["tbm"] = "shop"
        
        # Execute search
        search = GoogleSearch(search_params)
        results = search.get_dict()
        
        # Format response
        formatted_results = {
            "results": [],
            "image_results": []
        }
        
        # Extract organic results based on search type
        if search_type == "news" and "news_results" in results:
            for result in results["news_results"][:num_results]:
                formatted_results["results"].append({
                    "title": result.get("title", ""),
                    "link": result.get("link", ""),
                    "snippet": result.get("snippet", ""),
                    "displayedLink": result.get("source", "")
                })
        elif search_type == "videos" and "video_results" in results:
            for result in results["video_results"][:num_results]:
                formatted_results["results"].append({
                    "title": result.get("title", ""),
                    "link": result.get("link", ""),
                    "snippet": result.get("snippet", ""),
                    "displayedLink": result.get("source", "")
                })
        elif search_type == "shopping" and "shopping_results" in results:
            for result in results["shopping_results"][:num_results]:
                formatted_results["results"].append({
                    "title": result.get("title", ""),
                    "link": result.get("link", ""),
                    "snippet": result.get("snippet", f"Price: {result.get('price', 'N/A')}"),
                    "displayedLink": result.get("source", "")
                })
        elif "organic_results" in results:
            for result in results["organic_results"][:num_results]:
                formatted_results["results"].append({
                    "title": result.get("title", ""),
                    "link": result.get("link", ""),
                    "snippet": result.get("snippet", ""),
                    "displayedLink": result.get("displayed_link", "")
                })
        
        # Extract image results if requested
        if include_images and "images_results" in results:
            for img in results["images_results"][:num_results]:
                formatted_results["image_results"].append({
                    "thumbnail": img.get("thumbnail", ""),
                    "source": img.get("source", ""),
                    "title": img.get("title", ""),
                    "link": img.get("link", "")
                })
        
        logger.info(f"Web search completed with {len(formatted_results['results'])} results")
        return formatted_results
        
    except Exception as e:
        logger.error(f"Error in web search skill: {e}")
        raise Exception(f"Failed to execute web search: {str(e)}")

Learnings

  1. Windsurf is smart and can optimize code very well. It introduced a fast path to skip the reasoning path and directly invoke the web-skill.
  2. It tends to lose track of context and make things even more complicated. This is expected of any junior/mid level engineer. Like everyone else, its needs a break aka a new session :)
  3. Langraph used by the Agent Service is a great way to structure the agent’s behavior and make it more maintainable and uses a graph representation to structure reasoning, state, skills invocation and response formulation.
    graph TB subgraph "Reasoning Phase" D[Memory Manager] D --> E[Reasoning Node] E --> F{Match Invocation Patterns} subgraph "Pattern Matching" F --> |No Match| G[Direct Response] F --> |Match Found| H[Select Highest Priority Pattern] H --> I[Extract Parameters] end end subgraph "Skill Registry" SR[Skill Registry] --> |Provide Skills| F SR --> |Skill Definitions| SK1[Web Search Skill] SR --> |Skill Definitions| SK2[Summarize Text Skill] subgraph "Invocation Patterns" SK1 --> |Contains| P1[Pattern: news, latest, etc.] SK1 --> |Parameter Extraction| PE1[Extract query, search_type] SK2 --> |Contains| P2[Pattern: summarize, condense, etc.] SK2 --> |Parameter Extraction| PE2[Extract text, format] end end subgraph "Skill Execution Phase" I --> J[Skill Service Client] J --> K[Skill Validator] K --> L[Skill Executor] L --> M[Execute Selected Skill] M --> N[Store Skill Result] end subgraph "Response Formulation Phase" N --> O[Response Formulation Node] G --> O O --> P[Generate Human-Friendly Response] P --> Q[Send Response to User] end %% Data flow connections Q --> |Update| D SR <--> |Redis Storage| DB[(Redis)] N --> |Store Results| DB