Module 2: Agentic Research with the Explore Subagent
Semantic Search vs Grep — Finding by Meaning
Semantic Search vs Grep — Finding by Meaning
Capsule description
So far you've used Explore as a read-only investigation tool. But there's a capability that separates it radically from any traditional search tool: semantic search. While grep searches for exact text, Explore understands meaning. And that difference completely changes how you find code in a codebase.
In this capsule you're going to experience the difference first-hand. You're going to search for the same thing with grep and with Explore, compare results, and understand when to use each. It's not about replacing grep — it's about having two complementary tools and knowing when each one is superior.
The connection with the project is direct: in capsule 05 you're going to answer questions about a codebase using Explore. Many of those questions require semantic search — "where are inputs validated?" isn't something grep solves well. This capsule gives you the ability to find code by what it does, not just by what it's called.
The Problem with Text Search
Why grep isn't enough
grep is an extraordinary tool. It's been the standard for searching text in files for decades. But it has a fundamental limitation: it searches for strings, not concepts.
# You search for where user inputs are validated
grep -r "validate" src/
# Results:
# src/utils/helpers.py: # validate email format
# src/models/user.py: def validate_name(self):
# src/tests/test_validation.py: class TestValidate:
You found 3 results. But what if the real validation is in functions called check_params, sanitize_input, ensure_valid, or verify_data? grep doesn't find them because it searches for the word "validate", not the concept of validation.
The gap between text and meaning
# ALL of these functions do input validation
# But none contains the word "validate"
def check_params(request_data: dict) -> bool:
"""Check that the required parameters are present."""
required = ["name", "email", "age"]
return all(key in request_data for key in required)
def sanitize_input(raw_text: str) -> str:
"""Clean text of dangerous characters."""
import html
return html.escape(raw_text.strip())
def ensure_valid_age(age: int) -> int:
"""Confirm that the age is in a reasonable range."""
if not 0 < age < 150:
raise ValueError(f"Age out of range: {age}")
return age
def verify_email_format(email: str) -> bool:
"""Check that the email has the correct format."""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
If you search grep -r "validate", none of these functions show up. But they're all exactly what you need. This is the gap between text search and meaning search.
Semantic Search with Explore
How it works
When you ask Explore something like "where are user inputs validated?", it doesn't search for the word "validate." It understands you're asking about code that verifies, cleans, confirms, or rejects input data. It searches by concept, not by string.
Direct demo: the same question, two tools
# With grep:
$ grep -rn "validat" src/
src/utils/helpers.py:45: # validate email format
src/models/user.py:23: def validate_name(self):
# 2 results. Incomplete.
# With Explore in Claude Code:
> "Use Explore to find where user inputs are validated
in this project"
# Explore finds:
# 1. src/utils/helpers.py:45 - validate_email() — format validation
# 2. src/models/user.py:23 - validate_name() — name validation
# 3. src/middleware/sanitizer.py:12 - sanitize_input() — input cleaning
# 4. src/api/validators.py:8 - check_params() — parameter verification
# 5. src/services/user_service.py:67 - ensure_valid_age() — age range
# 6. src/middleware/auth.py:34 - verify_token() — auth token validation
#
# Explore also reports:
# "Input validation happens in 3 layers:
# 1. Middleware (sanitizer.py) - general cleaning
# 2. API validators (validators.py) - required parameters
# 3. Model level (user.py, user_service.py) - business rules"
# 6 results + layer analysis. Complete.
The difference isn't marginal — it's fundamental. Explore found three times as many results AND provided architectural context.
Progressive example: from simple to complex
Basic level — Search for a functionality:
# Prompt to Claude Code:
> "Use Explore to find where payments are handled
in this project"
# Explore searches by the meaning "payment handling":
# - src/services/payment_service.py — main payment logic
# - src/api/routes/checkout.py — checkout endpoints
# - src/models/transaction.py — transaction model
# - src/integrations/stripe_client.py — Stripe integration
# - src/utils/currency.py — currency conversion
Intermediate level — Search for a pattern:
# Prompt to Claude Code:
> "Use Explore to find where the retry pattern
is implemented in this project"
# Explore understands "retry pattern" as a concept:
# - src/utils/retry.py — @retry decorator with exponential backoff
# - src/integrations/api_client.py:89 — manual retry with loop and sleep
# - src/services/email_service.py:34 — send retry with a counter
# - src/config/settings.py:78 — MAX_RETRIES, RETRY_DELAY configured
#
# Note: "There are 2 retry implementations: one centralized
# (retry.py) and one ad-hoc (api_client.py). The ad-hoc one doesn't
# use the centralized decorator."
Advanced level — Search for an abstract concept:
# Prompt to Claude Code:
> "Use Explore to find possible security vulnerabilities
in the handling of user data"
# Explore analyzes semantically:
# - src/api/routes/users.py:45 — password logged in debug mode
# - src/middleware/cors.py:12 — CORS allows origin "*" (wildcard)
# - src/utils/crypto.py:23 — uses MD5 for hashing (deprecated, insecure)
# - src/services/user_service.py:89 — concatenated SQL without parameterizing
# - src/config/settings.py:5 — SECRET_KEY hardcoded in the file
Comparison: grep vs Semantic Search
| Criterion | grep / ripgrep | Explore (semantic) |
|---|---|---|
| Searches by | Exact text / regex | Meaning / concept |
| Speed | Milliseconds | Seconds (requires an LLM) |
| Results | Lines with a match | Files + context + analysis |
| False positives | Many (comments, strings, similar names) | Few (understands context) |
| False negatives | Many (synonyms, abstractions) | Few (understands synonyms) |
| When to use | You know the exact name | You know what it does but not what it's called |
| Cost | Free, local | API tokens |
| Multilingual | Doesn't understand language | Understands questions in any language |
When to use each?
Use grep when:
- You know the exact name:
grep -r "PaymentService" src/ - You're searching for a literal string:
grep -r "TODO:" src/ - You're searching for a specific import:
grep -r "from stripe" src/ - You need maximum speed (millions of files)
- You're searching for an exact error:
grep -r "Error: connection refused" logs/
Use Explore when:
- You know what the code does but not what it's called: "where is authentication handled?"
- You're searching for an abstract concept: "where are there possible memory leaks?"
- You need context in addition to location: "how does the cache system work?"
- You're searching for code that implements a pattern: "where is the observer pattern used?"
- You need to understand relationships: "what components depend on the auth module?"
Key trade-off: grep is free and fast but limited to text. Explore costs tokens and takes longer but understands meaning. In professional practice, you use both: grep for fast exact searches, Explore for deep investigation.
Advanced Semantic Search Techniques
Technique 1: Search by behavior
Instead of searching by name, search by what the code does:
# Instead of: grep -r "cache" src/
# Ask:
> "Use Explore to find code that stores results
to avoid repeated calculations"
# Finds: functions with memoize, @lru_cache, dict lookups
# that act as cache, Redis calls, and instance variables
# that store previous results — many without the word "cache"
Technique 2: Search by impact
Search for code that affects a specific resource:
# Instead of: grep -r "database\|db\|sql" src/
# Ask:
> "Use Explore to find all code that reads or writes
to the database"
# Finds: direct queries, ORM calls, migrations,
# seeders, and functions that indirectly cause queries
# through lazy loading
Technique 3: Negative search
Search for the absence of something:
# You can't do this with grep
# Ask:
> "Use Explore to find endpoints that do NOT have
authentication"
# Finds: public routes that should be private,
# endpoints missing auth middleware, admin routes
# without permission checks
Technique 4: Comparative search
Search for inconsistencies:
# Ask:
> "Use Explore to find functions that handle errors
differently from the project's dominant pattern"
# Finds: functions that use print() instead of logger,
# that silence exceptions with a bare except,
# or that return None instead of raising
Combining grep and Explore
The professional workflow
The best results come from combining both tools:
# Step 1: Explore for broad investigation
> "Use Explore to understand how the notification
system works"
# Explore reports:
# "The notification system uses a pub/sub pattern.
# The publishers are in src/events/, the subscribers
# in src/handlers/, and the configuration in src/config/
# notifications.yaml. The main class is EventBus
# in src/core/event_bus.py"
# Step 2: grep for specific details
$ grep -rn "EventBus" src/
# src/core/event_bus.py:5: class EventBus:
# src/api/routes/orders.py:12: from core.event_bus import EventBus
# src/services/payment_service.py:8: from core.event_bus import EventBus
# ... (exact list of every file that uses it)
# Step 3: Explore for deep analysis
> "Use Explore to analyze whether there are events that are
published but nobody listens to (dead events)"
The sequence is: Explore (broad view) → grep (exact details) → Explore (deep analysis).
Complete example: investigate the auth system
# 1. General view with Explore
> "Use Explore to explain how
authentication works in this project"
# Explore reports: JWT with refresh tokens, middleware
# in src/middleware/auth.py, user model in src/models/,
# login endpoint in src/api/routes/auth.py
# 2. Grep to find all the protected routes
$ grep -rn "@require_auth\|@login_required" src/api/
# Exact list of 23 protected endpoints
# 3. Explore to find gaps
> "Use Explore to find endpoints in src/api/routes/
that access user data but don't have an authentication
decorator"
# Explore finds 2 endpoints without protection that should
# have it — this is a real security finding
Connection with the Project
In the Module Project (capsule 05) you're going to answer specific questions about a codebase using Explore. Many of those questions are semantic by nature:
- "How does a login request flow from the endpoint to the database?" — requires a behavior search
- "Where are user inputs validated?" — requires a concept search
- "What dependencies does the payments module have?" — requires a relationship search
Without semantic search, these questions require grep + manually reading dozens of files. With Explore, you get direct answers with context.
Troubleshooting
Problem 1: Explore doesn't find what I'm looking for
Cause: The prompt is too vague or uses different terminology from the codebase.
Solution: Be more specific and try synonyms:
# Vague:
> "Use Explore to find the security"
# Specific:
> "Use Explore to find where user permissions
are checked before accessing resources"
Problem 2: Explore returns too many results
Cause: The question is too broad.
Solution: Narrow it down by directory or module:
# Too broad:
> "Use Explore to find error handling"
# Narrowed:
> "Use Explore to find error handling
in src/api/routes/ — specifically HTTP errors"
Problem 3: grep is faster for my case
Cause: You're searching for an exact string you know.
Solution: Use grep. Not everything requires semantic search:
# For this, grep is superior:
grep -rn "from fastapi import" src/
# Instant, exact, complete result
Problem 4: I don't know whether to use grep or Explore
Cause: You haven't defined whether you're searching for text or a concept.
Solution: Ask yourself this question: "Do I know the exact name of what I'm looking for?"
- Yes → grep
- No, but I know what it does → Explore
Problem 5: Explore interprets my question incorrectly
Cause: Ambiguity in the prompt.
Solution: Add context:
# Ambiguous:
> "Use Explore to find the model"
# With context:
> "Use Explore to find the data model
(ORM/database model) for users, not the machine
learning model"
Exercises
Exercise 1: Identify the right tool (Easy)
For each search, decide whether you'd use grep or Explore and explain why:
- Find all the files that import
requests - Find where rate limiting is implemented
- Find the definition of the
UserServiceclass - Find possible SQL injections
- Find all the
TODOs in the project
See solution
- grep — searches for the exact string
from requests import/import requests - Explore — "rate limiting" can be implemented as a decorator, middleware, or counter without using that phrase
- grep — searches for the exact string
class UserService - Explore — SQL injection shows up as string concatenation in queries, f-strings with SQL, etc. — there's no single string to search for
- grep — searches for the exact string
TODO
General rule: if you can write the regex, use grep. If you need to describe the concept, use Explore.
Exercise 2: Reformulate for semantic search (Easy)
Convert these grep searches into semantic questions for Explore:
grep -r "try.*except" src/grep -r "sleep\|time.sleep" src/grep -r "os.environ\|getenv" src/
See solution
grep "try.*except"→ "Use Explore to find functions that handle errors and exceptions, including those that use conditionals to detect errors without try/except"grep "sleep"→ "Use Explore to find code that introduces delays, waits, or throttling, including asyncio.sleep, polling loops, and rate limiters"grep "os.environ"→ "Use Explore to find where environment configurations are read, including environment variables, .env files, config files, and secrets managers"
Note: the semantic version finds more because it includes synonyms and variants that grep doesn't capture.
Exercise 3: Combined search (Medium)
You have a Django project and you need to understand the permission system. Design a 3-step sequence combining grep and Explore:
See solution
# Step 1: Explore for a general view
> "Use Explore to explain how the permission
system works in this Django project. Does it use Django's
built-in system, a package like django-guardian,
or a custom implementation?"
# Step 2: grep to map exact usage
$ grep -rn "@permission_required\|has_perm\|user_passes_test" src/
$ grep -rn "PermissionMixin\|BasePermission" src/
# Step 3: Explore to find gaps
> "Use Explore to find views in src/views/
that access sensitive data but don't check
permissions in any way"
The logic: Explore gives the big picture, grep gives the exact numbers, Explore finds what's missing.
Exercise 4: Negative search (Medium)
Write 3 questions for Explore that search for the absence of something (things that should exist but don't):
See solution
- "Use Explore to find public functions in src/api/ that don't have docstrings"
- "Use Explore to find database models that don't have validation on their fields"
- "Use Explore to find endpoints that accept user input but don't sanitize it before processing it"
Why this matters: negative search is impossible with grep (you can't search for something that isn't there). It's one of the most valuable capabilities of semantic search for code auditing and vulnerability detection.
Exercise 5: Real case — Onboarding with mixed search (Hard)
You're onboarding to a 15K-line Python project. Design an investigation plan of 10 searches (a mix of grep and Explore) to understand:
- How authentication works
- Where the entry points are
- What database it uses and how it's accessed
See solution
# Authentication (4 searches):
1. Explore: "How does authentication work in this project?
JWT, sessions, OAuth, API keys?"
2. grep: grep -rn "SECRET_KEY\|JWT\|token" src/config/
3. Explore: "Which endpoints require authentication and which are public?"
4. grep: grep -rn "@auth\|@login\|@require" src/api/
# Entry points (3 searches):
5. grep: grep -rn "if __name__\|app.run\|uvicorn" src/
6. Explore: "What are all the entry points of this application?
Include CLI, web, workers, and scheduled tasks"
7. grep: grep -rn "@app.route\|@router" src/
# Database (3 searches):
8. Explore: "What database does this project use and how does it connect?
Does it use an ORM or raw queries?"
9. grep: grep -rn "DATABASE\|SQLALCHEMY\|psycopg\|pymongo" src/
10. Explore: "Are there raw SQL queries in the project? If so,
is any of them vulnerable to SQL injection?"
Pattern: each area uses Explore for the general view and grep for the exact data. This produces a complete map in minutes, not hours.
Exercise 6: Build your cheatsheet (Hard)
Create a quick reference table with 10 common search scenarios, indicating for each: recommended tool, exact prompt/command, and what kind of result you expect.
See solution
| Scenario | Tool | Command/Prompt | Expected result |
|---|---|---|---|
| Find a class definition | grep | grep -rn "class ClassName" src/ | Exact line + file |
| Understand data flow | Explore | "How does data flow from input to the DB?" | Textual flow diagram |
| Find a module's imports | grep | grep -rn "from module import" src/ | List of files |
| Find vulnerabilities | Explore | "Are there security vulnerabilities in src/api/?" | List with context |
| Find TODOs | grep | grep -rn "TODO|FIXME|HACK" src/ | List with lines |
| Understand error handling | Explore | "How does this project handle errors? Is there a consistent pattern?" | Pattern analysis |
| Find tests of a function | grep | grep -rn "test_function_name" tests/ | Test files |
| Find dead code | Explore | "Are there functions that are defined but never called?" | List of dead code |
| Find config values | grep | grep -rn "KEY_NAME" src/config/ | Exact values |
| Understand dependencies | Explore | "What does module X depend on? What depends on X?" | Dependency graph |
Principle: grep for what you can name, Explore for what you can describe.
Summary
In this capsule you learned:
- grep searches for text, Explore searches for meaning — it's the fundamental difference between the two tools
- Explore finds synonyms and variants that grep can't:
check_params,sanitize_input,ensure_validare all "validation" to Explore - Semantic search has 4 advanced techniques: by behavior, by impact, negative, and comparative
- Negative search is exclusive to Explore — you can't search for the absence of something with grep
- The professional workflow combines both: Explore (broad view) → grep (exact details) → Explore (deep analysis)
- The decision rule is simple: do you know the exact name? grep. Do you know what it does but not what it's called? Explore
Next capsule: Exploration Patterns — Top-Down, Dependency-Following, Feature-Tracing. You're going to learn three systematic strategies to investigate codebases with Explore, each optimized for a different type of question.
Additional Resources
- Claude Code Documentation - Subagents - Official reference on how subagents work, including Explore
- ripgrep (rg) - Documentation - The modern alternative to grep, faster and with better UX
- The Art of Searching Code - Sourcegraph Blog - Perspectives on searching code at scale
- Semantic Code Search - Papers with Code - Research on semantic code search
- grep vs ripgrep vs ag - Benchmarks - Comparison of text search tools
- How LLMs Understand Code - Anthropic Research - How language models understand source code
Module 2, Capsule 03 — Refactoring & Legacy Code with Claude Code Guide