# RLM (Recursive Language Model) Agent Notes

The `rcl-rlm` tool is the Python-based Recursive Language Model that queries message data using LLM-generated code. It supports multiple data sources: local iMessage (via shadow.db) and cloud Gmail (via ModelRelay data_source wrapper).

## Quick Start

```bash
cd tools/rcl-rlm

# Query iMessage (default)
uv run rcl-rlm "What topics do people message me about?" --db ~/Library/Application\ Support/Recall/shadow.db

# Run with verbose output to see sub-calls
uv run rcl-rlm "Summarize my conversations from last week" --verbose

# Query Gmail (cloud)
uv run rcl-rlm "What emails did I get from GitHub today?" --source gmail
uv run rcl-rlm "Summarize my unread emails" --source gmail --verbose

# Run benchmarks
just bench-rlm-semantic --verbose
just bench-rlm-factual --verbose
```

## Architecture

The RLM follows the peek-partition-process pattern:
1. **Peek** - Get database stats and structure via `get_stats()`
2. **Partition** - LLM decides how to chunk the data
3. **Process** - Execute queries and recursive `llm_query()` sub-calls
4. **Aggregate** - Combine results into final answer

### Key Modules

| Module | Purpose |
|--------|---------|
| `rlm.py` | Main orchestration, iteration loop |
| `sandbox.py` | RestrictedPython execution environment |
| `prompts.py` | System prompts for root and sub-calls |
| `modelrelay.py` | ModelRelay API client |
| `host.py` | Entry point for XPC/subprocess calls (delegates to `rlm_runner`) |
| `runner_adapter.py` | Adapter hook used by `rlm_runner` to execute Recall RLM |
| `fingerprint.py` | Request fingerprinting for debugging |
| `cloud_auth.py` | OAuth flow, token storage for cloud sources |
| `gmail_rlm.py` | Gmail RLM via ModelRelay data_source wrapper |

### Sandbox Functions

Available to LLM-generated code:
- `query(sql)` - Execute read-only SQL against shadow.db
- `search(terms, limit, sender, chat, days)` - FTS search
- `get_messages(limit)` - Bulk message retrieval
- `get_stats()` - Database statistics
- `get_chats()` - List all chats
- `get_recent(days, limit)` - Recent messages
- `llm_query(prompt, context)` - Recursive LLM sub-call
- `chunk_data(data, size)` - Split data for parallel processing

## Design Principles

**The LLM is the reasoning layer.** It writes Python code that executes in a sandboxed REPL. The sandbox provides thin data access functions that return raw data. The LLM decides how to query, join, filter, and interpret.

**Do NOT add logic to the data access layer:**
- Helper functions in `sandbox.py` should be thin wrappers around SQL
- Do not add JOINs, transformations, or business logic to helpers
- If the LLM needs complex queries, it should write them via `query()`
- If output is wrong, fix the **prompt** (`prompts.py`), not the data functions

## LLM Client Naming

- Use `responses_create(...)` for LLM calls (message-based), mirroring ModelRelay `/responses`.
- Avoid chat/completions terminology in protocol surfaces to prevent confusion with OpenAI APIs.

## Data Sources

### iMessage (default)
- Uses local `shadow.db` SQLite database
- LLM generates Python code executed in RestrictedPython sandbox
- Data access via thin helper functions (`query()`, `search()`, etc.)

### Gmail (cloud)
- Uses ModelRelay's data_source wrapper pattern
- Calls cloud backend wrapper endpoints for Gmail API access
- First-time use triggers OAuth flow (opens browser for Google sign-in)
- Credentials stored in `~/.config/recall/credentials.json`

## Configuration

Environment variables:
- `MODELRELAY_API_KEY` - Required for LLM calls
- `RECALL_RLM_ROOT_MODEL` - Model for main reasoning (default: gemini-3-flash-preview)
- `RECALL_RLM_SUBCALL_MODEL` - Model for sub-calls (default: same as root)
- `RECALL_RLM_MAX_DEPTH` - Max recursion depth (default: 1)
- `RECALL_RLM_MAX_CALLS` - Max sub-calls per query (default: 10)

**Gmail-specific:**
- `RECALL_CLOUD_URL` - Override cloud backend URL (default: https://recall-ai.fly.dev)
- `RECALL_CLOUD_SESSION` - Pre-configured session token (for CI/scripts, skips OAuth)

## Testing

```bash
# Syntax check
uv run python -m py_compile src/rcl_rlm/sandbox.py

# Run semantic benchmark (should be 100% accuracy)
just bench-rlm-semantic --verbose

# Run factual benchmark
just bench-rlm-factual --verbose

# Run both and save results
just bench-compare
```

## Integration with Swift

The RLM is called from Swift via:
1. **XPC Service** - `RecallRLMHelper.xpc` bundles Python runtime
2. **Direct subprocess** - `RLMHelperRunner` spawns Python process

Request/response format is JSON over stdin/stdout. See `host.py` for the entry point.

## Debugging

```bash
# Verbose mode shows iteration details
just rlm-query "query" --verbose

# Check fingerprint for reproducibility
# Response includes fingerprint with db hash, model info, limits
```

Logs go to stderr in JSON format for structured parsing.
