Using Cursor Rules¶
Cursor rules are markdown files (.mdc format) stored in .cursor/rules/ that act as persistent context for the AI. They tell Cursor how your project works — API patterns, coding conventions, architecture decisions — so that generated code is consistent and correct from the first attempt.
What Are Cursor Rules?¶
A Cursor rule file is a markdown document with a YAML frontmatter header that controls when and how the rule is applied:
---
description: Gaia API patterns and conventions
globs:
- "backend/**/*.py"
- "sdk/**/*.py"
alwaysApply: false
---
# Gaia API Patterns
When working with the Gaia API, always use the `GaiaClient` class...
| Frontmatter Field | Purpose |
|---|---|
description | Human-readable summary shown in the rules panel. |
globs | File patterns that trigger this rule. When you edit a matching file, the rule is automatically included as context. |
alwaysApply | If true, the rule is included in every AI interaction regardless of which file you're editing. |
Rules are additive — multiple rules can be active at once. When you're editing a Python backend file, both gaia-api-patterns.mdc and gaia-fastapi-backend.mdc activate simultaneously.
Rules Included in This Project¶
gaia-api-patterns.mdc¶
Scope: backend/**/*.py, sdk/**/*.py
This rule documents the Gaia API itself. It teaches Cursor:
- Authentication — API key passed via
apiKeyheader, optionalsecurityContextheader for multi-tenancy. - Base URL — default
https://helios.cohesity.com/v2/mcm/gaia, configurable viaGAIA_BASE_URL. - Endpoint reference — every available endpoint with method, path, request body, and response shape.
- Conversation flow — how
conversationIdenables multi-turn queries. - Error format — standard error response shape with status code mapping.
When this rule is active, asking Cursor to "create an endpoint that queries Gaia" produces code that uses the correct HTTP method, path, headers, and request body — without you having to specify any of it.
Sample content from gaia-api-patterns.mdc
## Authentication
All requests require an `apiKey` header:
```python
headers = {
"apiKey": settings.gaia_api_key,
"Content-Type": "application/json",
"Accept": "application/json",
}
Key Endpoints¶
| Method | Path | Description |
|---|---|---|
| POST | /ask | Synchronous RAG query |
| POST | /ask/stream | Streaming RAG query (SSE) |
| PUT | /ask/exhaustive | Exhaustive document search |
| POST | /ask/refine | Refine answer with specific docs |
| GET | /datasets | List all datasets |
| ``` |
gaia-fastapi-backend.mdc¶
Scope: backend/**/*.py
This rule establishes backend architecture patterns:
- Project structure — where routes, services, models, and config live.
- Dependency injection — how to inject
GaiaClientandSettingsinto route handlers. - Streaming pattern —
StreamingResponsewithmedia_type="text/event-stream"for SSE forwarding. - Error handling — exception handlers that map
GaiaErrorsubclasses to HTTP status codes. - Session management — SQLite-backed sessions with TTL expiration.
Sample content from gaia-fastapi-backend.mdc
## GaiaClient as a Dependency
```python
from app.config import settings
from gaia_sdk import GaiaClient
async def get_gaia_client():
async with GaiaClient(
api_key=settings.gaia_api_key,
base_url=settings.gaia_base_url,
verify_ssl=settings.gaia_verify_ssl,
) as client:
yield client
Streaming Responses¶
from fastapi.responses import StreamingResponse
@router.post("/ask/stream")
async def ask_stream(body: AskRequest, gaia: GaiaClient = Depends(get_gaia_client)):
async def event_generator():
async for chunk in gaia.ask_stream_iter(body.dataset_names, body.query):
yield f"data: {chunk.data}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
gaia-react-frontend.mdc¶
Scope: frontend/src/**/*.{tsx,ts,jsx,js}
This rule guides React component generation:
- API client — centralized
apiClientwith base URL fromimport.meta.env.VITE_API_BASE_URL. - Streaming consumption — using
fetchwithReadableStreamfor SSE, notEventSource(which doesn't support POST). - Component patterns — chat message components, dataset selectors, search result lists.
- TypeScript types — interfaces matching the backend's response shapes.
- Error display — user-friendly error messages with retry options.
Sample content from gaia-react-frontend.mdc
## Streaming with fetch + ReadableStream
```typescript
async function streamAsk(datasetNames: string[], query: string, onChunk: (text: string) => void) {
const response = await fetch(`${API_BASE}/api/ask/stream`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ datasetNames, query }),
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
onChunk(text);
}
}
gaia-project-setup.mdc¶
Scope: alwaysApply: true
This rule provides high-level project context:
- Tech stack — Python 3.12, FastAPI, React 18, Vite, TypeScript, Tailwind CSS.
- Directory layout — monorepo with
backend/,frontend/,sdk/,docs/. - Key files — where to find configuration, routes, components, and SDK code.
- Development commands — how to start the dev server, run tests, build for production.
Because alwaysApply is true, this rule is active in every AI interaction, giving Cursor baseline awareness of the project regardless of which file you're editing.
How Rules Help AI Generate Better Code¶
Without rules, Cursor generates generic code that may not match your project's patterns:
# Generated with gaia-fastapi-backend.mdc active
@router.post("/ask", response_model=AskResponseSchema)
async def ask(
body: AskRequest,
gaia: GaiaClient = Depends(get_gaia_client),
):
response = await gaia.ask(
dataset_names=body.dataset_names,
query=body.query,
conversation_id=body.conversation_id,
)
return AskResponseSchema.from_gaia_response(response)
The rules-aware version uses async/await, the GaiaClient SDK, proper dependency injection, Pydantic models, and the correct router pattern — all without you having to specify any of it in the prompt.
Creating Custom Rules¶
As your application grows, add rules for your specific patterns. Create a new .mdc file in .cursor/rules/:
---
description: Custom business logic patterns for [Your App]
globs:
- "backend/app/services/**/*.py"
alwaysApply: false
---
# [Your App] Business Logic
## Data Processing Pipeline
When creating data processing services, follow this pattern:
1. Validate input with Pydantic models
2. Query Gaia using the service layer (never call GaiaClient directly from routes)
3. Transform the response for the frontend
4. Cache results when appropriate
## Domain-Specific Conventions
- Always include `department` context when querying HR datasets
- Financial queries must include a `fiscal_year` parameter
- ...
When to Create a Rule¶
| Scenario | Create a Rule? |
|---|---|
| You find yourself repeating the same instructions to the AI | Yes |
| A specific pattern is used across multiple files | Yes |
| A convention applies to only one file | No — use inline comments instead |
| You want to document an external API's behavior | Yes |
| You want to enforce code style | Maybe — linters are better for style enforcement |
Best Practices for Rule Authoring¶
Be Concrete, Not Abstract¶
Show actual code examples, not vague descriptions:
<!-- Bad -->
Use proper error handling in all endpoints.
<!-- Good -->
Wrap all Gaia API calls in try/except and map exceptions:
```python
try:
response = await gaia.ask(datasets, query)
except GaiaAuthError:
raise HTTPException(status_code=401, detail="Invalid Gaia API key")
except GaiaRateLimitError:
raise HTTPException(status_code=429, detail="Rate limit exceeded")
except GaiaError as e:
raise HTTPException(status_code=502, detail=f"Gaia API error: {e}")
```
Scope Rules Narrowly¶
Use globs to target specific file types. A rule about React components shouldn't activate when editing Python files.
Keep Rules Focused¶
One rule per concern. Don't combine API patterns, project structure, and deployment configuration in a single rule.
Update Rules as Patterns Evolve¶
Rules are living documents. When you discover a better pattern, update the rule so all future AI interactions use the improved approach.
Include Anti-Patterns¶
Show what not to do alongside the correct approach:
## Auth Pattern
NEVER pass the API key from the frontend:
```typescript
// WRONG — exposes API key in client bundle
fetch("/api/ask", { headers: { "apiKey": import.meta.env.VITE_GAIA_KEY } })
```
ALWAYS let the backend handle authentication:
```typescript
// CORRECT — backend adds the API key server-side
fetch("/api/ask", { body: JSON.stringify({ query }) })
```
Next Steps¶
- AI Development Workflow — Put these rules to work in a complete development workflow.
- Cursor IDE Setup — Revisit Cursor configuration and keyboard shortcuts.