# Request Optimization

How to minimize LLM requests when digesting documents and querying the wiki.

## Understanding the Constraints

### The binding limit: raw_read truncation

`raw_read` truncates text content at **10,000 characters per document** — regardless of context window size. This cap is what determines how much of a document your agent actually sees.

| Language | 10K chars ≈ tokens | Docs per 200k context |
|----------|--------------------|----------------------:|
| English  | ~2,500             | ~72                   |
| Chinese  | ~5,000             | ~36                   |

**Documents shorter than 10K chars** (~6-7 pages) are fully read — quality is high.  
**Documents longer than 10K chars** — only the first portion is read unless you paginate.

Check your corpus size before designing a workflow:

```bash
find raw/ -type f | xargs wc -c | tail -1
# bytes ÷ 4 ≈ tokens (English)
# bytes ÷ 2 ≈ tokens (Chinese)
```

---

## Pagination for Large Documents

When you specify a pagination parameter, `raw_read` bypasses the 10K truncation and returns the requested range in full, along with metadata for follow-up reads.

| Format | Param | Example | Response includes |
|--------|-------|---------|------------------|
| PDF    | `pages`  | `"1-10"`, `"3"`, `"1-3,7-10"` | `pagination.total_pages` |
| PPTX   | `pages`  | `"1-20"` | `pagination.total_slides` |
| XLSX   | `sheet`  | `"Revenue"` | `pagination.sheet_names`, `total_sheets` |
| DOCX / text | `offset` + `limit` | `offset: 0, limit: 200` | `pagination.total_lines`, `truncated`, `next_offset` |

**XLSX tip:** always read without `sheet` first — the response lists all available sheet names so you can then target each one individually.

**DOCX/text tip:** use `next_offset` from the response to paginate: if `truncated: true`, call again with `offset: <next_offset>`.

---

## Batch Digest Strategy

### How many docs per request?

Because `raw_read` caps each document at 10K chars, batching is predictable:

```
available context (e.g. 180k) ÷ tokens per doc = max docs per request
  English: 180k ÷ 2,500 ≈ 72 docs
  Chinese: 180k ÷ 5,000 ≈ 36 docs
```

For a 10 MB corpus, you need roughly 15–25 requests minimum — context window size doesn't change this math.

### Quality tradeoffs

| Approach | Requests | Per-doc depth | Cross-doc patterns |
|----------|:--------:|:-------------:|:------------------:|
| One doc at a time | N | High | Low |
| Batch by topic (10–15/group) | N/10 | Medium | High |
| All at once (50+) | 1 | Low / truncated | Medium |

The sweet spot for most use cases is **batching by topic**, 10–15 docs per request, and targeting synthesis pages as output.

### Output strategy: synthesis over individual pages

When batching, write one synthesis page per batch rather than one page per document:

```markdown
---
title: Q1 Engineering Reports — Key Findings
type: synthesis
derived_from: [report-jan.md, report-feb.md, report-mar.md]
---

# Cross-cutting insights from Q1 reports
...
```

Future queries read **1 page** instead of 15 individual reads — this is where the real request savings compound over time.

---

## Per-Tool Shortcuts (saves 1 request each)

These save single round trips in the most common patterns:

### `wiki_read` — read multiple pages at once

Instead of 3 separate reads (or a verbose `batch` block):

```json
{ "tool": "wiki_read", "args": { "pages": ["concept-a.md", "concept-b.md", "concept-c.md"] } }
```

Returns an array of results. Missing pages are marked `not_found: true` rather than throwing.

### `wiki_write` — get the written content back

Set `return_content: true` to receive the final content inline. Eliminates a follow-up `wiki_read` in write-then-reference workflows:

```json
{ "tool": "wiki_write", "args": {
    "page": "synthesis.md",
    "content": "---\ntitle: Synthesis\n---\nContent...",
    "return_content": true
}}
```

Response includes `content` — no need to call `wiki_read` afterwards.

### `wiki_search` — filter by type or tags inline

Instead of `wiki_search` + manual filtering or a separate `wiki_list`:

```json
{ "tool": "wiki_search", "args": { "query": "neural networks", "type": "concept", "tags": ["ml"] } }
```

Filter is applied post-ranking — BM25 scores are unaffected. `tags` is an OR match (any tag satisfies).

### `raw_coverage` — one call replaces multi-step coverage discovery

Instead of `raw_list` + `wiki_list`/`wiki_search` + client-side diff to figure out which sources are still uncompiled:

```json
{ "tool": "raw_coverage", "args": { "limit": 50, "sort": "newest" } }
```

Returns `{ totalRaw, coveredRaw, uncoveredRaw, coverageRatio, uncovered[], truncated }` — everything the agent needs to pick what to compile next.

**Usage pattern — session-scoped, not per-turn.** Call `raw_coverage` at the **start** of a compilation session to plan, or at the **end** to confirm progress. Do **not** call it every turn as a "live dashboard" — the coverage set changes only when `raw_ingest` or `wiki_write` runs, so re-polling without those events just burns requests. Cache the result in the agent's working memory until you actually add or digest something.

### `wiki_search` — `knowledge_gap` on miss → skip retry loop

When search returns 0 results, the response includes a ready-to-use `knowledge_gap`:

```json
{
  "results": [],
  "count": 0,
  "knowledge_gap": {
    "query": "transformer attention",
    "suggested_page": "concept-transformer-attention.md",
    "suggested_title": "Transformer Attention",
    "suggested_type": "concept",
    "suggested_tags": ["transformer", "attention-mechanism"],
    "hint": "No pages found. Use wiki_write to create ..."
  }
}
```

**Without `knowledge_gap`** (old pattern):
1. `wiki_search` → 0 results
2. Retry with synonyms × N
3. `wiki_list` to see what exists
4. Infer type/tags manually
5. `wiki_write`

**With `knowledge_gap`** (new pattern):
1. `wiki_search` → 0 results + `knowledge_gap`
2. `wiki_write` with `suggested_page` / `suggested_type` / `suggested_tags`

Saves N retry searches + `wiki_list` + manual classify step. `knowledge_gap` is computed inside the existing `wiki_search` call — zero extra requests.

---

## Batch Tools

The `batch` tool collapses multiple operations into a single MCP request:

```json
{ "tool": "batch", "args": { "operations": [
  { "tool": "wiki_read",   "args": { "page": "concept-a.md" } },
  { "tool": "wiki_read",   "args": { "page": "concept-b.md" } },
  { "tool": "wiki_write",  "args": { "page": "synthesis-x.md", "content": "..." } }
]}}
```

| Workflow | Without | With shortcuts/batch | Savings |
|----------|:---:|:---:|:---:|
| Read 3 related pages | 3 | **1** (`wiki_read pages[]`) | 67% |
| Write then reference | 2 | **1** (`return_content: true`) | 50% |
| Search + filter by type | 2 | **1** (`type` param) | 50% |
| Search miss → create page | 3–5+ | **2** (`knowledge_gap`) | 60–80% |
| Discover uncompiled sources | 2–3 | **1** (`raw_coverage`) | 50–67% |
| Import 5 files | 5 | **1** | 80% |
| Digest 5 sources into wiki | 10 | **2** | 80% |
| Search + read top results | 6 | **1** | 83% |
| Ingest 50-file directory | 50+ | **1** | 98% |

**`wiki_search` + `wiki_read pages:[...]`** — Search first, then read the top pages in one follow-up call. This replaces the old dedicated `wiki_search_read` surface without losing batching efficiency.

**`knowledge_ingest` with `mode: "batch"`** — Scan a directory, import files, extract text with provenance (per-page PDF, per-sheet XLSX, per-slide PPTX), chunk, and pack into digest packs — all in one request.

**`knowledge_ingest` with `mode: "digest_write"`** — Write LLM-generated summaries back to wiki with structured provenance (`sources`, `sourcePacks`). Batch writes with one index rebuild at the end.
