# Loom 0.7: quando la risposta è a pagina 40 di un PDF

> Loom 0.7 risolve il limite più fastidioso con i documenti lunghi: invece dei primi 8000 caratteri, all'LLM arriva il passaggio che risponde davvero alla domanda. In più un tool MCP loom_grep per le ricerche esatte, la lettura a pezzi dei file lunghi, il .loomignore e un bug che spegneva in silenzio la ricerca ibrida.

- URL: https://mautoblog.com/posts/loom-07-file-lunghi-grep-mcp-loomignore-2026/
- Lingua: it
- Pubblicato: 2026-10-15
- Autore: Matteo
- Categorie: Progetti, Intelligenza Artificiale
- Tags: Loom, RAG, IA Locale, MCP
- Guida: Loom (parte 3)
- Traduzione (en): https://mautoblog.com/en/posts/loom-07-long-files-grep-mcp-loomignore-2026/

---

A giugno [Loom ha imparato la ricerca semantica](/posts/loom-embeddings-ricerca-semantica-multilingua-http-2026/), con l'interruttore spento di default. Con le note brevi Loom funziona benissimo. Con i documenti lunghi, come manuali in PDF o documentazione tecnica di centinaia di pagine, veniva fuori un difetto che con le note non si vedeva.

Oggi esce la **0.7**, e quasi tutto ruota attorno a una frase: *la risposta era a pagina 40*.

---

## Il problema: Loom leggeva solo l'inizio

Quando fai una domanda con `loom ask`, Loom trova i file pertinenti e li manda all'LLM in **una sola chiamata**. Per non sfondare il contesto ogni file ha un budget: 8000 caratteri, circa 2000 token.

Con una nota di mezza pagina non cambia niente: il file entra tutto. Con un PDF di 60 pagine invece Loom tagliava **i primi 8000 caratteri** e buttava il resto. Copertina, indice, introduzione. Se la risposta stava al capitolo 7, il modello non la vedeva mai e rispondeva con quello che aveva: l'indice.

Il bello (si fa per dire) è che la ricerca funzionava benissimo. Il file giusto veniva trovato, stava in cima ai risultati, e poi arrivava all'LLM senza la parte che serviva.

---

## La soluzione: l'inizio più il passaggio giusto

Dalla 0.7, quando un file supera il budget, Loom manda due pezzi:

1. **l'inizio del file** (circa 1200 caratteri): titolo, abstract, intro. Servono quando la domanda è "di cosa parla questo documento?";
2. **la finestra che risponde meglio alla domanda**, presa da qualunque punto del file.

All'LLM arriva una cosa così:

```text
Manuale di installazione – Server XYZ
1. Introduzione
Questo documento descrive...

[...]

7.3 Connection pooling con PgBouncer
Per limitare le connessioni al database impostare pool_mode = transaction...
[...truncated]
```

Come sceglie la finestra? Niente embeddings e niente chiamate in più. Fa scorrere una finestra sul testo e dà un punteggio a ogni posizione in base ai termini della domanda, con tre accorgimenti:

- **I termini rari pesano di più.** In un manuale di database la parola "database" compare ovunque e non aiuta a scegliere; "pgbouncer" compare in tre punti e decide lei. È lo stesso principio dell'IDF di BM25, applicato dentro un singolo file.
- **Uno stemming rozzo ma efficace**: i termini vengono tagliati a 6 lettere, così "ricetta" trova anche "ricette" e "configurazione" trova "configurare". Non serve classificare documenti, serve solo atterrare vicino alla risposta.
- **Un termine ripetuto 200 volte non vince da solo**: ogni termine conta al massimo 3 volte per finestra. Meglio una finestra che nomina tutte le parole della domanda una volta, che una che ripete la stessa all'infinito.

Se nessuna finestra contiene i termini della domanda, Loom torna al vecchio comportamento e manda l'inizio del file. Resta **una sola chiamata all'LLM**, non serve rifare la scansione e non c'è niente da configurare. Vale per la CLI, la GUI e il tool MCP `loom_ask`.

Unico avviso: sui file lunghi le risposte possono cambiare rispetto a prima. Di solito in meglio, visto che prima il modello rispondeva leggendo l'indice.

---

## `loom_grep`: quando ti serve la parola esatta

La ricerca di Loom (BM25 o ibrida) risponde a "quali file **parlano di** questo argomento?". Ma spesso la domanda è un'altra: "dove compare **esattamente** questa cosa?". Un numero di fattura, il nome di un cliente, una data, un codice di errore. La ricerca per rilevanza qui può sbagliare: il file che nomina la fattura una volta, in una tabella, non sarà mai primo in classifica.

Per questo c'è un nuovo tool MCP, `loom_grep`: un'espressione regolare sul contenuto estratto di tutti i file indicizzati (compreso il testo estratto dai PDF), oppure di un solo file.

```text
loom_grep(pattern="FT-2026-0[0-9]{3}", context=1)
```

Per ogni riga trovata restituisce il file, il **numero di riga**, l'**offset in byte** e, se li chiedi, qualche riga di contesto. Non chiama l'LLM. Per default ignora maiuscole e minuscole e si ferma a 50 risultati (al massimo 500), così un pattern come `e` non ti rovescia addosso l'intero archivio.

Usa il motore RE2 di Go, che gira in **tempo lineare**: un pattern scritto male da un agente non può bloccare il processo con il backtracking catastrofico che colpisce altri motori regex.

### Leggere solo il pezzo che serve

`loom_grep` ha senso insieme alla seconda novità: `loom_get_file` ora accetta `offset` e `length` e restituisce **un intervallo** del file invece di tutto il contenuto.

Messi insieme, permettono a Claude di esplorare una knowledge base come esplora un codebase:

1. `loom_grep` trova, per esempio, la riga che nomina un codice di errore all'offset 48.210 di un PDF da 300 KB;
2. `loom_get_file` legge solo i 4000 byte attorno a quell'offset;
3. Claude risponde senza aver caricato nel contesto gli altri 296 KB.

Chi usa Claude Code riconoscerà lo schema: è quello che fa con `grep` e la lettura di un file a righe. Ora funziona anche sui tuoi documenti.

---

## `excerpt_chars`: lo stesso taglio anche via HTTP

Chi usa [`loom-http`](/posts/loom-embeddings-ricerca-semantica-multilingua-http-2026/) come livello di retrieval per un'altra applicazione aveva lo stesso problema, spostato di un passo: `/search` restituisce il contenuto intero di ogni file, e l'applicazione doveva tagliarlo da sé. Di solito all'inizio.

Ora basta passare `excerpt_chars`:

```bash
curl -s localhost:8080/search \
  -d '{"query":"orario check-in","limit":3,"excerpt_chars":3000}'
```

I file lunghi tornano tagliati come fa `ask`, cioè inizio più passaggio pertinente, a circa 3000 byte, con un campo `content_len` che dice quanto era lungo il file intero. I file corti tornano interi. Se non passi il parametro, la risposta è identica a prima.

Ho sistemato anche un dettaglio: con budget piccoli l'inizio del file occupa al massimo un quarto dello spazio. Prima, con 1200 byte di budget, sarebbe stato tutto inizio e niente risposta.

---

## `.loomignore`: decidi tu cosa non indicizzare

Loom salta da sempre i file e le cartelle nascosti (`.git`, `.obsidian`...). Oltre a quello **non ignora niente di suo**, ed è voluto: cosa sia rumore (build, bozze vecchie, export duplicati, l'archivio del 2019) cambia da persona a persona.

Quindi le regole le scrivi tu, con la sintassi che conosci già da Git:

```gitignore
# ~/loom/.loomignore
# qualunque cartella "build", a qualsiasi profondità
build/
# solo la cartella archivio_v1 nella radice
/archivio_v1/
# tutti i file .bak
*.bak
bozze/**/*.old.md
```

Se preferisci non mettere file dentro una cartella (per esempio un vault di Obsidian), puoi usare la lista `ignore` in `~/.loom/config.toml`, che vale per tutte le cartelle indicizzate:

```toml
notes_dirs = ["/Users/tu/Documents/lavoro", "/Users/tu/Obsidian/vault"]
ignore     = ["build/", "*.bak"]
```

Qualche dettaglio pratico:

- le cartelle ignorate **non vengono nemmeno attraversate**, quindi la scansione è anche più veloce;
- i file già indicizzati che diventano ignorati vengono **tolti dall'indice** alla scansione successiva, come se li avessi cancellati. Togli la regola e alla scansione dopo tornano;
- un pattern non valido ferma la scansione con un errore che indica file e riga, **senza toccare l'indice**;
- la negazione (`!pattern`) non è supportata: è l'unico pezzo di `.gitignore` che manca;
- in modalità multi-corpus ogni corpus usa il suo `notes/.loomignore`.

---

## I bug che non vedevi

Due correzioni meritano una riga, perché erano del tipo peggiore: quelle silenziose.

**Salvare le impostazioni dalla GUI spegneva la ricerca ibrida.** Il codice che scriveva il `config.toml` emetteva sempre il blocco `[embeddings]` commentato. Bastava aprire le impostazioni della GUI e salvare, per qualunque motivo, perché il tuo `enabled = true` sparisse. Loom tornava al solo BM25 senza dire niente: nessun errore, solo risultati un po' peggiori sulle domande in un'altra lingua. Ora il blocco viene riscritto attivo con i tuoi valori. Della chiave API viene scritto solo il nome della variabile d'ambiente, mai la chiave.

**`/scan` via HTTP non diceva cosa era andato storto** (corretto nella 0.6.1). Restituiva solo i contatori: `{"failed": 1}` e arrangiati. Ora restituisce lo stesso elenco di errori della CLI, con file e motivo, e riporta anche i file **spostati** (stesso contenuto, percorso nuovo), che prima sembravano "non è successo niente". CLI, MCP e HTTP ora usano lo stesso codice per il resoconto, così non possono più divergere.

---

## Aggiornare

```bash
brew upgrade loom
```

Per Claude Desktop scarica il nuovo pacchetto `.mcpb` dalla [pagina delle release](https://github.com/Mauto82/loom/releases). Non serve rifare la scansione: gli estratti pertinenti, `loom_grep` e gli intervalli lavorano sull'indice che hai già. Il `.loomignore` entra in gioco alla prossima `loom scan`.

---

## Tirando le somme

Gli aggiornamenti di giugno erano funzioni nuove: embeddings, HTTP, multi-corpus. Questo è diverso. Sistema cose che c'erano già e non funzionavano come dovevano: domande a cui Loom avrebbe dovuto rispondere e non rispondeva.

La regola però resta quella di sempre: niente infrastruttura in più. L'estratto pertinente è un ciclo su una stringa, `loom_grep` è la libreria standard di Go, il `.loomignore` è un file di testo. Un binario, un file SQLite, il modello che usi già.

---

## Risorse

- **Repository Loom**: [github.com/Mauto82/loom](https://github.com/Mauto82/loom)
- **Changelog 0.7.0**: [CHANGELOG.md](https://github.com/Mauto82/loom/blob/main/CHANGELOG.md)
- **Homebrew tap**: [github.com/Mauto82/homebrew-loom](https://github.com/Mauto82/homebrew-loom)
- **Sintassi RE2**: [github.com/google/re2/wiki/Syntax](https://github.com/google/re2/wiki/Syntax)
- **I post precedenti su Loom**: [la presentazione](/posts/loom-memoria-locale-llm-senza-docker-rag-2026/) e [la ricerca semantica opzionale](/posts/loom-embeddings-ricerca-semantica-multilingua-http-2026/)

---

*P.S. Il bug della GUI è il mio preferito della categoria: nessun errore, nessun log, solo risposte un po' peggiori. È il tipo di bug per cui dai la colpa al modello di embedding per giorni, prima di pensare ad aprire un file di configurazione.*

