Skip to content

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:

Markdown
---
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:

  • AuthenticationAPI key passed via apiKey header, optional securityContext header for multi-tenancy.
  • Base URL — default https://helios.cohesity.com/v2/mcm/gaia, configurable via GAIA_BASE_URL.
  • Endpoint reference — every available endpoint with method, path, request body, and response shape.
  • Conversation flow — how conversationId enables 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
Markdown
## 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 GaiaClient and Settings into route handlers.
  • Streaming patternStreamingResponse with media_type="text/event-stream" for SSE forwarding.
  • Error handling — exception handlers that map GaiaError subclasses to HTTP status codes.
  • Session management — SQLite-backed sessions with TTL expiration.
Sample content from gaia-fastapi-backend.mdc
Markdown
## 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

Python
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 apiClient with base URL from import.meta.env.VITE_API_BASE_URL.
  • Streaming consumption — using fetch with ReadableStream for SSE, not EventSource (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

Markdown
## 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:

Python
# Generic FastAPI endpoint — misses project conventions
@app.post("/ask")
async def ask(query: str):
    import requests
    response = requests.post("https://api.example.com/ask", json={"query": query})
    return response.json()
Python
# 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/:

Markdown
---
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:

Markdown
<!-- 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:

Markdown
## 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