# rcl-rlm

RLM (Recursive Language Model) for querying your messages. Uses iterative code execution to search and analyze your messages. Supports multiple data sources: local iMessage (via shadow.db) and cloud Gmail (via ModelRelay data_source wrapper).

## How it works

1. You ask a question about your messages
2. LLM generates Python code to search the database
3. Code executes locally in a sandbox
4. LLM sees results and either answers or writes more code
5. Repeat until answer is found

This approach handles nuanced queries that keyword search can't:
- "What projects did we discuss?" (finds "initiative", "proposal", "idea")
- "When did I make dinner plans with John?" (reasons about context)
- "Who sends me the most messages on weekends?" (aggregates and analyzes)

## Setup

```bash
cd tools/rcl-rlm

# Create virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install dependencies
pip install -e .

# Set API key
export MODELRELAY_API_KEY=your_key_here
```

## Usage

### iMessage (default)

```bash
# Basic query
rcl-rlm "What did John say about the project?"

# Verbose mode (shows code execution)
rcl-rlm "Find messages about dinner plans" --verbose

# Show full execution log
rcl-rlm "Who messages me the most?" --show-log

# Custom database path
rcl-rlm "Recent messages" --db /path/to/shadow.db

# Limit sandbox stdout output (default: 25000 chars)
rcl-rlm "Summarize recent messages" --max-output-chars 25000
```

### Gmail

```bash
# Query Gmail
rcl-rlm "What emails did I get from GitHub today?" --source gmail

# Verbose mode
rcl-rlm "Summarize my unread emails" --source gmail --verbose

# First-time use opens browser for Google OAuth sign-in
# Credentials are saved to ~/.config/recall/credentials.json
```

## Security

The code execution is sandboxed with multiple layers:

### 1. RestrictedPython (code-level)

Only safe operations are allowed:
- ✅ `sqlite3` queries (via `query()` function)
- ✅ `re` regex operations
- ✅ `json` parsing
- ✅ `collections.Counter`, `defaultdict`
- ✅ `datetime` operations
- ❌ File I/O (open, read, write)
- ❌ Network (socket, urllib, requests)
- ❌ Shell execution (subprocess, etc.)
- ❌ Dynamic imports

### 2. Read-only Database

Database is opened with `?mode=ro` - writes are impossible at SQLite level.

### 3. macOS sandbox-exec (OS-level, optional)

For additional isolation, run with macOS sandbox:

```bash
sandbox-exec -f sandbox.sb python3 -m rcl_rlm "query"
```

This restricts file access and blocks all network at the OS level.

## Architecture

### iMessage (local)

```
User Question
     │
     ▼
┌─────────────────┐
│  ModelRelay API │  ◄── LLM reasoning (cloud)
└────────┬────────┘
         │ generates code
         ▼
┌─────────────────┐
│ RestrictedPython│  ◄── Code sandbox (local)
│    Sandbox      │
└────────┬────────┘
         │ query()
         ▼
┌─────────────────┐
│   shadow.db     │  ◄── Message data (local, read-only)
│   (SQLite)      │
└─────────────────┘
```

Data never leaves your device. Only the question and code outputs go to ModelRelay.

### Gmail (cloud)

```
User Question
     │
     ▼
┌─────────────────┐
│  ModelRelay RLM │  ◄── LLM reasoning + data_source sandbox
└────────┬────────┘
         │ data_source_search(), data_source_content()
         ▼
┌─────────────────┐
│  Cloud Backend  │  ◄── Wrapper translates to Gmail API
│ (recall-ai.fly) │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│   Gmail API     │  ◄── Your email data
└─────────────────┘
```

Gmail uses ModelRelay's data_source wrapper pattern. The cloud backend handles OAuth token management and Gmail API calls. First-time use requires browser-based Google sign-in.

## Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `MODELRELAY_API_KEY` | Yes | Your ModelRelay API key |
| `SHADOW_DB_PATH` | No | Path to shadow.db (default: `~/Library/Application Support/RecallRLM/shadow.db`) |
| `CONTACTS_DB_PATH` | No | Path to contacts.db (default: alongside shadow.db) |
| `RECALL_RLM_ROOT_MODEL` | No | Override the root model (default: `gemini-3-flash-preview`) |
| `RECALL_RLM_SUBCALL_MODEL` | No | Override the subcall model (defaults to root model) |
| `RECALL_RLM_MODEL` | No | Override the default model (default: `gemini-3-flash-preview`) |
| `MODELRELAY_MODEL` | No | Alternate env var for model override |
| `RECALL_CLOUD_URL` | No | Override cloud backend URL (default: `https://recall-ai.fly.dev`) |
| `RECALL_CLOUD_SESSION` | No | Pre-configured cloud session token (skips OAuth, for CI/scripts) |

## Output budget

Sandbox stdout is capped to keep the model summarizing instead of dumping raw rows.
Use `--max-output-chars` to tune the limit (default: 25000).

## Model resolution precedence

The CLI allows environment overrides; embedded macOS host paths do not. This keeps app behavior stable
even when shell env vars are present.

CLI precedence (highest to lowest):
- Root model: `--root-model` > `RECALL_RLM_ROOT_MODEL` > `RECALL_RLM_MODEL`/`MODELRELAY_MODEL` > default
- Subcall model: `--subcall-model` > `RECALL_RLM_SUBCALL_MODEL` > `--root-model` >
  `RECALL_RLM_ROOT_MODEL` > `RECALL_RLM_MODEL`/`MODELRELAY_MODEL` > default

Embedded host precedence (macOS helper):
- Root model: explicit request payload only (no env overrides) > default
- Subcall model: explicit request payload only (no env overrides) > explicit root model > default

## Comparison with SQL Agent

| Feature | SQL Agent | RLM |
|---------|-----------|-----|
| Approach | LLM generates SQL | LLM generates Python + SQL |
| Iterations | Single SQL query | Multiple code executions |
| Semantic queries | Limited (term expansion) | Strong (code-based reasoning) |
| Aggregation | SQL only | Python + SQL |
| Latency | ~14s | ~5-15s (depends on iterations) |

RLM is better for complex queries that require reasoning over results.
