# RFC-002-R1: Session Compression for Compiler Input

**Status:** Proposed  
**Created:** 2026-02-20  
**Supersedes:** RFC-002 §4.3 (partial — context budget calculation)

---

## 1. Problem Statement

### 1.1 Token Explosion

During development, we discovered that coding sessions generate massive amounts of data:

| Data Type | Daily Volume | Percentage | Signal Value |
|-----------|-------------|------------|--------------|
| text (user/assistant messages) | 562K tokens | 1.3% | 🟢 Highest |
| reasoning (thinking) | 1.8M tokens | 4.2% | 🟢 High |
| tool output (file contents, command results) | 8.5M tokens | 21.6% | 🔴 Low |
| tool input (file paths, commands) | 1.8M tokens | 4.6% | 🟡 Medium |
| tool metadata (diffs, diagnostics) | 28.8M tokens | 67.4% | 🔴 Lowest |
| **Total** | **42.7M tokens/day** | 100% | — |

At this rate, sending raw session data to the Compiler costs ~$3.20/day ($96/month) with Gemini Flash.

### 1.2 The Tool Output Paradox

**Observation**: Tool output (file contents, command results) consumes 21.6% of tokens but provides almost no value for pattern extraction.

**Why?**
- File contents are instantaneous — they change between sessions
- The decision to read a file is captured in `reasoning` and `text`
- The actual content doesn't help the Compiler learn reusable patterns

**What about tool metadata (67.4%)?**
- Contains `oldString/newString` from edits — redundant with `text` descriptions
- Contains linter diagnostics — often 200MB+ for a single project scan
- Provides no unique signal for pattern extraction

### 1.3 Double Token Cost for MCP

For MCP clients (Claude Code, etc.), the original design required:
```
1. Agent receives tool output (already in context) → sunk cost
2. Agent calls log_turn(tool_output) → sends again → double cost!
```

At Claude/GPT-4 prices, this is unacceptable.

---

## 2. Design Principles

### 2.1 Data Value Hierarchy

| Type | What It Contains | Pattern Extraction Value | Retention |
|------|-----------------|-------------------------|-----------|
| **text** | User intent, assistant responses, problem statements, solutions | 🟢 Critical — "why" decisions | Full |
| **reasoning** | Agent's thought process, trade-offs, decision chains | 🟢 High — "how" decisions | Full |
| **tool input** | File paths, commands, search patterns | 🟡 Medium — "what" was touched | Summary only |
| **tool output** | File contents, command results | 🔴 Low — already processed by agent | Status + error only |
| **tool metadata** | Diffs, linter diagnostics | 🔴 None — redundant/noisy | Drop |

### 2.2 Compression Principle

> **Compress at capture time, not compile time.**

Rationale:
- Preparation cost is paid once
- Compile can iterate over different strategies without re-processing
- Storage is minimal compared to original data

### 2.3 Unified Interface Principle

> **Same interface across Plugin, SDK, and MCP.**

Rationale:
- Reduces cognitive load
- Consistent behavior regardless of entry point
- Single source of truth for data format

---

## 3. Layer Design

### 3.1 Layer 0: Rule-Based Compression (Zero Cost)

Applied at capture time with deterministic rules:

**Input → Output mapping:**

| Input | Compression Rule | Example Output |
|-------|-----------------|----------------|
| `text` | Keep full content | `"auth.py 报错了"` |
| `reasoning` | Keep full content | `"需要检查 UserValidator..."` |
| `tool input: read` | Keep file path only | `"src/auth.py"` |
| `tool input: bash` | Keep command only | `"npm test"` |
| `tool input: edit` | Keep file path only | `"src/auth.py"` |
| `tool output: success` | Keep size only | `"[50KB, 1200 lines]"` |
| `tool output: error` | Keep first 500 chars | `"ImportError: No module named 'jwt'"` |
| `tool metadata` | Drop entirely | — |

**Result: 42.7M → ~3M tokens (92% reduction)**

### 3.2 Layer 1: Small Model Summarization (Optional, Local)

Applied at compile time with local model (Ollama/MLX):

**When to use:**
- Sessions exceed 1M tokens after Layer 0
- User enables via config: `use_layer1_summarizer = true`

**What it does:**
- Summarizes long reasoning chains to decision points
- Extracts key trade-offs from conversations
- Runs on local hardware (no API cost)

**Models:**
- `llama3.2:3b` via Ollama
- `gemma2:2b` via Ollama
- Any MLX-compatible model

**Result: 3M → ~2M tokens (additional 33% reduction)**

---

## 4. Interface Specification

### 4.1 Unified Event Format

```python
@dataclass
class Event:
    type: Literal["user", "assistant", "reasoning", "tool"]
    content: str
    # tool-specific (ignored for other types)
    input: str = ""      # "src/auth.py", "npm test"
    status: str = ""     # "success" | "error"
    error: str = ""      # error message (max 500 chars)
```

### 4.2 Plugin Interface (OpenCode)

```typescript
// OpenCode Plugin extracts events and compresses before sending
const events = message.parts?.map(part => {
  if (part.type === "text" || part.type === "reasoning") {
    return { type: part.type, content: part.text }
  }
  if (part.type === "tool") {
    return {
      type: "tool",
      content: part.tool,
      input: summarize_input(part.tool, part.state?.input),
      status: part.state?.status,
      error: part.state?.status === "error" 
        ? part.state?.output?.slice(0, 500) 
        : undefined,
    }
  }
  return null
}).filter(Boolean)

execFile("lattice", ["ingest", "--stdin"], { 
  input: JSON.stringify({ session_id, events }) 
})
```

### 4.3 SDK Interface

```python
class Client:
    def log(
        self,
        type: Literal["user", "assistant", "reasoning", "tool"],
        content: str,
        *,
        input: str = "",
        status: str = "",
        error: str = "",
    ) -> None:
        """
        Record an event.
        
        type=user/assistant/reasoning:
            client.log("user", "auth 报错了")
            client.log("reasoning", "需要检查 UserValidator...")
        
        type=tool (only summary, not raw output!):
            client.log("tool", "read", input="src/auth.py", status="success")
            client.log("tool", "edit", input="src/auth.py", status="error", error="syntax")
        """
```

### 4.4 MCP Interface

```python
@server.tool
def log(
    type: Literal["user", "assistant", "reasoning", "tool"],
    content: str,
    *,
    input: str = "",
    status: str = "",
    error: str = "",
) -> dict:
    """
    Record event to Lattice memory. Keep it brief to save tokens.
    
    type=user/assistant/reasoning: Pass full content.
    type=tool: Pass only summary (status + error if any), NOT raw output.
    
    Examples:
      log("user", "auth 报错了")
      log("tool", "read", input="src/auth.py", status="success")
      log("tool", "edit", input="src/auth.py", status="error", error="syntax")
    """
```

---

## 5. Database Schema

### 5.1 Events Table

```sql
CREATE TABLE events (
    id INTEGER PRIMARY KEY,
    external_id TEXT UNIQUE NOT NULL,
    session_id TEXT NOT NULL,
    timestamp TEXT NOT NULL,
    type TEXT NOT NULL,           -- user | assistant | reasoning | tool
    content TEXT NOT NULL,
    -- tool-specific
    tool_input TEXT,
    tool_status TEXT,
    tool_error TEXT,
);

CREATE INDEX idx_events_session ON events(session_id);
CREATE INDEX idx_events_type ON events(type);
CREATE VIRTUAL TABLE events_fts USING fts5(content, content=events, content_rowid=id);
```

### 5.2 Migration from `logs` Table

The `logs` table is renamed to `events` with these changes:
- `role` → `type` (clearer semantics)
- `metadata` JSON → typed columns (`tool_input`, `tool_status`, `tool_error`)
- No `tool_output` column (compressed at capture)

---

## 6. Compile-Time Data Preparation

```python
def prepare_for_compile(session_id: str, config: Config) -> str:
    """Load compressed events, optionally apply Layer 1."""
    
    events = query("SELECT * FROM events WHERE session_id = ? ORDER BY timestamp", session_id)
    
    lines = []
    for e in events:
        if e.type in ("user", "assistant", "reasoning"):
            prefix = e.type.upper()
            lines.append(f"[{prefix}] {e.content}")
        elif e.type == "tool":
            error_part = f" ERROR: {e.tool_error}" if e.tool_error else ""
            lines.append(f"[TOOL:{e.content}] {e.tool_input} -> {e.tool_status}{error_part}")
    
    content = "\n\n".join(lines)
    
    # Optional Layer 1 summarization
    if config.use_layer1_summarizer and has_local_model():
        content = layer1_summarize(content)
    
    return content
```

**Example output:**
```
[USER] auth.py 报错了，帮我看看

[REASONING] 用户说 auth.py 报错，需要先读取文件

[TOOL:read] src/auth.py -> success

[REASONING] 发现 UserValidator 没有处理 None 的情况

[TOOL:edit] src/auth.py -> success

[ASSISTANT] 我修复了 UserValidator 的 None 处理问题
```

---

## 7. Cost Analysis

### 7.1 Daily Compile Cost (per day, Gemini Flash at $0.075/1M)

| Strategy | Tokens | Daily Cost | Monthly Cost |
|----------|--------|------------|--------------|
| Raw (no compression) | 42.7M | $3.20 | $96 |
| Layer 0 only | 3M | $0.23 | $7 |
| Layer 0 + Layer 1 | 2M | $0.15 | $5 |

### 7.2 Savings Summary

- **Layer 0**: 92% reduction, zero additional cost
- **Layer 1**: Additional 33% reduction, requires local model

---

## 8. Configuration

### 8.1 New Config Options

```toml
[compiler]
# ... existing options ...

[layer1]
enabled = false                    # Enable Layer 1 summarization
model = "ollama/llama3.2:3b"      # Local model to use
max_chunk_size = 4000             # Max tokens per summarization call

[thresholds]
# ... existing options ...
per_session_tokens = 12000        # Max tokens per session after Layer 0
```

---

## 9. Trade-offs

| Decision | Gain | Sacrifice |
|----------|------|-----------|
| Drop tool output | 92% token reduction | Lose ability to search file contents |
| Drop tool metadata | 67% token reduction | Lose detailed diff history |
| Unified `log()` interface | Consistency | Slightly more verbose than specialized methods |
| Layer 1 is optional | Flexibility | Users must have local model for maximum compression |

---

## 10. Open Questions

| Question | Owner | Impact |
|----------|-------|--------|
| Should we store original tool output for debugging? | User | Increases storage cost |
| What's the minimum viable local model for Layer 1? | Engineer | Affects compression quality |
| Should Layer 1 be applied to all sessions or only large ones? | User | Affects compile latency |

---

## 11. References

- RFC-002: Original Lattice architecture
- Data analysis: OpenCode session logs (Feb 14-20, 2026)
- Compression patterns: LLM agent memory systems
