From 4158e2b6c3b701a97ab2a48378e29cb72de6865b Mon Sep 17 00:00:00 2001 From: max Date: Mon, 3 Aug 2026 18:49:56 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20landing=20README=20=E2=80=94=20banner?= =?UTF-8?q?=20hero,=20badges,=20synced=20RU/ES=20+=20new=20ZH=20translatio?= =?UTF-8?q?ns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The English README takes the landing structure drafted on codex/landing-readme (hero banner, badges, tool/capability tables, mermaid diagrams, collapsible details) and folds back the load-bearing content from main that the draft dropped: model licensing notes, the manual SessionStart hook, team-mode transports, metadocs mechanics, sidechain skipping, vector reuse. One hero image instead of logo + banner (the banner already carries both), stars badge dropped, og.webp recompressed from the 1.7 MB PNG to 106 KB at visually identical quality and committed under assets/ so the README does not depend on the site branch merging. All four language versions now carry identical content and a shared language switcher: English (reference), Русский, Español, and new 中文. Co-Authored-By: Claude Fable 5 --- README.es-ES.md | 628 +++++++++++++++++++++++++++++++-------- README.md | 737 +++++++++++++++++++++++++--------------------- README.zh-CN.md | 556 ++++++++++++++++++++++++++++++++++ assets/og.webp | Bin 0 -> 106300 bytes docs/README.ru.md | 684 ++++++++++++++++++++++++++++++++++++------ 5 files changed, 2053 insertions(+), 552 deletions(-) create mode 100644 README.zh-CN.md create mode 100644 assets/og.webp diff --git a/README.es-ES.md b/README.es-ES.md index 6da2d86..bc1db5d 100644 --- a/README.es-ES.md +++ b/README.es-ES.md @@ -1,92 +1,211 @@ +
+ + Session Recall — tus agentes de código olvidan; Session Recall, no. Una sola memoria para Claude Code, Codex y Cursor. + -# session-recall +
+
-*Traducción de la comunidad (gracias, @webbrain-one). Puede ir por detrás del -[README en inglés](README.md), que es la referencia; versión en ruso: -[docs/README.ru.md](docs/README.ru.md).* +Memoria semántica compartida para Claude Code, Codex y Cursor.
+Encuentra una decisión antigua por su significado. Abre la evidencia en bruto. Continúa el trabajo. -**Memoria compartida para Claude Code, Codex y Cursor.** Retoma el trabajo de hace un mes sin tener que reexplicarlo: Claude puede leer lo que Codex o Cursor resolvieron ayer, porque los tres alimentan un mismo índice. No es un archivo de resumen que alguien mantiene a mano: son los turnos reales, incluidas las llamadas a herramientas y el razonamiento, buscables por significado. +
+
-```console -$ session-recall index -indexed 2175 chunks from changed transcripts +[![Licencia MIT](https://img.shields.io/badge/license-MIT-b8f84f?style=flat-square&labelColor=11120f)](LICENSE) +[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-b8f84f?style=flat-square&labelColor=11120f)](pyproject.toml) +[![MCP nativo](https://img.shields.io/badge/MCP-native-b8f84f?style=flat-square&labelColor=11120f)](src/session_recall/server.py) +[![Tests](https://img.shields.io/github/actions/workflow/status/AbsoluteMode/session-recall/test.yml?branch=main&style=flat-square&label=tests&labelColor=11120f)](https://github.com/AbsoluteMode/session-recall/actions/workflows/test.yml) -your history: 1053 sessions spanning 168 days, 40,037 searchable fragments - Claude Code 372 · Codex 680 · Cursor 1 - busiest: sidekey, trend_detection, glitch -``` +
-Entonces, tu agente dejará de preguntarte qué estabas haciendo: +[English](README.md) · [Русский](docs/README.ru.md) · Español · [中文](README.zh-CN.md) -> **tú:** estábamos arreglando el conflicto del token de autenticación entre los dos servicios, ¿en qué quedamos? -> -> **agente:** *(recall_search → expand_around)* Ambos servicios compartían una cuenta OAuth, y el proveedor rota los tokens de refresco por cuenta, por lo que cada refresco invalidaba la copia del otro. Rechazaste el parche de directorio de credenciales compartidas por estar demasiado acoplado, y te decidiste por un servicio gestor propietario de la sesión. Las especificaciones nunca se escribieron: ese era el siguiente paso. +*La versión en inglés es la de referencia; las traducciones pueden quedar rezagadas.* -Cinco herramientas a través de MCP: +
-- `recall_search(query)` — encuentra una discusión pasada **por significado** (no por subcadena). Responde `{"anchors": [...], "degraded": null | str}`; `degraded` se establece cuando el proveedor de incrustaciones es inalcanzable y solo se ejecutó la coincidencia literal, para que el agente pueda indicarlo en lugar de confundir un fallo léxico con un historial vacío. -- `expand_around(session_id, uuid)` — un cursor en el turno raw (llamadas a herramientas, salidas, pensamiento). -- `step(session_id, uuid, direction)` — muévete a un turno adyacente (paso de cursor económico). -- `grep(pattern)` — exploración bajo demanda de subcadenas en **todos** los transcripciones indexadas, incluidos los turnos internos (salida de herramientas, pensamiento) que nunca se convirtieron en fragmentos de búsqueda. -- `recent_sessions()` — las sesiones pasadas más recientes primero (qué está activo, qué tan fresco es el índice). +--- -Bajo demanda (sin autoinyección proactiva en v1). Local y de código abierto. +Tus agentes de código recuerdan el chat actual. Tu trabajo vive repartido en meses de chats: +sesiones retomadas, suscripciones en paralelo, worktrees, agentes distintos. -`recall_search`, `grep` y `recent_sessions` también aceptan un opcional `scope_cwd`: pasa tu directorio de trabajo actual para limitar los resultados al repo actual (los worktrees colapsan a la raíz del repo); omítelo para recordatorios entre proyectos. Los resultados clasificados incluyen una marca de tiempo legible por humanos `when_human` junto con la época raw. Cada herramienta MCP acepta un `source` opcional (`claude`, `codex` o `cursor`); omítelo para usar el historial unificado. Los resultados incluyen la procedencia correspondiente. Las tres herramientas de descubrimiento también aceptan `on_date` para un solo día o `start_date` / `end_date` inclusivos (`YYYY-MM-DD`) más un `timezone` IANA opcional, para que un agente pueda restringir la recuperación a un día calendario local real en lugar de esperar que una fecha escrita en la consulta semántica afecte la clasificación. Si se omite `timezone`, Session Recall usa la zona horaria de la computadora que ejecuta el servidor MCP. +Session Recall convierte ese historial en un único índice local-first y lo sirve de vuelta a +través de cinco herramientas MCP bien acotadas. Una sesión recién abierta puede recuperar lo +que Codex resolvió ayer y lo que Claude Code rechazó hace tres meses — con enlaces a los +turnos reales, la salida de las herramientas y el razonamiento. No es un archivo de resumen +que alguien mantiene a mano: la conversación original sigue siendo la fuente de verdad. -**Estado:** v1, construido y validado con historial real. La clave del razonamiento de diseño está en [docs/decisions/](docs/decisions/). +> **tú:** estábamos arreglando el conflicto del token de autenticación entre los dos +> servicios — ¿en qué quedamos? +> +> **agente:** *(recall_search → expand_around)* Los dos servicios compartían una misma cuenta +> OAuth, y el proveedor rota los tokens de refresco por cuenta, así que cada refresco +> invalidaba la copia del otro. Rechazaste el parche del directorio de credenciales +> compartidas por demasiado acoplado, y optaste por un servicio keeper como dueño de la +> sesión. La especificación nunca llegó a escribirse — ese era el siguiente paso. + +## Qué obtienes + +| | Capacidad | Qué cambia | +|---|---|---| +| **Una sola memoria** | Claude Code, Codex y Cursor alimentan el mismo índice | Cambia de agente sin reiniciar la historia del proyecto | +| **Recuperación semántica** | Busca por significado, no solo por palabras exactas | Recupera decisiones que puedes describir pero no citar | +| **Navegación profunda** | Abre los turnos en bruto: llamadas a herramientas, salidas, razonamiento | Verifica la respuesta en lugar de fiarte de un resumen | +| **Degradación honesta** | Una caída de la parte semántica se comunica explícitamente | Un respaldo solo literal nunca se hace pasar por búsqueda semántica | +| **Local por defecto** | Incrustaciones ONNX incluidas y SQLite local | Empieza sin clave, sin servidor y sin cuenta | +| **Recall acotado** | Filtra por repo, origen o fechas del calendario local | Deja los proyectos ajenos fuera de la respuesta | +| **Respuestas de equipo** | Pregunta a la memoria local de un colega, con aprobación del dueño | Comparte contexto duramente ganado sin exponer sesiones en bruto | + +## Dónde compensa + +- **Arranque de sesión.** Una sesión nueva empieza ya en contexto — tanto si haces malabares + con varias suscripciones, como si saltas entre agentes o vuelves a una tarea que + «comentaste en algún momento». +- **Bugs y regresiones.** Antes de arreglar nada, el agente pregunta al historial: *¿se había + visto ya este bug? ¿cómo se arregló? ¿por qué creímos que estaba arreglado?* Una recaída + deja de parecer un bug nuevo — y la corrección pasa de ser un parche a ser una excavación + en el componente. +- **Procedimientos.** Explica un flujo de trabajo una sola vez — cómo leer una traza, cómo + desglosar el gasto de tokens por tarea — y cualquier sesión posterior lo repite sin que + haya que guiarla de nuevo. +- **Causa y efecto.** Di «cambiemos esta decisión» y el agente busca el momento en que se + tomó: *«elegimos X por compatibilidad con Y — antes de cambiar nada, asegúrate de que Y + sobrevive»*. + +## Cinco herramientas, un solo flujo de trabajo + +La interfaz se mantiene deliberadamente pequeña: + +| Herramienta MCP | Úsala cuando | +|---|---| +| `recall_search(query)` | Recuerdas la idea, no las palabras exactas | +| `expand_around(session_id, uuid)` | Encontraste un ancla y necesitas la evidencia que la rodea | +| `step(session_id, uuid, direction)` | Necesitas el turno en bruto adyacente sin otra búsqueda | +| `grep(pattern)` | Conoces un error, un símbolo, una ruta o un identificador exactos | +| `recent_sessions()` | Quieres el trabajo más fresco — y la frescura del índice | + +```mermaid +flowchart LR + Q["describe the old problem"] --> S["recall_search"] + S --> A["ranked anchor"] + A --> E["expand_around"] + E --> T["step next / prev"] + Q -. exact identifier .-> G["grep"] + R["what is current?"] --> RS["recent_sessions"] +``` -## Cómo funciona +Cada herramienta de descubrimiento acepta un `source` opcional (`claude` | `codex` | `cursor`), +un `scope_cwd` para acotar los resultados al repo actual (los worktrees colapsan a la raíz +del repo) y fechas del calendario local (`on_date`, o `start_date` / `end_date`, más un +`timezone` IANA). Las anclas clasificadas llevan su procedencia y una marca de tiempo +legible. `grep` escanea bajo demanda **todas** las transcripciones indexadas — incluidos los +turnos internos (salida de herramientas, razonamiento) que nunca se convirtieron en +fragmentos de búsqueda. Solo bajo demanda: sin inyección proactiva de contexto en cada prompt. -Las transcripciones de Claude Code, las sesiones de Codex desde `~/.codex/sessions` y `~/.codex/archived_sessions`, y las conversaciones de Cursor comparten el mismo índice. Cursor se lee desde su SQLite local y cada conversación se conserva como una instantánea JSONL normalizada dentro del directorio de datos de session-recall; por eso `expand_around`, `step` y `grep` siguen funcionando aunque Cursor esté cerrado o se desinstale. -Solo se incrusta la "superficie" de la conversación: los prompts del usuario y las respuestas de texto del asistente. -Las llamadas a herramientas, resultados, razonamiento y otros datos de traza no se incrustan, pero permanecen accesibles bajo demanda mediante `expand_around` (y `step`) o `grep`. Las transcripciones originales de Claude/Codex y las instantáneas raw normalizadas de Cursor permanecen locales; solo la superficie de conversación extraída se envía al proveedor de incrustaciones configurado. +
+Ver una llamada completa + +```json +{ + "query": "why did refresh tokens conflict?", + "scope_cwd": "/work/keeper", + "source": "codex", + "start_date": "2026-05-01", + "end_date": "2026-06-30", + "timezone": "Europe/Moscow" +} +``` -La ruta sin configuración usa un modelo ONNX local elegido por idioma → SQLite (`sqlite-vec` KNN + FTS5, clasificación bm25) → top-k. Con una clave de Voyage se usa la ruta alojada de mayor calidad: `voyage-4-large` (dim 1024) → SQLite → `rerank-2.5`. La indexación es incremental y reutiliza los vectores de fragmentos sin cambios. Cada archivo o sesión se indexa en su propia transacción; un fallo se registra y se reintenta sin destruir los datos buenos anteriores. +`recall_search` responde `{"anchors": [...], "degraded": null | "reason"}`. Cuando `degraded` +viene establecido, el proveedor de incrustaciones estaba inalcanzable y solo se ejecutó la +coincidencia literal — el agente puede decirlo en lugar de confundir un fallo léxico con un +historial vacío. -Las incrustaciones son intercambiables (el modelo local incluido es el predeterminado sin claves); el reranker es opcional, y el sistema se degrada elegantemente a KNN + FTS sin él. Se detecta el cambio de proveedor/modelo de incrustación y la búsqueda semántica se detiene hasta que todos los orígenes pertenezcan al mismo espacio vectorial. +
-## Instalación +## Puesta en marcha rápida -Dos componentes: un CLI de Python (que también incluye el servidor MCP) y un plugin que lo conecta a tu agente. Presupuesta unos dos minutos más la primera ejecución del índice. +Dos piezas: un CLI de Python (que también incluye el servidor MCP) y un plugin que lo conecta +a tu agente. Calcula unos dos minutos más la primera ejecución del índice. -### 1. CLI +### 1. Instala el CLI y construye el índice ```bash pipx install git+https://github.com/AbsoluteMode/session-recall -session-recall index # first run walks your whole history; later runs are incremental +session-recall setup # one question (interaction language), then the first index ``` -Eso es todo: sin una clave ni un servidor local, se descarga una vez el modelo ONNX incluido y luego se ejecuta en tu máquina. Consulta [Proveedores de incrustaciones](#proveedores-de-incrustaciones) para las demás opciones. Para usar las incrustaciones alojadas de Voyage, exporta una clave primero: +No hace falta clave: sin nada configurado, la indexación corre sobre un modelo de CPU +incluido, descargado una vez y elegido según tu idioma de interacción. La primera ejecución +recorre todo tu historial — minutos para meses de transcripciones, segundos a partir de +entonces. Instalaciones con script: `session-recall setup --lang en --yes`. -```bash -export VOYAGE_API_KEY=... # voyageai.com; put the line in your shell profile +```console +$ session-recall index +indexed 2175 chunks from changed transcripts + +your history: 1053 sessions spanning 168 days, 40,037 searchable fragments + Claude Code 372 · Codex 680 · Cursor 1 + busiest: sidekey, trend_detection, glitch ``` -`pipx` coloca `session-recall` y `session-recall-mcp` en `~/.local/bin`: exactamente donde los manifiestos del plugin los buscan. La primera indexación depende de cuánto historial tengas: minutos para meses de transcripciones, segundos después. +Las incrustaciones alojadas de Voyage clasifican notablemente mejor que el modelo incluido; +para usarlas, exporta `VOYAGE_API_KEY` antes de indexar — consulta +[Proveedores de incrustaciones](#proveedores-de-incrustaciones). -### 2. Plugin +### 2. Conecta tus agentes -**Claude Code** +`pipx` coloca `session-recall` y `session-recall-mcp` en `~/.local/bin` — exactamente donde +los manifiestos de los plugins los buscan. -``` +
+Claude Code + +```text /plugin marketplace add AbsoluteMode/session-recall /plugin install session-recall ``` -Luego inicia una nueva sesión: los servidores MCP y las habilidades se cargan al iniciar la sesión, no al instalar. +Después inicia una sesión nueva — los servidores MCP, las skills y el hook SessionStart se +cargan al arrancar la sesión, no al instalar. ¿Prefieres que el agente remate el trabajo? +Dile `set up session-recall` (o ejecuta `/session-recall:setup`): hace las preguntas de la +configuración inicial en el chat, ejecuta los comandos por sí mismo y termina con una +comprobación de salud y una búsqueda real sobre tu historial. + +
+ +
+Codex -**Codex** — el manifiesto `.codex-plugin/plugin.json` está listo para colocar en un repo local o en tu marketplace personal; consulta la [local plugin installation guide](https://learn.chatgpt.com/docs/build-plugins#install-a-local-plugin-manually). Codex también te pedirá revisar los hooks recién instalados una vez mediante `/hooks`. +El repositorio incluye un [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json) nativo — +listo para soltarlo en un repo local o en tu marketplace personal; consulta la +[guía de instalación local de plugins](https://learn.chatgpt.com/docs/build-plugins#install-a-local-plugin-manually). +Codex también te pide revisar una vez los hooks recién instalados mediante `/hooks`. -**Cursor** — el repositorio incluye un plugin nativo con MCP, skills, comandos, subagente de recall y un hook `sessionStart` en el formato de Cursor: +
+ +
+Cursor + +Requiere Cursor 2.5+ (ahí se introdujeron los plugins). Añade el repositorio como +marketplace: ```bash cursor-agent plugin marketplace add https://github.com/AbsoluteMode/session-recall.git ``` -Después ejecuta `/add-plugin session-recall` dentro de Cursor Agent. Para desarrollo local se puede iniciar con `cursor-agent --plugin-dir /ruta/absoluta/a/session-recall`. -Cursor muestra su aprobación habitual una sola vez para el servidor MCP stdio local; aprueba `session-recall` para iniciar las herramientas. +Después escribe `/add-plugin session-recall` en Cursor Agent y aprueba una sola vez el +servidor MCP stdio local, para que las herramientas puedan arrancar. Para desarrollar el +plugin, lanza `cursor-agent --plugin-dir /absolute/path/to/session-recall` en lugar de +instalar una copia cacheada. + +Cursor se detecta automáticamente en su ruta de datos habitual de macOS/Linux y no necesita +estar en ejecución. ¿Perfil portátil o personalizado? Apunta directamente a la base de datos +con `SESSION_RECALL_CURSOR_DB=/path/to/User/globalStorage/state.vscdb`. + +
### 3. Comprueba que funciona @@ -94,81 +213,109 @@ Cursor muestra su aprobación habitual una sola vez para el servidor MCP stdio l session-recall search "something you actually discussed last week" ``` -Los resultados con un `score` significan que la búsqueda semántica está activa. En el agente, `claude mcp list` debería mostrar `session-recall ✔ Connected`, y preguntarle sobre trabajo pasado debería activar `recall_search`. - -No hay nada más que configurar: cada plugin nativo incluye el formato de hook de su host y vuelve a indexar en segundo plano, manteniendo actualizados los tres historiales. - -Cursor se detecta automáticamente en su ruta de datos normal de macOS/Linux y no necesita estar abierto. Para un perfil portátil o personalizado, usa `SESSION_RECALL_CURSOR_DB=/ruta/a/User/globalStorage/state.vscdb`. Session Recall abre la base en modo de solo lectura y obtiene una copia coherente mediante la API de backup de SQLite. - -### Solución de problemas +Los resultados con `score` significan que la búsqueda semántica está viva. En el agente, +`claude mcp list` debería mostrar `session-recall ✔ Connected`, y preguntar por trabajo +pasado debería disparar `recall_search`. No hay nada más que configurar: cada plugin incluye +el hook de arranque de su host y reindexa en segundo plano, de modo que el índice compartido +se mantiene al día con los tres historiales por sí solo. -Empieza aquí: verifica toda la cadena y sale con código distinto de cero cuando algo está realmente roto, por lo que también funciona desde un temporizador: - -```console -$ session-recall health -[ok ] Freshness 2 minutes behind -[warn] Embedder responded in 5828 ms - → slow provider will make indexing crawl -[ok ] Vector space builtin/BAAI/bge-small-en-v1.5/384 -[ok ] Corpus 1054 sessions (claude 373, codex 680, cursor 1) -[ok ] Sources claude, codex, cursor present +## Cómo funciona -verdict: AMBER (voyage/voyage-4-large, index at ~/.local/share/session-recall/index.db) +```mermaid +flowchart TB + subgraph Sources["local history sources"] + CC["Claude Code JSONL"] + CX["Codex JSONL"] + CU["Cursor SQLite"] + end + + CC --> I["incremental indexer"] + CX --> I + CU -->|"consistent WAL snapshot"| I + I --> V["conversation surface → embeddings"] + I --> R["raw trace, kept local"] + V --> DB["SQLite · sqlite-vec KNN · FTS5"] + R --> DB + DB --> MCP["five on-demand MCP tools"] + MCP --> A["Claude Code · Codex · Cursor · any MCP client"] ``` -La frescura compara la transcripción más nueva en el disco con el turno más nuevo en el índice, por lo que un indexador que se ejecuta en cada sesión y falla cada vez sigue mostrando un retraso: que es exactamente el fallo que de otra forma sería invisible. +Solo se incrusta la «superficie» de la conversación — los prompts del usuario y las +respuestas de texto del asistente. Las llamadas a herramientas, los resultados, el +razonamiento y demás datos de traza nunca se envían a un proveedor de incrustaciones, pero +siguen accesibles bajo demanda mediante `expand_around`, `step` y `grep`. Las sidechains de +Claude y las sesiones de subagentes lanzados se omiten a propósito: son maquinaria interna, +no la conversación. -| Síntoma | Causa | -|---|---| -| `recall_search` responde con `degraded` establecido | El proveedor de incrustaciones es inalcanzable: solo se ejecutó la coincidencia literal de palabras. Los resultados siguen siendo reales, pero un fallo no prueba nada. | -| El indexador registra `HTTP code 403` con un cuerpo HTML | No es tu clave: un WAF está bloqueando tu IP (común en salidas de VPN y centros de datos). El mismo 403 aparece sin clave alguna. Redirige la salida a otro lado o cambia de proveedor. | -| `Missing dependencies for SOCKS support` | Se ha configurado un proxy SOCKS en el entorno pero `PySocks` no está instalado en ese venv. | -| `recent_sessions` muestra una marca de tiempo antigua | El indexador no ha tenido éxito recientemente. Ejecuta `session-recall index` manualmente y lee la salida. | +Cursor se lee desde su almacén SQLite con la API de backup en línea, de modo que una base de +datos WAL viva se captura de forma consistente sin bloquear el editor. Sus burbujas se +normalizan en instantáneas JSONL duraderas y direccionadas por contenido dentro del +directorio de datos — la navegación profunda sigue funcionando después de que Cursor se +cierre, se actualice o se desinstale. -### Referencia de CLI +La indexación es incremental y barata sobre transcripciones vivas: son de solo añadir +(append-only), así que los fragmentos sin cambios se emparejan por hash de contenido y sus +vectores se reutilizan — solo los turnos nuevos llegan al proveedor de incrustaciones. Mover +un rollout de Codex al archivo también reutiliza sus vectores. Cada archivo se indexa en su +propia transacción; un archivo que falla se registra y se reintenta en la siguiente +ejecución, sin abortar nunca el resto. -```bash -session-recall index --source claude|codex|cursor|all # defaults to all -session-recall search "query" --source codex -session-recall recent --date 2026-07-14 # this computer's timezone -session-recall search "deployment work" --start-date 2026-07-14 \ - --end-date 2026-07-14 --timezone Asia/Yekaterinburg -session-recall grep "exact" --limit 100 # raw scan, no API key needed -session-recall prune # drop rows for deleted transcripts -``` +## Referencia rápida del CLI -`search`, `recent`, `grep` y `prune` aceptan un opcional `--source claude|codex|cursor`; omítelo para buscar en el historial unificado. Los filtros de fecha son inclusivos y se puede omitir cualquiera de los límites; la zona horaria predeterminada es la de esta computadora y acepta cualquier nombre IANA. `grep` se limita a 100 coincidencias por defecto. +```bash +# Refresh every history, or one source +session-recall index +session-recall index --source cursor -Para desarrollo, un virtualenv dentro del árbol también funciona: +# Semantic search — unified by default, scopable to a repo +session-recall search "why did we choose the keeper service?" +session-recall search "deployment work" --source codex --scope /work/keeper -```bash -python -m venv .venv && .venv/bin/pip install -e . -.venv/bin/session-recall index -``` +# Local calendar dates, any IANA timezone (defaults to this computer's) +session-recall recent --date 2026-07-14 +session-recall search "deployment work" \ + --start-date 2026-07-14 --end-date 2026-07-16 \ + --timezone Asia/Yekaterinburg -Para registrar el servidor MCP manualmente en lugar de usar el plugin: +# Exact raw scan — no embedding call, caps at 100 matches by default +session-recall grep "invalid_grant" --limit 100 -```bash -claude mcp add session-recall --scope user -- /absolute/path/.venv/bin/session-recall-mcp +# Housekeeping +session-recall prune # drop rows for transcripts deleted from disk +session-recall health # the whole chain, verdict GREEN/AMBER/RED ``` +`search`, `recent`, `grep` y `prune` aceptan `--source claude|codex|cursor`; omítelo para el +historial unificado. Los filtros de fecha son inclusivos y puede omitirse cualquiera de los +dos límites. + ## Proveedores de incrustaciones -Nada está atado a un solo proveedor. `SESSION_RECALL_EMBED=` establece punto de conexión, modelo, dimensión y reranker juntos, porque esos cuatro no son elecciones independientes: +Nada está atado a un solo proveedor. `SESSION_RECALL_EMBED=` establece punto de +conexión, modelo, dimensión y reranker a la vez, porque esas cuatro no son elecciones +independientes: -| preset | runs | model | dim | reranker | -|---|---|---|---|---| -| `voyage` | hosted, needs a key | `voyage-4-large` | 1024 | `rerank-2.5` | -| `ollama` | **local, free** | `nomic-embed-text` | 768 | — | -| `lmstudio` | **local, free** | `nomic-embed-text-v1.5` | 768 | — | -| `openai` | hosted, needs a key | `text-embedding-3-large` | 1024 | — | +| Preset | Ejecución | Modelo | Dim | Reranker | +|---|---|---|---:|---| | `builtin-en` | **incluido, gratis** | `bge-small-en-v1.5` | 384 | — | | `builtin-zh` | **incluido, gratis** | `bge-small-zh-v1.5` | 512 | — | | `builtin-multi` | **incluido, gratis** | `paraphrase-multilingual-MiniLM-L12-v2` | 384 | — | - -Sin ningún preset configurado, session-recall elige Voyage cuando `VOYAGE_API_KEY` está presente, luego busca un servidor local que ya esté escuchando y, si no encuentra ninguno, usa el modelo ONNX incluido. Con una clave configurada, no se ejecuta la búsqueda local. - -**Gratuito y local, de principio a fin:** +| `ollama` | **local, gratis** | `nomic-embed-text` | 768 | — | +| `lmstudio` | **local, gratis** | `nomic-embed-text-v1.5` | 768 | — | +| `voyage` | alojado, requiere clave | `voyage-4-large` | 1024 | `rerank-2.5` | +| `openai` | alojado, requiere clave | `text-embedding-3-large` | 1024 | — | + +Sin ningún preset configurado, Session Recall elige Voyage cuando `VOYAGE_API_KEY` está +presente, después sondea si ya hay un servidor local escuchando y, si no, ejecuta el modelo +ONNX incluido — lo de serie siempre funciona. La variante incluida sigue el idioma de +interacción que elegiste en la configuración inicial (`SESSION_RECALL_LANG=en|zh|…`: un +pequeño especialista en inglés o en chino, multilingüe en el resto de casos). El primer uso +descarga el modelo una vez al directorio de datos (70–240 MB); desde entonces, inferencia en +CPU. La clasificación es notablemente más gruesa que la de Voyage alojado — un punto de +partida, no el techo. Los presets locales no incluyen reranker, así que la clasificación es +solo KNN + FTS. + +**Gratis y local, de principio a fin:** ```bash ollama pull nomic-embed-text @@ -176,7 +323,9 @@ export SESSION_RECALL_EMBED=ollama session-recall index ``` -**Tu propio punto de conexión** — cualquier servidor que hable `/v1/embeddings` (llama.cpp, vLLM, una puerta de enlace corporativa). Las variables individuales siempre prevalecen sobre el preset, así que mezcla libremente: +**Tu propio punto de conexión** — cualquier servidor que hable `/v1/embeddings` (llama.cpp, +vLLM, una puerta de enlace corporativa). Las variables individuales siempre ganan al preset, +así que mezcla con libertad: ```bash export SESSION_RECALL_EMBED_PROVIDER=openai-compatible @@ -185,18 +334,36 @@ export SESSION_RECALL_EMBED_MODEL=your-model export SESSION_RECALL_EMBED_DIM=1024 ``` -Dos cosas que vale la pena saber antes de cambiar: +**Un embedder distinto necesita su propio índice.** Las tablas vectoriales son de ancho +fijo, así que cambiar el modelo o la dimensión implica reconstruir: borra +`~/.local/share/session-recall/index.db` y vuelve a ejecutar `index`. Session Recall toma la +huella del espacio de incrustaciones de cada archivo indexado y se niega a mezclar espacios — +la búsqueda semántica se apaga con un mensaje explícito en lugar de devolver clasificaciones +engañosas. + +
+Notas sobre licencias de modelos -- **Un embedder diferente necesita su propio índice.** Las tablas vectoriales tienen ancho fijo, por lo que cambiar el modelo o la dimensión significa reconstruir: elimina `~/.local/share/session-recall/index.db` y vuelve a ejecutar `index`. Intentar reutilizar el antiguo ahora falla con un mensaje que dice exactamente eso, en lugar de parecer un embedder muerto. -- **Los presets locales no incluyen reranker**, por lo que la clasificación es solo KNN + FTS: suficiente, pero notablemente más gruesa que la ruta alojada. +`nomic-embed-text` es el predeterminado local porque es Apache-2.0 y se instala con un solo +comando. Existen modelos pequeños más fuertes — `jina-embeddings-v5-text-nano` puntúa +muchísimo mejor para su tamaño — pero son **CC BY-NC**, algo que cualquiera que indexe +historial de trabajo estaría violando sin que nadie se lo dijera. Si tu uso es genuinamente +no comercial, apunta las variables de arriba a uno de ellos. Si trabajas en más idiomas que +el inglés, `qwen3-embedding:0.6b` (Apache-2.0) maneja el historial multilingüe bastante +mejor que `nomic`. -Sobre la elección del modelo: `nomic-embed-text` es el predeterminado porque es Apache-2.0 e instala en un solo comando. Existen modelos pequeños más potentes: `jina-embeddings-v5-text-nano` puntúa mucho más alto para su tamaño, pero son **CC BY-NC**, lo que cualquiera que indexe historial de trabajo violaría sin que nunca se lo digan. Si tu uso es genuinamente no comercial, apunta las variables anteriores a uno de ellos. Si trabajas en más idiomas que el inglés, `qwen3-embedding:0.6b` (Apache-2.0) maneja el historial multilingüe mucho mejor que `nomic`. +
## Mantener el índice actualizado -Si instalaste el plugin, esto ya está manejado: pasa a la siguiente sección. El hook `SessionStart` incluido ejecuta `session-recall index` en segundo plano, y el `--source all` predeterminado actualiza Claude, Codex y Cursor. La indexación es incremental, por lo que mantenerse al día es económico. +Si instalaste un plugin, esto ya está resuelto: el hook `SessionStart` incluido ejecuta +`session-recall index` en segundo plano en cada arranque de sesión, y la indexación +incremental lo mantiene barato. -Solo si registraste el servidor MCP manualmente, añade el hook tú mismo en `~/.claude/settings.json`: +
+¿Registraste el servidor MCP a mano? Añade el hook tú mismo + +En `~/.claude/settings.json`: ```json "hooks": { @@ -209,32 +376,239 @@ Solo si registraste el servidor MCP manualmente, añade el hook tú mismo en `~/ } ``` -La guardia `pgrep` previene ejecuciones superpuestas; `(\` … \`& )\` se desacopla para que el inicio de sesión no espere. Mantén el hook a nivel de host síncrono: el shell ya envía el indexador a segundo plano, y Codex ignora la extensión `async` de Claude. Un temporizador `launchd`/cron es otra opción. (Local en una máquina es suficiente; un índice del lado del servidor solo tiene sentido en varias máquinas: a costa de la privacidad y la red.) +La guarda `pgrep` evita ejecuciones solapadas; `( … & )` se desacopla para que el arranque de +sesión no espere. Mantén síncrono el hook a nivel de host — el shell ya manda el indexador a +segundo plano, y Codex ignora la extensión `async` de Claude. Un temporizador `launchd`/cron +también sirve. + +
-## meta docs — la memoria del proyecto, escrita +## Modo equipo — pregunta al historial de un colega -El recordatorio raw responde a "qué se dijo". meta docs responde a las preguntas que los agentes hacen realmente a mitad de tarea: *¿se arregló este error antes? ¿cómo realizo esta acción? ¿por qué se decidió de esta manera?* Un trabajo diario destila el diálogo de cada sesión — solo mensajes del usuario y respuestas finales, nunca el ruido de las herramientas — en documentos vivos dentro de un repositorio git de tu elección: +El mismo recall, entre máquinas: emparéjate una vez con un colega y tu agente podrá +preguntarle al suyo por su trabajo pasado. -- `/bugs.md` — errores que se arreglaron realmente: cómo se reconoció, diagnosticó, arregló y demostró que estaba arreglado cada uno; -- `/actions.md` — procedimientos, paso a paso, escritos para que un agente consultado de nuevo pueda seguir la entrada solo; -- `/decisions.md` — decisiones controvertidas: qué se decidió, por qué así, qué se rechazó; -- `USER.md` — un mapa de dónde vive tu información y *cómo encontrarla* (comandos de búsqueda, ubicaciones de almacenamiento — nunca los valores almacenados en sí). +> **tú → el agente de un colega:** cuando te topaste con el problema del arranque en local +> de X — ¿cómo lo resolviste? +> +> **su agente** *(después de que el colega apruebe la respuesta)*: fija la configuración en +> …, luego …, y el problema no vuelve a aparecer. + +Lo que antes era un hilo de Slack y una explicación recordada a medias se convierte en una +pregunta y una respuesta fundamentada. Nunca ves el historial en bruto del colega — solo la +respuesta que aprobó. + +Aquí la privacidad es mecánica, no política: + +- las preguntas y las respuestas viajan como **sobres cifrados de extremo a extremo**; el + relay almacena blobs ciegos que no puede leer; +- las respuestas las construye un **worker aislado de solo lectura**, acotado a los proyectos + concedidos explícitamente a ese contacto (`share allow`); +- cada respuesta candidata pasa por un **escáner de secretos** y después por la **aprobación + explícita del dueño** (bot de Telegram, o `share approve` en local) antes de salir de la + máquina; +- un contacto puede pausarse en cualquier momento (`share pause`) y un peer puede revocarse + (`share revoke`). + +Buscar en el índice de un peer no exige configurar incrustaciones de tu lado: la consulta +viaja como texto, y el worker del dueño la incrusta con su propio proveedor contra su propio +índice. + +
+Elige un transporte (una vez, antes del emparejamiento) + +Una instalación recién hecha **no tiene transporte** y nunca habla con un servidor que no +hayas elegido. El relay es ciego — todo lo que transporta se sella y se firma en los +clientes — así que cuál usar es coordinación entre peers, no una cuestión de confianza. + +**Carpeta compartida — cero infraestructura.** Dos cuentas en una misma máquina, o cualquier +carpeta que ambos peers sincronicen (Syncthing, Dropbox, un montaje NFS): + +```bash +export SESSION_RECALL_SHARE_TRANSPORT_DIR=~/Sync/sr-share # both peers, same folder +``` + +**Tu relay en la LAN.** Una máquina lo ejecuta y todos apuntan a ella. Los sobres van +cifrados de extremo a extremo en cualquier caso, pero esto es HTTP plano — resérvalo para +una red en la que confíes: + +```bash +session-recall share relay --port 8787 --host 0.0.0.0 # on the relay machine +export SESSION_RECALL_RELAY_URL=http://192.168.1.20:8787 # on every peer +``` + +**Tu relay en internet.** El relay se liga a localhost a propósito y espera un terminador +TLS delante (Caddy es la opción de dos líneas): ```bash -session-recall metadocs init ~/meta-docs # every git project; or --projects name… -session-recall metadocs run # one pass now -session-recall metadocs enable # daily launchd job (default 21:00) +session-recall share relay --port 8787 # binds 127.0.0.1 +``` + +```text +relay.example.com { + reverse_proxy 127.0.0.1:8787 +} +``` + +Después, en cada peer: `export SESSION_RECALL_RELAY_URL=https://relay.example.com`. El relay +solo almacena blobs sellados, y cada buzón se vacía al recogerlo. +`SESSION_RECALL_RELAY_URL=none` mantiene una instalación en silencio de red a propósito. Pon +el `export` en tu perfil de shell para que los agentes y los temporizadores también lo vean. + +
+ +
+Emparejar y preguntar + +El emparejamiento es una ceremonia de una sola vez con una breve verificación SAS; después, +preguntar es un solo comando: + +```bash +session-recall share init # once per device, both sides +session-recall share invite # you: prints a one-time code +session-recall share join # colleague: accepts it +session-recall share complete # you: finish the handshake +session-recall share trust # both: confirm the SAS matched, name the peer +session-recall share allow +session-recall share notify # owner side: worker + approval loop + +session-recall share ask "how did you fix the local X launch?" +session-recall share fetch # collect the answers +``` + +
+ +## Meta docs — la memoria del proyecto, puesta por escrito + +El recall en bruto responde a *qué se dijo*. Meta docs responde a lo que los agentes +preguntan de verdad en mitad de una tarea: *¿este bug ya se arregló antes? ¿cómo realizo +esta acción? ¿por qué se decidió así?* Un trabajo diario entrega el diálogo de cada sesión — +mensajes del usuario y respuestas finales, nunca el ruido de las herramientas — a un agente +destilador que mantiene entradas en Markdown dentro de un repositorio Git que tú eliges: + +- `/bugs/` — bugs que de verdad se arreglaron: cómo se reconoció, diagnosticó, + arregló y demostró arreglado cada uno; +- `/actions/` — procedimientos, paso a paso, escritos para que un agente al que se + le pregunte de nuevo pueda seguir la entrada por sí solo; +- `/decisions/` — decisiones disputadas: qué se decidió, por qué así, qué se + rechazó; +- `USER/` — un mapa global de dónde vive tu información y *cómo encontrarla* (comandos de + consulta y ubicaciones de almacenamiento — nunca los valores almacenados en sí). + +```bash +session-recall metadocs init ~/meta-docs --from-today # memory starts now +session-recall metadocs run # one pass now +session-recall metadocs enable # daily job: launchd (macOS) / systemd user timer (Linux) session-recall metadocs status +session-recall metadocs index-history --days 30 # opt-in: distill the past, once +``` + +Todo el mundo del destilador son cuatro verbos MCP — `search / create / edit / delete` — y +las reglas que soportan la carga son mecánica del servidor, no peticiones en el prompt: +`create` se rechaza hasta que el agente haya hecho `search` (la deduplicación es +obligatoria), las entradas se escanean en busca de secretos antes de que un solo byte llegue +al disco, y `delete` exige un motivo. Las ejecuciones son incrementales y cada proyecto con +cambios recibe su propio commit local — revisar es un diff, deshacer es un revert, y +compartir la memoria con un equipo es simplemente subir el repo a algún lugar privado. Nada +se sube a menos que actives `--push`; el motor y el modelo salen solo de la configuración +(`init --engine claude-cli|codex --model …`) — nada se elige en silencio. + +## La privacidad es una invariante estricta + +Este es un repositorio público. **En él solo entra código.** Los datos de ejecución viven +bajo `~/.local/share/session-recall/`, fuera del árbol del repo — físicamente no pueden +acabar en un commit. + +| Se queda en tu máquina | Sale solo cuando tú lo decides | +|---|---| +| Las transcripciones originales de Claude Code y Codex | El texto de superficie de la conversación → el embedder alojado que configures | +| El almacén SQLite de Cursor y sus instantáneas normalizadas | Una respuesta del modo equipo aprobada explícitamente | +| Llamadas a herramientas, salidas, razonamiento — toda la traza en bruto | Nada, en la ruta de incrustación incluida/local | +| El índice SQLite y los vectores almacenados | | + +- Las claves de API son solo variables de entorno; `.gitignore` bloquea `.env`. +- Las pruebas usan fixtures sintéticos, nunca un trozo real de una sesión. +- El proveedor incluido mantiene toda la ruta de indexación en el dispositivo. Si eliges un + proveedor alojado, elige uno al que confiarías el texto de superficie de tus + transcripciones. + +## Solución de problemas + +Empieza aquí — comprueba toda la cadena y sale con código distinto de cero cuando algo está +roto de verdad, así que también funciona desde un temporizador: + +```console +$ session-recall health +[ok ] Freshness 2 minutes behind +[warn] Embedder responded in 5828 ms + → slow provider will make indexing crawl +[ok ] Vector space builtin/BAAI/bge-small-en-v1.5/384 +[ok ] Corpus 1054 sessions (claude 373, codex 680, cursor 1) +[ok ] Sources claude, codex, cursor present + +verdict: AMBER (voyage/voyage-4-large, index at ~/.local/share/session-recall/index.db) ``` -Las ejecuciones son incrementales (marcas de agua por sesión), el destilador es una llamada `claude -p` enjaulada sin todas las herramientas, cada documento se escanea en busca de secretos antes de escribirse (un documento marcado se bloquea, no se enmascara), y cada ejecución termina en un commit git — la revisión es un diff, deshacer es un revert, y compartir la memoria con un equipo es simplemente enviar el repo a algún lugar privado. Los commits permanecen locales a menos que optes por `--push`. +La frescura compara la transcripción más nueva en disco con el turno más nuevo del índice, +así que un indexador que se ejecuta en cada sesión y falla todas las veces sigue apareciendo +retrasado — exactamente el fallo que de otro modo resulta invisible. + +| Síntoma | Causa / siguiente paso | +|---|---| +| `recall_search` responde con `degraded` establecido | El proveedor de incrustaciones está inalcanzable — solo se ejecutó la coincidencia literal. Los resultados son reales, pero un fallo no demuestra nada. | +| `degraded` dice "embedder changed" | El índice se construyó en otro espacio de incrustaciones. Ejecuta `session-recall index` para reincrustar; hasta entonces la clasificación semántica queda apagada, a propósito. | +| El indexador registra `HTTP code 403` con un cuerpo HTML | No es tu clave: un WAF está bloqueando tu IP (habitual en salidas de VPN y de centros de datos). El mismo 403 aparece incluso sin clave. Enruta la salida por otro lado o cambia de proveedor. | +| `Missing dependencies for SOCKS support` | Hay un proxy SOCKS configurado en el entorno, pero `PySocks` no está instalado en ese venv. | +| `recent_sessions` muestra una marca de tiempo antigua | El indexador no ha terminado bien últimamente. Ejecuta `session-recall index` a mano y lee la salida. | +| Cursor vive en un perfil personalizado | Define `SESSION_RECALL_CURSOR_DB=/path/to/User/globalStorage/state.vscdb`. | -## Privacidad — invariante estricta +## Desarrollo -Este es un repositorio público. **Solo entra código en él.** +```bash +git clone https://github.com/AbsoluteMode/session-recall.git +cd session-recall +python -m venv .venv +.venv/bin/pip install -e ".[dev]" +.venv/bin/pytest -q +``` + +Para registrar el servidor MCP a mano en lugar de usar el plugin: + +```bash +claude mcp add session-recall --scope user -- /absolute/path/.venv/bin/session-recall-mcp +``` -- Datos, índices, transcripciones raw, incrustaciones → `~/.local/share/session-recall/`, **fuera del árbol del repo**. Físicamente no pueden ser commitados. -- Claves API → solo en el entorno (`VOYAGE_API_KEY`); `.gitignore` bloquea `.env`. -- Pruebas → solo fixtures sintéticos, nunca una porción real de una sesión. -- Las transcripciones de Claude Code, las transcripciones activas y archivadas de Codex y el SQLite de Cursor se leen localmente. Las instantáneas normalizadas de Cursor permanecen en el directorio de datos. Solo el texto superficial de usuario/asistente se incrusta; las herramientas y el razonamiento quedan fuera de las incrustaciones. -- Los textos de los fragmentos se envían al proveedor configurado. Sin claves, el proveedor incluido permanece completamente local; si eliges Voyage u otro endpoint alojado, usa uno en el que confíes. +La justificación de ingeniería y las invariantes viven en [`docs/decisions/`](docs/decisions/). +Empieza por: + +- [Índice unificado de Claude Code + Codex](docs/decisions/2026-07-10-unified-claude-codex-index.md) +- [Cursor como origen en bruto duradero](docs/decisions/2026-08-03-cursor-durable-raw-source.md) +- [Recall acotado por proyecto](docs/decisions/2026-06-26-recall-project-scope.md) +- [Puerta de seguridad de la compartición P2P](docs/decisions/2026-07-30-p2p-sharing-v1-security-gate.md) +- [Meta docs, memoria viva del proyecto](docs/decisions/2026-07-31-metadocs-living-project-memory.md) + +## Hoja de ruta + +- **Índice alojado/de equipo** — un índice compartido para todo el equipo en lugar de copias + por máquina. La pregunta abierta honesta: quien busca debe incrustar la consulta, así que + un espacio vectorial compartido implica una ruta de incrustación compartida. +- **Omitir la aprobación por contacto** — saltarse la aprobación por respuesta con los peers + en los que confías plenamente; hoy cada respuesta se aprueba explícitamente. +- **Más historiales** — transcripciones de otros agentes más allá de Claude Code, Codex y + Cursor. + +## Contribuir + +Los issues, las mejoras de documentación, los adaptadores de hosts y las traducciones son +bienvenidos. Mantén los fixtures sintéticos y nunca incluyas en un commit transcripciones +reales, índices, incrustaciones ni credenciales. + +
+
+ Deja de reconstruir el contexto. Continúalo. +
+
+ Empieza ahora +  ·  + Licencia MIT +
diff --git a/README.md b/README.md index 0be748b..dbf65aa 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,39 @@ -# session-recall +
-*Русская версия: [docs/README.ru.md](docs/README.ru.md) · Español: -[README.es-ES.md](README.es-ES.md) (community translation)* + + Session Recall — your coding agents forget, Session Recall doesn't. One memory for Claude Code, Codex, and Cursor. + -**Shared memory for Claude Code, Codex, and Cursor.** Pick up work from a month ago without -re-explaining it — and Claude can read what Codex or Cursor worked out yesterday, because all -three feed one index. Not a summary file someone maintains by hand: the actual turns, including tool -calls and reasoning, searchable by meaning. +
+
-```console -$ session-recall index -indexed 2175 chunks from changed transcripts +Shared semantic memory for Claude Code, Codex, and Cursor.
+Find an old decision by meaning. Open the raw evidence. Continue the work. -your history: 1053 sessions spanning 168 days, 40,037 searchable fragments - Claude Code 372 · Codex 680 · Cursor 1 - busiest: sidekey, trend_detection, glitch -``` +
+
+ +[![MIT License](https://img.shields.io/badge/license-MIT-b8f84f?style=flat-square&labelColor=11120f)](LICENSE) +[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-b8f84f?style=flat-square&labelColor=11120f)](pyproject.toml) +[![MCP native](https://img.shields.io/badge/MCP-native-b8f84f?style=flat-square&labelColor=11120f)](src/session_recall/server.py) +[![Tests](https://img.shields.io/github/actions/workflow/status/AbsoluteMode/session-recall/test.yml?branch=main&style=flat-square&label=tests&labelColor=11120f)](https://github.com/AbsoluteMode/session-recall/actions/workflows/test.yml) + +
-Then your agent stops asking you what you were doing: +English · [Русский](docs/README.ru.md) · [Español](README.es-ES.md) · [中文](README.zh-CN.md) + +
+ +--- + +Your coding agents remember the current chat. Your work lives across months of chats — +resumed sessions, parallel subscriptions, worktrees, different agents. + +Session Recall turns that history into one local-first index and serves it back through +five focused MCP tools. A fresh session can recover what Codex worked out yesterday and +what Claude Code rejected three months ago — with links back to the actual turns, tool +output, and reasoning. Not a summary file someone maintains by hand: the original +conversation stays the source of truth. > **you:** we were fixing the auth token conflict between the two services — where did we land? > @@ -26,151 +42,158 @@ Then your agent stops asking you what you were doing: > rejected the shared-credentials-directory patch as too coupled, and settled on a keeper service > owning the session. The spec was never written — that was the next step. -## Where it pays off +## What you get -Two months of daily use; the cases that stuck: +| | Capability | What it changes | +|---|---|---| +| **One memory** | Claude Code, Codex, and Cursor feed the same index | Switch agents without resetting the project story | +| **Semantic retrieval** | Search by meaning, not only exact words | Recover decisions you can describe but cannot quote | +| **Deep navigation** | Open raw turns: tool calls, outputs, reasoning | Verify the answer instead of trusting a summary | +| **Honest degradation** | A semantic outage is reported explicitly | A literal-only fallback never poses as semantic search | +| **Local by default** | Bundled ONNX embeddings and local SQLite | Start without a key, a server, or an account | +| **Scoped recall** | Filter by repo, source, or local calendar dates | Keep unrelated projects out of the answer | +| **Team answers** | Ask a colleague's local memory, owner-approved | Share hard-won context without exposing raw sessions | + +## Where it pays off - **Session onboarding.** A fresh session starts already in context — whether you juggle - several subscriptions, hop between coding agents (Claude reads what Codex worked out - yesterday), brainstorm the same problem across parallel sessions, or return to a task - you "discussed at some point". -- **Bugs and regressions.** Before fixing anything, the agent asks the history: *was this - bug seen before? how was it fixed? why did we believe it was fixed?* A recurrence you - yourself forgot stops looking like a fresh bug — and the fix turns from a patch into a - dig into the component. -- **Actions.** Explain a procedure once — how to read a trace, fill your work - spreadsheets, break down token spend per task per model — and any later session - replays it without being walked through again. -- **Cause and effect.** Say "let's change this decision", and the agent looks up the - moment it was made: *"we picked X for compatibility with Y — before changing anything, - make sure Y survives."* - -Five tools over MCP: - -- `recall_search(query)` — find a past discussion **by meaning** (not substring). Answers - `{"anchors": [...], "degraded": null | str}`; `degraded` is set when the embedding provider was - unreachable and only literal matching ran, so the agent can say so instead of mistaking a - lexical miss for an empty history. -- `expand_around(session_id, uuid)` — a cursor into the raw turn (tool calls, outputs, thinking). -- `step(session_id, uuid, direction)` — move to an adjacent turn (cheap cursor step). -- `grep(pattern)` — on-demand substring scan over **all** indexed transcripts, including - under-the-hood turns (tool output, thinking) that never became search chunks. -- `recent_sessions()` — the freshest past sessions first (what's current, how fresh the index is). - -On-demand (no proactive auto-injection in v1). Local, open source. The tools are plain -MCP, so any MCP-capable agent can search the same history; the histories being -indexed today come from Claude Code, Codex, and Cursor. - -`recall_search`, `grep` and `recent_sessions` also take an optional `scope_cwd` — pass your -current working directory to scope results to the current repo (worktrees collapse to the repo -root); omit it for cross-project recall. Ranked hits carry a human-readable `when_human` -timestamp alongside the raw epoch. Every MCP tool accepts an optional `source` (`claude`, -`codex`, or `cursor`); omit it to use the unified history. Results include provenance such as -`source=claude`, `source=codex`, or `source=cursor`. The three discovery tools also accept `on_date` for one day or inclusive -`start_date` / `end_date` (`YYYY-MM-DD`) plus an optional IANA `timezone`, so an agent can -constrain retrieval to an actual local calendar day instead of hoping a date written into the -semantic query affects ranking. If `timezone` is omitted, Session Recall uses the timezone of -the computer running the MCP server. - -**Status:** v1, built and validated on real history. Key design rationale lives in -[docs/decisions/](docs/decisions/). + several subscriptions, hop between agents, or return to a task you "discussed at some point". +- **Bugs and regressions.** Before fixing anything, the agent asks the history: *was this bug + seen before? how was it fixed? why did we believe it was fixed?* A recurrence stops looking + like a fresh bug — and the fix turns from a patch into a dig into the component. +- **Procedures.** Explain a workflow once — how to read a trace, how to break down token + spend per task — and any later session replays it without being walked through again. +- **Cause and effect.** Say "let's change this decision", and the agent looks up the moment it + was made: *"we picked X for compatibility with Y — before changing anything, make sure Y + survives."* -## How it works +## Five tools, one workflow -Claude Code transcripts, Codex sessions from `~/.codex/sessions` plus -`~/.codex/archived_sessions`, and Cursor sessions (read from its SQLite store, -`User/globalStorage/state.vscdb`; subagent sessions skipped, workspaces mapped -to projects) share the same index. Cursor bubbles are normalized into durable, -content-addressed JSONL snapshots under the session-recall data directory, so -raw navigation keeps working after Cursor closes, upgrades, or is uninstalled. -Only the conversation "surface" is embedded — user prompts and assistant text replies. -Tool calls, results, reasoning, and other trace data are not embedded but stay reachable on -demand via `expand_around` (and `step`) or `grep`. Original Claude/Codex transcripts and -Cursor's normalized raw snapshots remain local; only the extracted conversation surface is -sent to the configured embedding provider. - -The zero-config path is a language-aware bundled ONNX model → SQLite -(`sqlite-vec` KNN + FTS5, bm25-ranked) → top-k. With a Voyage key the higher-quality -hosted path is `voyage-4-large` (dim 1024) → SQLite → `rerank-2.5`. Indexing is -incremental (by file metadata, including Codex inode+size) and cheap on live transcripts: they are append-only, so -unchanged chunks are matched by content hash and their vectors reused — only new turns -hit the embedding API. Moving a Codex rollout into the archive also reuses its existing -vectors. Each file indexes in its own transaction; a failing file is -logged and retried on the next run, never aborting the rest. Claude sidechains -(`/subagents/`) and Codex spawned-subagent sessions are intentionally skipped — -they are under-the-hood tooling, not the primary user/agent conversation. - -Embeddings are pluggable (the bundled local model is the no-key default); the reranker is optional, and the -system degrades gracefully to KNN + FTS without one. Switching the embedding -provider/model is detected (an embed fingerprint is part of each file's index -signature) and triggers a clean re-embed instead of silently mixing vector spaces. - -## Install - -Two pieces: a Python CLI (which also ships the MCP server) and a plugin that wires it into your -agent. Budget about two minutes plus the first index run. - -### 1. CLI +The interface stays deliberately small: + +| MCP tool | Use it when | +|---|---| +| `recall_search(query)` | You remember the idea, not the wording | +| `expand_around(session_id, uuid)` | You found an anchor and need the surrounding evidence | +| `step(session_id, uuid, direction)` | You need the adjacent raw turn without another search | +| `grep(pattern)` | You know an exact error, symbol, path, or identifier | +| `recent_sessions()` | You want the freshest work — and the index freshness | + +```mermaid +flowchart LR + Q["describe the old problem"] --> S["recall_search"] + S --> A["ranked anchor"] + A --> E["expand_around"] + E --> T["step next / prev"] + Q -. exact identifier .-> G["grep"] + R["what is current?"] --> RS["recent_sessions"] +``` + +Every discovery tool accepts an optional `source` (`claude` | `codex` | `cursor`), a +`scope_cwd` to narrow results to the current repo (worktrees collapse to the repo root), and +local calendar dates (`on_date`, or `start_date` / `end_date`, plus an IANA `timezone`). +Ranked anchors carry provenance and a human-readable timestamp. `grep` scans **all** indexed +transcripts on demand — including under-the-hood turns (tool output, thinking) that never +became search chunks. On-demand only: no proactive context injection into every prompt. + +
+See a complete call + +```json +{ + "query": "why did refresh tokens conflict?", + "scope_cwd": "/work/keeper", + "source": "codex", + "start_date": "2026-05-01", + "end_date": "2026-06-30", + "timezone": "Europe/Moscow" +} +``` + +`recall_search` answers `{"anchors": [...], "degraded": null | "reason"}`. When `degraded` +is set, the embedding provider was unreachable and only literal matching ran — the agent can +say so instead of mistaking a lexical miss for an empty history. + +
+ +## Quick start + +Two pieces: a Python CLI (which also ships the MCP server) and a plugin that wires it into +your agent. Budget about two minutes plus the first index run. + +### 1. Install the CLI and build the index ```bash pipx install git+https://github.com/AbsoluteMode/session-recall session-recall setup # one question (interaction language), then the first index ``` -`setup` is optional sugar — `session-recall index` directly works too, every -setting has a default. The first run walks your whole history; later runs are -incremental. Scripted installs: `session-recall setup --lang en --yes`. +No key required: with nothing configured, indexing runs on a bundled CPU model, downloaded +once and picked by your interaction language. The first run walks your whole history — +minutes for months of transcripts, seconds after that. Scripted installs: +`session-recall setup --lang en --yes`. -That is the whole thing: with no key and no local server, indexing runs on a -bundled CPU model, downloaded once and picked by your interaction language -(`SESSION_RECALL_LANG=en|zh|…`, multilingual when unset) — see -[Embedding providers](#embedding-providers) for the options. Hosted Voyage -embeddings rank noticeably better; to use them instead, export a key first: +```console +$ session-recall index +indexed 2175 chunks from changed transcripts -```bash -export VOYAGE_API_KEY=... # voyageai.com; put the line in your shell profile +your history: 1053 sessions spanning 168 days, 40,037 searchable fragments + Claude Code 372 · Codex 680 · Cursor 1 + busiest: sidekey, trend_detection, glitch ``` -`pipx` puts `session-recall` and `session-recall-mcp` on `~/.local/bin` — exactly where the -plugin manifests look for them. The first index depends on how much history you have: minutes -for months of transcripts, seconds after that. +Hosted Voyage embeddings rank noticeably better than the bundled model; to use them, export +`VOYAGE_API_KEY` before indexing — see [Embedding providers](#embedding-providers). -### 2. Plugin +### 2. Connect your agents -**Claude Code** +`pipx` puts `session-recall` and `session-recall-mcp` on `~/.local/bin` — exactly where the +plugin manifests look for them. -``` +
+Claude Code + +```text /plugin marketplace add AbsoluteMode/session-recall /plugin install session-recall ``` -Then start a new session — MCP servers and skills load at session start, not on install. +Then start a new session — MCP servers, skills, and the SessionStart hook load at session +start, not on install. Prefer to let the agent finish the job? Say `set up session-recall` +(or run `/session-recall:setup`): it asks the onboarding questions in chat, runs the +commands itself, and ends with a health check and a real search over your history. -Prefer to let the agent do the setup? After installing the plugin, say -"set up session-recall" (or run `/session-recall:setup`): the agent asks the -three onboarding questions in chat — language, local or cloud embedder, -solo or team — runs the commands itself, and finishes with a health check -and a real search over your history. +
-**Codex** — the `.codex-plugin/plugin.json` manifest is ready to drop into a local repo or your -personal marketplace; see the +
+Codex + +The repository ships a native [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json) — +ready to drop into a local repo or your personal marketplace; see the [local plugin installation guide](https://learn.chatgpt.com/docs/build-plugins#install-a-local-plugin-manually). Codex also asks you to review newly installed hooks once via `/hooks`. -**Cursor** — this repository also ships a native `.cursor-plugin/plugin.json`, Cursor-format -MCP and hook configs, and the same skills/commands/recall subagent. Add the repository as a -marketplace, then install the plugin in Cursor: +
+ +
+Cursor + +Requires Cursor 2.5+ (plugins were introduced there). Add the repository as a marketplace: ```bash cursor-agent plugin marketplace add https://github.com/AbsoluteMode/session-recall.git ``` -Then type `/add-plugin session-recall` in Cursor Agent. This requires Cursor 2.5+ (plugins were -introduced there). The plugin exposes the five recall tools to Cursor and its `sessionStart` -hook refreshes Claude Code, Codex, and Cursor history together. For local development, launch -`cursor-agent --plugin-dir /absolute/path/to/session-recall` instead of installing a cached copy. -Cursor shows its normal one-time approval for the local stdio MCP server; approve -`session-recall` so the tools can start. +Then type `/add-plugin session-recall` in Cursor Agent and approve the local stdio MCP +server once, so the tools can start. For plugin development, launch +`cursor-agent --plugin-dir /absolute/path/to/session-recall` instead of installing a +cached copy. + +Cursor is auto-detected at its normal macOS/Linux data path and does not need to be +running. Portable or custom profile? Point at the database directly with +`SESSION_RECALL_CURSOR_DB=/path/to/User/globalStorage/state.vscdb`. + +
### 3. Check it works @@ -178,101 +201,100 @@ Cursor shows its normal one-time approval for the local stdio MCP server; approv session-recall search "something you actually discussed last week" ``` -Hits with a `score` mean semantic search is live. In the agent, `claude mcp list` should show -`session-recall ✔ Connected`, and asking it about past work should trigger `recall_search`. - -Nothing else to configure: each native plugin ships the host's startup-hook format and re-indexes -in the background, so the shared index keeps up with all three histories on its own. - -Cursor is auto-detected at its normal macOS/Linux data path and does not need to be running. -If you use a portable or custom Cursor profile, point directly at its database with -`SESSION_RECALL_CURSOR_DB=/path/to/User/globalStorage/state.vscdb`. Session Recall opens it -read-only and uses SQLite's backup API, so a live WAL database is read consistently without -blocking the editor. - -### Troubleshooting - -Start here — it checks the whole chain and exits non-zero when something is actually -broken, so it also works from a timer: +Hits with a `score` mean semantic search is live. In the agent, `claude mcp list` should +show `session-recall ✔ Connected`, and asking about past work should trigger +`recall_search`. Nothing else to configure: each plugin ships its host's startup hook and +re-indexes in the background, so the shared index keeps up with all three histories on its +own. -```console -$ session-recall health -[ok ] Freshness 2 minutes behind -[warn] Embedder responded in 5828 ms - → slow provider will make indexing crawl -[ok ] Vector space builtin/BAAI/bge-small-en-v1.5/384 -[ok ] Corpus 1054 sessions (claude 373, codex 680, cursor 1) -[ok ] Sources claude, codex, cursor present +## How it works -verdict: AMBER (voyage/voyage-4-large, index at ~/.local/share/session-recall/index.db) +```mermaid +flowchart TB + subgraph Sources["local history sources"] + CC["Claude Code JSONL"] + CX["Codex JSONL"] + CU["Cursor SQLite"] + end + + CC --> I["incremental indexer"] + CX --> I + CU -->|"consistent WAL snapshot"| I + I --> V["conversation surface → embeddings"] + I --> R["raw trace, kept local"] + V --> DB["SQLite · sqlite-vec KNN · FTS5"] + R --> DB + DB --> MCP["five on-demand MCP tools"] + MCP --> A["Claude Code · Codex · Cursor · any MCP client"] ``` -Freshness compares the newest transcript on disk against the newest turn in the index, -so an indexer that runs on every session and fails every time still shows as behind — -which is exactly the failure that is otherwise invisible. +Only the conversation "surface" is embedded — user prompts and assistant text replies. Tool +calls, results, reasoning, and other trace data are never sent to an embedding provider but +stay reachable on demand via `expand_around`, `step`, and `grep`. Claude sidechains and +spawned-subagent sessions are intentionally skipped: under-the-hood tooling, not the +conversation. -| Symptom | Cause | -|---|---| -| `recall_search` answers with `degraded` set | The embedding provider is unreachable — only literal word matching ran. Results are still real, but a miss proves nothing. | -| `degraded` says "embedder changed" | The index was built in a different embedding space than the current config. Run `session-recall index` to re-embed; semantic ranking stays off until then, on purpose. | -| Indexer logs `HTTP code 403` with an HTML body | Not your key: a WAF is blocking your IP (common on VPN and datacenter exits). Same 403 appears with no key at all. Route egress elsewhere or switch provider. | -| `Missing dependencies for SOCKS support` | A SOCKS proxy is set in the environment but `PySocks` is not installed in that venv. | -| `recent_sessions` shows an old timestamp | The indexer has not succeeded recently. Run `session-recall index` by hand and read the output. | +Cursor is read from its SQLite store with the online backup API, so a live WAL database is +captured consistently without blocking the editor. Its bubbles are normalized into durable, +content-addressed JSONL snapshots under the data directory — deep navigation keeps working +after Cursor closes, upgrades, or is uninstalled. -### CLI reference +Indexing is incremental and cheap on live transcripts: they are append-only, so unchanged +chunks are matched by content hash and their vectors reused — only new turns hit the +embedding provider. Moving a Codex rollout into the archive also reuses its vectors. Each +file indexes in its own transaction; a failing file is logged and retried next run, never +aborting the rest. + +## CLI cheat sheet ```bash -session-recall index --source claude|codex|cursor|all # defaults to all -session-recall search "query" --source codex -session-recall recent --date 2026-07-14 # this computer's timezone -session-recall search "deployment work" --start-date 2026-07-14 \ - --end-date 2026-07-14 --timezone Asia/Yekaterinburg -session-recall grep "exact" --limit 100 # raw scan, no API key needed -session-recall prune # drop rows for deleted transcripts -``` +# Refresh every history, or one source +session-recall index +session-recall index --source cursor -`search`, `recent`, `grep`, and `prune` all take an optional `--source claude|codex|cursor`; omit it to -search the unified history. Date filters are inclusive and either boundary may be omitted; the timezone -defaults to this computer's and accepts any IANA name. `grep` caps at 100 matches by default. +# Semantic search — unified by default, scopable to a repo +session-recall search "why did we choose the keeper service?" +session-recall search "deployment work" --source codex --scope /work/keeper -For development, an in-tree virtualenv works too: +# Local calendar dates, any IANA timezone (defaults to this computer's) +session-recall recent --date 2026-07-14 +session-recall search "deployment work" \ + --start-date 2026-07-14 --end-date 2026-07-16 \ + --timezone Asia/Yekaterinburg -```bash -python -m venv .venv && .venv/bin/pip install -e . -.venv/bin/session-recall index -``` - -To register the MCP server by hand instead of using the plugin: +# Exact raw scan — no embedding call, caps at 100 matches by default +session-recall grep "invalid_grant" --limit 100 -```bash -claude mcp add session-recall --scope user -- /absolute/path/.venv/bin/session-recall-mcp +# Housekeeping +session-recall prune # drop rows for transcripts deleted from disk +session-recall health # the whole chain, verdict GREEN/AMBER/RED ``` +`search`, `recent`, `grep`, and `prune` all take `--source claude|codex|cursor`; omit it for +the unified history. Date filters are inclusive and either boundary may be omitted. + ## Embedding providers Nothing is locked to one vendor. `SESSION_RECALL_EMBED=` sets endpoint, model, -dimension and reranker together, because those four are not independent choices: +dimension, and reranker together, because those four are not independent choices: -| preset | runs | model | dim | reranker | -|---|---|---|---|---| -| `voyage` | hosted, needs a key | `voyage-4-large` | 1024 | `rerank-2.5` | -| `ollama` | **local, free** | `nomic-embed-text` | 768 | — | -| `lmstudio` | **local, free** | `nomic-embed-text-v1.5` | 768 | — | -| `openai` | hosted, needs a key | `text-embedding-3-large` | 1024 | — | +| Preset | Runs | Model | Dim | Reranker | +|---|---|---|---:|---| | `builtin-en` | **bundled, free** | `bge-small-en-v1.5` | 384 | — | | `builtin-zh` | **bundled, free** | `bge-small-zh-v1.5` | 512 | — | | `builtin-multi` | **bundled, free** | `paraphrase-multilingual-MiniLM-L12-v2` | 384 | — | +| `ollama` | **local, free** | `nomic-embed-text` | 768 | — | +| `lmstudio` | **local, free** | `nomic-embed-text-v1.5` | 768 | — | +| `voyage` | hosted, needs a key | `voyage-4-large` | 1024 | `rerank-2.5` | +| `openai` | hosted, needs a key | `text-embedding-3-large` | 1024 | — | -With no preset set, session-recall picks Voyage when `VOYAGE_API_KEY` is -present, then probes for a local server already listening, and otherwise runs -the **bundled ONNX model** — out of the box always works. The bundled flavor -follows the interaction language you choose at onboarding -(`SESSION_RECALL_LANG=en|zh|…`; a small English or Chinese specialist, -multilingual for every other answer and for no answer at all — `builtin` as a -preset name resolves the same way). First use downloads the model once into -the data dir (70–240MB), CPU inference from then on; ranking is noticeably -coarser than hosted Voyage — a starting point, not the ceiling. With a key -configured, no probe runs. +With no preset set, Session Recall picks Voyage when `VOYAGE_API_KEY` is present, then +probes for a local server already listening, and otherwise runs the bundled ONNX model — +out of the box always works. The bundled flavor follows the interaction language you chose +at onboarding (`SESSION_RECALL_LANG=en|zh|…`: a small English or Chinese specialist, +multilingual otherwise). First use downloads the model once into the data dir (70–240 MB), +CPU inference from then on. Ranking is noticeably coarser than hosted Voyage — a starting +point, not the ceiling. Local presets ship no reranker, so ranking is KNN + FTS only. **Free and local, start to finish:** @@ -282,8 +304,8 @@ export SESSION_RECALL_EMBED=ollama session-recall index ``` -**Your own endpoint** — any server speaking `/v1/embeddings` (llama.cpp, vLLM, a -company gateway). Individual variables always beat the preset, so mix freely: +**Your own endpoint** — any server speaking `/v1/embeddings` (llama.cpp, vLLM, a company +gateway). Individual variables always beat the preset, so mix freely: ```bash export SESSION_RECALL_EMBED_PROVIDER=openai-compatible @@ -292,32 +314,34 @@ export SESSION_RECALL_EMBED_MODEL=your-model export SESSION_RECALL_EMBED_DIM=1024 ``` -Two things worth knowing before you switch: +**A different embedder needs its own index.** Vector tables are fixed-width, so changing +the model or dimension means rebuilding: delete `~/.local/share/session-recall/index.db` +and re-run `index`. Session Recall fingerprints the embedding space of every indexed file +and refuses to mix spaces — semantic search shuts off with an explicit message instead of +returning misleading rankings. -- **A different embedder needs its own index.** Vector tables are fixed-width, so - changing the model or dimension means rebuilding: delete - `~/.local/share/session-recall/index.db` and re-run `index`. Attempting to reuse the old - one now fails with a message saying exactly that, rather than looking like a dead - embedder. -- **Local presets ship no reranker**, so ranking is KNN + FTS only — good enough, but - noticeably coarser than the hosted path. +
+Model licensing notes -On model choice: `nomic-embed-text` is the default because it is Apache-2.0 and installs in -one command. Stronger small models exist — `jina-embeddings-v5-text-nano` scores far higher -for its size — but they are **CC BY-NC**, which anyone indexing work history would be -violating without ever being told. If your use is genuinely non-commercial, point the -variables above at one. If you work in more than English, `qwen3-embedding:0.6b` (Apache-2.0) -handles multilingual history far better than `nomic`. +`nomic-embed-text` is the local default because it is Apache-2.0 and installs in one +command. Stronger small models exist — `jina-embeddings-v5-text-nano` scores far higher for +its size — but they are **CC BY-NC**, which anyone indexing work history would be violating +without ever being told. If your use is genuinely non-commercial, point the variables above +at one. If you work in more than English, `qwen3-embedding:0.6b` (Apache-2.0) handles +multilingual history far better than `nomic`. + +
## Keeping the index fresh -If you installed the plugin, this is already handled — skip to the next section. The bundled -`SessionStart` hook works on both hosts and runs `session-recall index` in the background, and -the default `--source all` refreshes all three histories. Indexing is incremental (it skips -already-indexed files by signature), so staying fresh is cheap. +If you installed a plugin, this is already handled: the bundled `SessionStart` hook runs +`session-recall index` in the background on every session start, and incremental indexing +keeps it cheap. + +
+Registered the MCP server by hand? Add the hook yourself -Only if you registered the MCP server by hand, add the hook yourself in -`~/.claude/settings.json`: +In `~/.claude/settings.json`: ```json "hooks": { @@ -331,105 +355,87 @@ Only if you registered the MCP server by hand, add the hook yourself in ``` The `pgrep` guard prevents overlapping runs; `( … & )` detaches so session start doesn't -wait. Keep the host-level hook synchronous: the shell already backgrounds the indexer, and -Codex ignores Claude's `async` extension. A `launchd`/cron timer is another option. (Local on one machine is enough; a server-side -index only makes sense across several machines — at the cost of privacy and network.) +wait. Keep the host-level hook synchronous — the shell already backgrounds the indexer, and +Codex ignores Claude's `async` extension. A `launchd`/cron timer works too. + +
## Team mode — ask a colleague's history -The same recall, across machines: pair with a colleague once, and your agent can ask -their agent about their past work. +The same recall, across machines: pair with a colleague once, and your agent can ask their +agent about their past work. -> **you → a colleague's agent:** when you hit the local-launch problem with X — how did -> you solve it? +> **you → a colleague's agent:** when you hit the local-launch problem with X — how did you +> solve it? > -> **their agent** *(after the colleague approves the answer)*: pin the config to …, then -> …, and the problem does not come back. +> **their agent** *(after the colleague approves the answer)*: pin the config to …, then …, +> and the problem does not come back. -The agents negotiate the details between themselves; what used to be a Slack thread and a -half-remembered explanation becomes one question and one grounded answer. You never see -the colleague's raw history — only the approved answer. +What used to be a Slack thread and a half-remembered explanation becomes one question and +one grounded answer. You never see the colleague's raw history — only the answer they +approved. Privacy here is mechanics, not policy: -- questions and answers travel as **end-to-end encrypted envelopes** through a relay that - stores blind blobs — the relay cannot read them; -- answers are built by an **isolated read-only worker**, scoped to the projects that - contact was explicitly granted (`share allow`); -- every candidate answer passes a **secret scanner** and then **explicit approval** by - the owner (Telegram bot, or `share approve` locally) before it leaves the machine; +- questions and answers travel as **end-to-end encrypted envelopes**; the relay stores blind + blobs it cannot read; +- answers are built by an **isolated read-only worker**, scoped to the projects that contact + was explicitly granted (`share allow`); +- every candidate answer passes a **secret scanner** and then **explicit owner approval** + (Telegram bot, or `share approve` locally) before it leaves the machine; - a contact can be paused any time (`share pause`), a peer revoked (`share revoke`). -### Pick a transport (once, before pairing) +Searching a peer's index needs no embedding setup on your side: the query travels as text, +and the owner's worker embeds it with their own provider against their own index. + +
+Pick a transport (once, before pairing) -Nothing is baked in: a fresh install has **no transport** and never talks to a -server you didn't choose. The relay is blind — everything it carries is sealed -and signed on the clients — so which one to use is coordination between peers, -not a matter of trust. Three recipes, simplest first: +A fresh install has **no transport** and never talks to a server you didn't choose. The +relay is blind — everything it carries is sealed and signed on the clients — so which one to +use is coordination between peers, not a matter of trust. -**Shared folder — zero infrastructure.** Two accounts on one machine, or any -folder both peers sync (Syncthing, Dropbox, an NFS mount): +**Shared folder — zero infrastructure.** Two accounts on one machine, or any folder both +peers sync (Syncthing, Dropbox, an NFS mount): ```bash export SESSION_RECALL_SHARE_TRANSPORT_DIR=~/Sync/sr-share # both peers, same folder ``` -**Your relay on the LAN.** One machine runs it, everyone points at it. -Envelopes are end-to-end encrypted regardless, but this is plain HTTP — -mailbox addresses travel readable, so keep it to a network you trust: +**Your relay on the LAN.** One machine runs it, everyone points at it. Envelopes are +end-to-end encrypted regardless, but this is plain HTTP — keep it to a network you trust: ```bash session-recall share relay --port 8787 --host 0.0.0.0 # on the relay machine export SESSION_RECALL_RELAY_URL=http://192.168.1.20:8787 # on every peer ``` -**Your relay on the internet.** The relay binds localhost on purpose and -expects a TLS terminator in front. On the server: +**Your relay on the internet.** The relay binds localhost on purpose and expects a TLS +terminator in front (Caddy is the two-line option): ```bash -pipx install git+https://github.com/AbsoluteMode/session-recall session-recall share relay --port 8787 # binds 127.0.0.1 ``` -As a user systemd service (`~/.config/systemd/user/sr-relay.service`): - -```ini -[Unit] -Description=session-recall share relay - -[Service] -ExecStart=%h/.local/bin/session-recall share relay --port 8787 -Restart=on-failure - -[Install] -WantedBy=default.target -``` - -```bash -systemctl --user enable --now sr-relay -loginctl enable-linger $USER # keep it alive after logout -``` - -Any TLS proxy in front works; Caddy is the two-line option: - -``` +```text relay.example.com { reverse_proxy 127.0.0.1:8787 } ``` -Then on every peer: `export SESSION_RECALL_RELAY_URL=https://relay.example.com`. -The relay stores only sealed blobs under `/share-relay`, and a -mailbox is emptied on fetch; `SESSION_RECALL_RELAY_URL=none` keeps an install -network-silent on purpose. Put the `export` in your shell profile so agents +Then on every peer: `export SESSION_RECALL_RELAY_URL=https://relay.example.com`. The relay +stores only sealed blobs, and a mailbox is emptied on fetch. `SESSION_RECALL_RELAY_URL=none` +keeps an install network-silent on purpose. Put the `export` in your shell profile so agents and timers see it too. -### Pairing +
+ +
+Pairing and asking Pairing is a one-time ceremony with a short SAS check, then asking is one command: ```bash -export SESSION_RECALL_RELAY_URL=https://relay.example.com # both sides: the transport you picked session-recall share init # once per device, both sides session-recall share invite # you: prints a one-time code session-recall share join # colleague: accepts it @@ -442,27 +448,23 @@ session-recall share ask "how did you fix the local X launch?" session-recall share fetch # collect the answers ``` -Searching a peer's index needs no embedding setup on your side: the query travels as -text, and the owner's worker embeds it with their own provider against their own index. - -## meta docs — the project's memory, written down +
-Raw recall answers "what was said". meta docs answers the questions agents -actually ask mid-task: *was this bug fixed before? how do I perform this -action? why was it decided this way?* A daily job hands each session's -dialogue — user messages and final answers only, never the tool noise — to a -distiller agent that maintains entries in a git repository of your choice: +## Meta docs — the project's memory, written down -- `/bugs/` — bugs that were actually fixed: how each was recognized, - diagnosed, fixed, and proven fixed; -- `/actions/` — procedures, step by step, written so an agent asked - again can follow the entry alone; -- `/decisions/` — contested choices: what was decided, why that way, - what was rejected; -- `USER/` — a global map of where your information lives and *how to find it* - (lookup commands, storage locations — never the stored values themselves). +Raw recall answers *what was said*. Meta docs answers what agents actually ask mid-task: +*was this bug fixed before? how do I perform this action? why was it decided this way?* A +daily job hands each session's dialogue — user messages and final answers, never the tool +noise — to a distiller agent that maintains Markdown entries in a Git repository you choose: -One file per entry, with related PRs and source sessions in the frontmatter. +- `/bugs/` — bugs that were actually fixed: how each was recognized, diagnosed, + fixed, and proven fixed; +- `/actions/` — procedures, step by step, written so an agent asked again can + follow the entry alone; +- `/decisions/` — contested choices: what was decided, why that way, what was + rejected; +- `USER/` — a global map of where your information lives and *how to find it* (lookup + commands and storage locations — never the stored values themselves). ```bash session-recall metadocs init ~/meta-docs --from-today # memory starts now @@ -472,45 +474,106 @@ session-recall metadocs status session-recall metadocs index-history --days 30 # opt-in: distill the past, once ``` -The agent's whole world is four MCP verbs — `search / create / edit / -delete` — and the load-bearing rules are server mechanics, not prompt -requests: `create` is refused until the agent has `search`ed (dedup is -mandatory), entries are scanned for secrets before a byte reaches disk, and -`delete` demands a reason. Every built-in tool is stripped from the call. -Runs are incremental (per-session watermarks) and each changed project gets -its own commit — review is a diff, undo is a revert, and sharing the memory -with a team is just pushing the repo somewhere private. Commits stay local -unless you opt into `--push`; the distilling agent and model come from config only -(`init --engine claude-cli|codex --model … [--reasoning …]`) — nothing is picked -silently. The daily job never touches history; `index-history` is the only door to -the past. +The distiller's whole world is four MCP verbs — `search / create / edit / delete` — and the +load-bearing rules are server mechanics, not prompt requests: `create` is refused until the +agent has `search`ed (dedup is mandatory), entries are scanned for secrets before a byte +reaches disk, and `delete` demands a reason. Runs are incremental, and each changed project +gets its own local commit — review is a diff, undo is a revert, and sharing the memory with +a team is just pushing the repo somewhere private. Nothing is pushed unless you opt into +`--push`; the engine and model come from config only +(`init --engine claude-cli|codex --model …`) — nothing is picked silently. + +## Privacy is a hard invariant + +This is a public repository. **Only code goes in it.** Runtime data lives under +`~/.local/share/session-recall/`, outside the repo tree — it physically cannot be committed. + +| Stays on your machine | Leaves only when you choose it | +|---|---| +| Original Claude Code and Codex transcripts | Conversation surface text → your configured hosted embedder | +| Cursor's SQLite store and its normalized snapshots | An explicitly approved team-mode answer | +| Tool calls, outputs, reasoning — the whole raw trace | Nothing, on the bundled/local embedding path | +| The SQLite index and stored vectors | | + +- API keys are environment variables only; `.gitignore` blocks `.env`. +- Tests use synthetic fixtures, never a real slice of a session. +- The bundled provider keeps the entire indexing path on-device. If you choose a hosted + provider, pick one you trust with your transcript surface text. + +## Troubleshooting + +Start here — it checks the whole chain and exits non-zero when something is actually +broken, so it also works from a timer: + +```console +$ session-recall health +[ok ] Freshness 2 minutes behind +[warn] Embedder responded in 5828 ms + → slow provider will make indexing crawl +[ok ] Vector space builtin/BAAI/bge-small-en-v1.5/384 +[ok ] Corpus 1054 sessions (claude 373, codex 680, cursor 1) +[ok ] Sources claude, codex, cursor present + +verdict: AMBER (voyage/voyage-4-large, index at ~/.local/share/session-recall/index.db) +``` + +Freshness compares the newest transcript on disk against the newest turn in the index, so +an indexer that runs on every session and fails every time still shows as behind — exactly +the failure that is otherwise invisible. + +| Symptom | Cause / next step | +|---|---| +| `recall_search` answers with `degraded` set | The embedding provider is unreachable — only literal matching ran. Results are real, but a miss proves nothing. | +| `degraded` says "embedder changed" | The index was built in a different embedding space. Run `session-recall index` to re-embed; semantic ranking stays off until then, on purpose. | +| Indexer logs `HTTP code 403` with an HTML body | Not your key: a WAF is blocking your IP (common on VPN and datacenter exits). The same 403 appears with no key at all. Route egress elsewhere or switch provider. | +| `Missing dependencies for SOCKS support` | A SOCKS proxy is set in the environment but `PySocks` is not installed in that venv. | +| `recent_sessions` shows an old timestamp | The indexer has not succeeded recently. Run `session-recall index` by hand and read the output. | +| Cursor lives in a custom profile | Set `SESSION_RECALL_CURSOR_DB=/path/to/User/globalStorage/state.vscdb`. | + +## Development + +```bash +git clone https://github.com/AbsoluteMode/session-recall.git +cd session-recall +python -m venv .venv +.venv/bin/pip install -e ".[dev]" +.venv/bin/pytest -q +``` + +To register the MCP server by hand instead of using the plugin: + +```bash +claude mcp add session-recall --scope user -- /absolute/path/.venv/bin/session-recall-mcp +``` + +Engineering rationale and invariants live in [`docs/decisions/`](docs/decisions/). Start with: + +- [Unified Claude Code + Codex index](docs/decisions/2026-07-10-unified-claude-codex-index.md) +- [Cursor as a durable raw source](docs/decisions/2026-08-03-cursor-durable-raw-source.md) +- [Project-scoped recall](docs/decisions/2026-06-26-recall-project-scope.md) +- [P2P sharing security gate](docs/decisions/2026-07-30-p2p-sharing-v1-security-gate.md) +- [Meta docs living project memory](docs/decisions/2026-07-31-metadocs-living-project-memory.md) ## Roadmap - **Hosted/team index** — one shared index for a team instead of per-machine copies. The honest open question: whoever searches must embed the query, so a shared vector space - implies a shared embedding path (a team key, or a small embedding proxy in front of - the provider). -- **Per-contact bypass** — skip per-answer approval for peers you fully trust; today - every answer is approved explicitly. -- **More histories** — Cursor shipped as the third source (sessions are read - from its SQLite store, subagents skipped, workspace mapped to a project); - other agents' transcripts next. - -## Privacy — hard invariant - -This is a public repository. **Only code goes in it.** - -- Data, indexes, raw transcripts, embeddings → `~/.local/share/session-recall/`, - **outside the repo tree**. They physically cannot be committed. -- API keys → environment only (`VOYAGE_API_KEY`); `.gitignore` blocks `.env`. -- Tests → synthetic fixtures only, never a real slice of a session. -- Claude Code transcripts, active and archived Codex transcripts, and Cursor's - SQLite store are read locally. Cursor's normalized raw snapshots stay under - the data directory. Only user/assistant surface text is embedded; - tool/reasoning trace data stays out of embeddings and is exposed only by - explicit raw expansion or grep. -- Chunk texts ARE sent to your configured embedding/rerank provider. With no - key or local server the bundled provider stays entirely on-device; if you - choose Voyage or another hosted endpoint, pick one you trust with your - transcript surface text. + implies a shared embedding path. +- **Per-contact approval bypass** — skip per-answer approval for peers you fully trust; + today every answer is approved explicitly. +- **More histories** — other agents' transcripts beyond Claude Code, Codex, and Cursor. + +## Contributing + +Issues, documentation improvements, host adapters, and translations are welcome. Keep +fixtures synthetic and never commit real transcripts, indexes, embeddings, or credentials. + +
+
+ Stop rebuilding context. Continue it. +
+
+ Get started +  ·  + MIT License +
diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..de343cc --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,556 @@ +
+ + + Session Recall——你的编码智能体会遗忘,Session Recall 不会。Claude Code、Codex 与 Cursor 共用的一份记忆。 + + +
+
+ +Claude Code、Codex 与 Cursor 的共享语义记忆。
+按语义找回旧日决策。打开原始证据。继续当时的工作。 + +
+
+ +[![MIT License](https://img.shields.io/badge/license-MIT-b8f84f?style=flat-square&labelColor=11120f)](LICENSE) +[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-b8f84f?style=flat-square&labelColor=11120f)](pyproject.toml) +[![MCP native](https://img.shields.io/badge/MCP-native-b8f84f?style=flat-square&labelColor=11120f)](src/session_recall/server.py) +[![Tests](https://img.shields.io/github/actions/workflow/status/AbsoluteMode/session-recall/test.yml?branch=main&style=flat-square&label=tests&labelColor=11120f)](https://github.com/AbsoluteMode/session-recall/actions/workflows/test.yml) + +
+ +[English](README.md) · [Русский](docs/README.ru.md) · [Español](README.es-ES.md) · 中文 + +*英文版为基准版本,翻译可能滞后。* + +
+ +--- + +你的编码智能体只记得当前这场对话,而你的工作散落在数月的对话之中—— +被恢复的会话、并行的订阅、多个 worktree、不同的智能体。 + +Session Recall 把这些历史汇成一个本地优先的索引,再通过五个专注的 MCP 工具提供出来。 +新会话可以找回 Codex 昨天想通的方案、Claude Code 三个月前否掉的思路——并附上指回 +真实对话轮次、工具输出与推理过程的链接。它不是谁手工维护的摘要文件: +原始对话始终是事实来源。 + +> **你:** 我们之前在修两个服务之间的认证令牌冲突——最后是怎么定的? +> +> **智能体:** *(recall_search → expand_around)* 两个服务共用同一个 OAuth 账号,而提供方按 +> 账号轮换刷新令牌,因此任何一方刷新都会让另一方的副本失效。你否掉了共享凭据目录的补丁, +> 认为耦合太重,最终定下由一个 keeper 服务独占该会话。规格说明一直没写——那正是下一步。 + +## 你会得到什么 + +| | 能力 | 带来的改变 | +|---|---|---| +| **同一份记忆** | Claude Code、Codex 和 Cursor 写入同一个索引 | 切换智能体时无需从头重述项目脉络 | +| **语义检索** | 按语义搜索,而不只是精确字词 | 找回那些你能描述却无法逐字引用的决策 | +| **深度导航** | 打开原始轮次:工具调用、输出、推理过程 | 亲自核实答案,而不是轻信摘要 | +| **诚实降级** | 语义搜索失效时会被明确报告 | 纯字面回退绝不冒充语义搜索 | +| **默认本地** | 内置 ONNX 嵌入与本地 SQLite | 无需密钥、服务器或账号即可上手 | +| **按范围召回** | 按仓库、来源或本地日历日期过滤 | 把无关项目挡在答案之外 | +| **团队问答** | 询问同事的本地记忆,经所有者批准 | 分享来之不易的上下文,而不暴露原始会话 | + +## 在哪些场景见效 + +- **会话入场。** 新会话一开始就带着上下文——无论你同时打理几份订阅、在智能体之间 + 来回切换,还是回到某个“当时聊过”的任务。 +- **Bug 与回归。** 动手修复之前,智能体先向历史提问:*这个 bug 以前见过吗?当时怎么修的? + 我们凭什么相信它修好了?* 复发不再像一个全新的 bug——修复也从打补丁变成对组件的深挖。 +- **操作流程。** 一套流程只讲一遍——怎么读一份 trace、怎么按任务拆解 token 开销—— + 之后的任何会话都能照着重演,无需再被手把手带一遍。 +- **因果关系。** 当你说“我们改掉这个决定吧”,智能体会找出当初拍板的那一刻: + *“选 X 是为了兼容 Y——动手之前,先确认 Y 不受影响。”* + +## 五个工具,一条工作流 + +接口刻意保持精简: + +| MCP 工具 | 何时使用 | +|---|---| +| `recall_search(query)` | 你记得大意,却记不清原话 | +| `expand_around(session_id, uuid)` | 你已找到锚点,需要它周围的证据 | +| `step(session_id, uuid, direction)` | 你想看相邻的原始轮次,而不想再搜一遍 | +| `grep(pattern)` | 你手里有精确的报错、符号、路径或标识符 | +| `recent_sessions()` | 你想要最新的工作——以及索引的新鲜度 | + +```mermaid +flowchart LR + Q["describe the old problem"] --> S["recall_search"] + S --> A["ranked anchor"] + A --> E["expand_around"] + E --> T["step next / prev"] + Q -. exact identifier .-> G["grep"] + R["what is current?"] --> RS["recent_sessions"] +``` + +每个检索工具都接受可选的 `source`(`claude` | `codex` | `cursor`)、把结果收窄到当前仓库的 +`scope_cwd`(worktree 会折叠到仓库根目录),以及本地日历日期(`on_date`,或 +`start_date` / `end_date`,外加 IANA `timezone`)。排好序的锚点自带出处与人类可读的时间戳。 +`grep` 按需扫描**所有**已索引的会话记录——包括那些从未成为搜索分块的幕后轮次 +(工具输出、思考过程)。一切只按需进行:不会向每条提示词主动注入上下文。 + +
+查看一次完整调用 + +```json +{ + "query": "why did refresh tokens conflict?", + "scope_cwd": "/work/keeper", + "source": "codex", + "start_date": "2026-05-01", + "end_date": "2026-06-30", + "timezone": "Europe/Moscow" +} +``` + +`recall_search` 的应答形如 `{"anchors": [...], "degraded": null | "reason"}`。当 `degraded` +被置位时,说明嵌入提供方不可达、只运行了字面匹配——智能体可以如实说明这一点, +而不是把一次词面未命中误当作历史为空。 + +
+ +## 快速开始 + +一共两件东西:一个 Python CLI(同时附带 MCP 服务器),以及把它接进智能体的插件。 +预留大约两分钟,外加首次索引运行的时间。 + +### 1. 安装 CLI 并构建索引 + +```bash +pipx install git+https://github.com/AbsoluteMode/session-recall +session-recall setup # one question (interaction language), then the first index +``` + +无需密钥:在什么都不配置的情况下,索引由内置的 CPU 模型完成,模型只下载一次, +并按你的交互语言选定。首次运行会走完你的全部历史——数月的会话记录需要几分钟, +之后每次只要几秒。脚本化安装:`session-recall setup --lang en --yes`。 + +```console +$ session-recall index +indexed 2175 chunks from changed transcripts + +your history: 1053 sessions spanning 168 days, 40,037 searchable fragments + Claude Code 372 · Codex 680 · Cursor 1 + busiest: sidekey, trend_detection, glitch +``` + +托管的 Voyage 嵌入在排序上明显优于内置模型;要使用它,请在索引前导出 +`VOYAGE_API_KEY`——参见[嵌入提供方](#嵌入提供方)。 + +### 2. 接入你的智能体 + +`pipx` 会把 `session-recall` 和 `session-recall-mcp` 放到 `~/.local/bin`——插件清单 +正是在那里查找它们。 + +
+Claude Code + +```text +/plugin marketplace add AbsoluteMode/session-recall +/plugin install session-recall +``` + +然后开启一个新会话——MCP 服务器、技能和 SessionStart 钩子在会话启动时加载, +而不是安装时。想让智能体替你把事办完?对它说 `set up session-recall` +(或运行 `/session-recall:setup`):它会在对话中问完引导问题、自己执行命令, +最后以一次健康检查和一次针对你真实历史的搜索收尾。 + +
+ +
+Codex + +仓库自带原生的 [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json)——可直接放进 +本地仓库或你的个人 marketplace;参见 +[本地插件安装指南](https://learn.chatgpt.com/docs/build-plugins#install-a-local-plugin-manually)。 +Codex 还会要求你通过 `/hooks` 对新安装的钩子审查一次。 + +
+ +
+Cursor + +需要 Cursor 2.5+(插件正是在该版本引入的)。把本仓库添加为 marketplace: + +```bash +cursor-agent plugin marketplace add https://github.com/AbsoluteMode/session-recall.git +``` + +然后在 Cursor Agent 里输入 `/add-plugin session-recall`,并对本地 stdio MCP 服务器 +批准一次,工具即可启动。若要做插件开发,请用 +`cursor-agent --plugin-dir /absolute/path/to/session-recall` 启动,而不是安装缓存副本。 + +Cursor 会按其 macOS/Linux 常规数据路径被自动检测,且无需处于运行状态。 +便携版或自定义配置目录?用 +`SESSION_RECALL_CURSOR_DB=/path/to/User/globalStorage/state.vscdb` 直接指向数据库。 + +
+ +### 3. 验证是否生效 + +```bash +session-recall search "something you actually discussed last week" +``` + +结果带有 `score` 就说明语义搜索已生效。在智能体里,`claude mcp list` 应显示 +`session-recall ✔ Connected`,而询问过去的工作应触发 `recall_search`。再无其他要配置的: +每个插件都自带其宿主的启动钩子并在后台重新索引,共享索引会自行跟上三份历史的进度。 + +## 工作原理 + +```mermaid +flowchart TB + subgraph Sources["local history sources"] + CC["Claude Code JSONL"] + CX["Codex JSONL"] + CU["Cursor SQLite"] + end + + CC --> I["incremental indexer"] + CX --> I + CU -->|"consistent WAL snapshot"| I + I --> V["conversation surface → embeddings"] + I --> R["raw trace, kept local"] + V --> DB["SQLite · sqlite-vec KNN · FTS5"] + R --> DB + DB --> MCP["five on-demand MCP tools"] + MCP --> A["Claude Code · Codex · Cursor · any MCP client"] +``` + +只有对话的“表层”会被嵌入——用户提示词与助手的文字回复。工具调用、结果、推理过程 +及其他追踪数据绝不会发送给嵌入提供方,但始终可以通过 `expand_around`、`step` 和 `grep` +按需取到。Claude 的 sidechain 与派生子智能体的会话被有意跳过:那是幕后的工具活动, +不是对话。 + +Cursor 的数据经 online backup API 从其 SQLite 存储读取,因此活动中的 WAL 数据库也能 +被一致地捕获,且不阻塞编辑器。其 bubble 会被规范化为数据目录下持久、按内容寻址的 +JSONL 快照——即使 Cursor 关闭、升级或被卸载,深度导航依然可用。 + +索引是增量式的,对活跃会话记录开销很小:它们只会追加,未变化的分块按内容哈希匹配 +并复用已有向量——只有新轮次才会触达嵌入提供方。把 Codex rollout 移入归档同样复用 +其向量。每个文件在自己的事务中完成索引;失败的文件会被记录并在下次运行时重试, +绝不拖垮其余文件。 + +## CLI 速查表 + +```bash +# Refresh every history, or one source +session-recall index +session-recall index --source cursor + +# Semantic search — unified by default, scopable to a repo +session-recall search "why did we choose the keeper service?" +session-recall search "deployment work" --source codex --scope /work/keeper + +# Local calendar dates, any IANA timezone (defaults to this computer's) +session-recall recent --date 2026-07-14 +session-recall search "deployment work" \ + --start-date 2026-07-14 --end-date 2026-07-16 \ + --timezone Asia/Yekaterinburg + +# Exact raw scan — no embedding call, caps at 100 matches by default +session-recall grep "invalid_grant" --limit 100 + +# Housekeeping +session-recall prune # drop rows for transcripts deleted from disk +session-recall health # the whole chain, verdict GREEN/AMBER/RED +``` + +`search`、`recent`、`grep` 和 `prune` 都接受 `--source claude|codex|cursor`;省略即为 +统一历史。日期过滤是闭区间,两端边界均可省略。 + +## 嵌入提供方 + +不锁定任何单一供应商。`SESSION_RECALL_EMBED=` 会把端点、模型、维度和重排序器 +一并设定,因为这四项并不是相互独立的选择: + +| 预设 | 运行方式 | 模型 | 维度 | 重排序器 | +|---|---|---|---:|---| +| `builtin-en` | **内置,免费** | `bge-small-en-v1.5` | 384 | — | +| `builtin-zh` | **内置,免费** | `bge-small-zh-v1.5` | 512 | — | +| `builtin-multi` | **内置,免费** | `paraphrase-multilingual-MiniLM-L12-v2` | 384 | — | +| `ollama` | **本地,免费** | `nomic-embed-text` | 768 | — | +| `lmstudio` | **本地,免费** | `nomic-embed-text-v1.5` | 768 | — | +| `voyage` | 托管,需要密钥 | `voyage-4-large` | 1024 | `rerank-2.5` | +| `openai` | 托管,需要密钥 | `text-embedding-3-large` | 1024 | — | + +未设置预设时,Session Recall 的取舍顺序是:存在 `VOYAGE_API_KEY` 时选 Voyage, +其次探测已在监听的本地服务器,否则运行内置 ONNX 模型——开箱即用永远成立。 +内置模型的具体选型跟随你在引导时选定的交互语言(`SESSION_RECALL_LANG=en|zh|…`: +英文或中文各有小型专用模型,其余语言用多语言模型)。首次使用会把模型一次性下载到 +数据目录(70–240 MB),此后始终在 CPU 上推理。其排序明显粗于托管的 Voyage—— +是起点,不是上限。本地预设不带重排序器,因此排序只靠 KNN + FTS。 + +**从头到尾免费且本地:** + +```bash +ollama pull nomic-embed-text +export SESSION_RECALL_EMBED=ollama +session-recall index +``` + +**你自己的端点**——任何实现 `/v1/embeddings` 的服务器(llama.cpp、vLLM、公司网关)。 +单项变量永远优先于预设,可以放心混搭: + +```bash +export SESSION_RECALL_EMBED_PROVIDER=openai-compatible +export SESSION_RECALL_EMBED_BASE_URL=https://embeddings.internal/v1 +export SESSION_RECALL_EMBED_MODEL=your-model +export SESSION_RECALL_EMBED_DIM=1024 +``` + +**换嵌入器就得换索引。** 向量表是定宽的,因此更换模型或维度意味着重建:删除 +`~/.local/share/session-recall/index.db` 并重新运行 `index`。Session Recall 会为每个 +已索引文件记录嵌入空间指纹,并拒绝混用不同空间——语义搜索会带着明确的提示直接关闭, +而不是返回具有误导性的排序。 + +
+模型许可说明 + +`nomic-embed-text` 之所以是本地默认,是因为它采用 Apache-2.0 许可证,而且一条命令 +就能装好。更强的小模型确实存在——`jina-embeddings-v5-text-nano` 以其体量而言得分 +高得多——但它们采用 **CC BY-NC** 许可证,任何拿它索引工作历史的人都在无人提醒的 +情况下违反条款。如果你的用途确属非商业,可把上面的变量指向这类模型。如果你的工作 +不止用英语,`qwen3-embedding:0.6b`(Apache-2.0)处理多语言历史的能力远胜 `nomic`。 + +
+ +## 保持索引新鲜 + +如果你安装了插件,这一步已经自动处理:内置的 `SessionStart` 钩子会在每次会话启动时 +于后台运行 `session-recall index`,而增量索引让这份开销一直很低。 + +
+手动注册了 MCP 服务器?自己补上钩子 + +在 `~/.claude/settings.json` 中: + +```json +"hooks": { + "SessionStart": [ + { "hooks": [ { + "type": "command", + "command": "sr=/abs/path/.venv/bin/session-recall; pgrep -f \"$sr index\" >/dev/null 2>&1 || (VOYAGE_API_KEY=... \"$sr\" index >/tmp/sr-index.log 2>&1 &)" + } ] } + ] +} +``` + +`pgrep` 守卫可防止重复运行;`( … & )` 让进程脱离,会话启动无需等待。宿主层面的钩子 +请保持同步——shell 已经把索引器放到了后台,而且 Codex 会忽略 Claude 的 `async` 扩展。 +用 `launchd`/cron 定时器也可以。 + +
+ +## 团队模式——询问同事的历史 + +同样的召回能力,跨越机器:与同事配对一次,你的智能体就能向对方的智能体询问 +他们过去的工作。 + +> **你 → 同事的智能体:** 你们当时碰到 X 的本地启动问题——是怎么解决的? +> +> **对方的智能体** *(在同事批准答案之后)*:把配置固定到……,接着……, +> 问题从此不再复现。 + +过去需要一串 Slack 消息加一段记忆模糊的解释,如今变成一个问题和一个有据可依的答案。 +你永远看不到同事的原始历史——只能看到他们批准的那个答案。 + +这里的隐私靠机制保障,而不是靠政策承诺: + +- 问题与答案以**端到端加密的信封**传输;中继存储的只是它自己也无法读取的盲 blob; +- 答案由**隔离的只读 worker** 构建,范围仅限该联系人被明确授权的项目(`share allow`); +- 每个候选答案在离开机器之前,都要先过**机密扫描器**,再经**所有者的明确批准** + (Telegram 机器人,或本地执行 `share approve`); +- 联系人可以随时暂停(`share pause`),对等方可以随时吊销(`share revoke`)。 + +搜索对等方的索引不需要你这边配置任何嵌入:查询以纯文本传输,由所有者的 worker +用他们自己的提供方、对着他们自己的索引完成嵌入。 + +
+选择传输方式(配对前,一次即可) + +全新安装**不带任何传输方式**,绝不会连接你未选择的服务器。中继是盲的——它承载的 +一切都在客户端完成密封和签名——所以选哪种传输只是对等双方之间的协调问题, +而非信任问题。 + +**共享文件夹——零基础设施。** 一台机器上的两个账户,或任何双方都在同步的文件夹 +(Syncthing、Dropbox、NFS 挂载): + +```bash +export SESSION_RECALL_SHARE_TRANSPORT_DIR=~/Sync/sr-share # both peers, same folder +``` + +**局域网内自建中继。** 一台机器运行中继,其余机器都指向它。信封无论如何都是端到端 +加密的,但这毕竟是明文 HTTP——请限制在你信任的网络内: + +```bash +session-recall share relay --port 8787 --host 0.0.0.0 # on the relay machine +export SESSION_RECALL_RELAY_URL=http://192.168.1.20:8787 # on every peer +``` + +**公网上自建中继。** 中继刻意只绑定 localhost,并期望前面有一个 TLS 终结器 +(Caddy 是两行配置就能搞定的选项): + +```bash +session-recall share relay --port 8787 # binds 127.0.0.1 +``` + +```text +relay.example.com { + reverse_proxy 127.0.0.1:8787 +} +``` + +然后在每个对等方上执行:`export SESSION_RECALL_RELAY_URL=https://relay.example.com`。 +中继只存储密封后的 blob,且邮箱在取件时即被清空。`SESSION_RECALL_RELAY_URL=none` +可让一套安装刻意保持网络静默。把这条 `export` 写进你的 shell 配置文件, +让智能体和定时器也能读到它。 + +
+ +
+配对与提问 + +配对是一次性的仪式,附带一个简短的 SAS 校验,之后提问只需一条命令: + +```bash +session-recall share init # once per device, both sides +session-recall share invite # you: prints a one-time code +session-recall share join # colleague: accepts it +session-recall share complete # you: finish the handshake +session-recall share trust # both: confirm the SAS matched, name the peer +session-recall share allow +session-recall share notify # owner side: worker + approval loop + +session-recall share ask "how did you fix the local X launch?" +session-recall share fetch # collect the answers +``` + +
+ +## Meta docs——写下来的项目记忆 + +原始召回回答的是*当时说了什么*。Meta docs 回答的是智能体在任务中途真正会问的问题: +*这个 bug 以前修过吗?这个操作怎么做?当初为什么这么定?* 一个每日任务会把每个会话的 +对话内容——用户消息和最终回答,绝不含工具噪音——交给一个蒸馏智能体,由它在你指定的 +Git 仓库里维护 Markdown 条目: + +- `/bugs/`——确实修复过的 bug:每个 bug 如何被识别、诊断、修复, + 以及如何证明已修复; +- `/actions/`——按步骤写下的操作流程,再次被问到的智能体只凭条目本身 + 就能照做; +- `/decisions/`——有过争议的选择:定了什么、为何这样定、否掉了什么; +- `USER/`——一张全局地图,记录你的信息存放在哪里以及*如何找到它*(查询命令与 + 存储位置——绝不记录存储的值本身)。 + +```bash +session-recall metadocs init ~/meta-docs --from-today # memory starts now +session-recall metadocs run # one pass now +session-recall metadocs enable # daily job: launchd (macOS) / systemd user timer (Linux) +session-recall metadocs status +session-recall metadocs index-history --days 30 # opt-in: distill the past, once +``` + +蒸馏器的全部世界只有四个 MCP 动词——`search / create / edit / delete`——而那些承重的 +规则是服务器机制,不是提示词请求:智能体没先 `search` 就调用 `create` 会被拒绝 +(去重是强制的),条目在任何字节落盘之前都会先做机密扫描,`delete` 必须给出理由。 +运行是增量式的,每个有变更的项目都会得到自己的本地提交——审阅就是看 diff,撤销就是 +revert,与团队共享这份记忆只需把仓库推到某个私有位置。除非你主动选择 `--push`, +否则什么都不会被推送;引擎和模型只来自配置(`init --engine claude-cli|codex --model …`)—— +没有任何东西被悄悄选定。 + +## 隐私是硬性不变量 + +这是一个公开仓库,**里面只有代码**。运行时数据位于 `~/.local/share/session-recall/`, +在仓库目录树之外——物理上就无法被提交。 + +| 留在你的机器上 | 只有你主动选择才会离开 | +|---|---| +| Claude Code 与 Codex 的原始会话记录 | 对话表层文本 → 你配置的托管嵌入器 | +| Cursor 的 SQLite 存储及其规范化快照 | 一条经明确批准的团队模式答案 | +| 工具调用、输出、推理过程——完整的原始追踪 | 走内置/本地嵌入路径时——什么都不会离开 | +| SQLite 索引与已存储的向量 | | + +- API 密钥只以环境变量形式存在;`.gitignore` 拦截 `.env`。 +- 测试使用合成夹具,绝不使用真实会话的片段。 +- 内置提供方让整条索引链路都留在设备上。如果你选择托管提供方,请选一个你放心 + 托付会话记录表层文本的。 + +## 故障排查 + +从这里开始——它会检查整条链路,并在确实出问题时以非零码退出,因此也适合放进定时器: + +```console +$ session-recall health +[ok ] Freshness 2 minutes behind +[warn] Embedder responded in 5828 ms + → slow provider will make indexing crawl +[ok ] Vector space builtin/BAAI/bge-small-en-v1.5/384 +[ok ] Corpus 1054 sessions (claude 373, codex 680, cursor 1) +[ok ] Sources claude, codex, cursor present + +verdict: AMBER (voyage/voyage-4-large, index at ~/.local/share/session-recall/index.db) +``` + +Freshness 比较的是磁盘上最新的会话记录与索引中最新的轮次,因此一个每次会话都运行 +却每次都失败的索引器仍会显示为落后——这正是那类否则完全不可见的故障。 + +| 症状 | 原因 / 下一步 | +|---|---| +| `recall_search` 的应答中 `degraded` 被置位 | 嵌入提供方不可达——只运行了字面匹配。结果是真实的,但未命中不能说明任何问题。 | +| `degraded` 提示 “embedder changed” | 索引构建于另一个嵌入空间。运行 `session-recall index` 重新嵌入;在那之前语义排序保持关闭——这是有意为之。 | +| 索引器日志出现带 HTML 响应体的 `HTTP code 403` | 不是你的密钥的问题:是 WAF 在拦截你的 IP(VPN 和数据中心出口很常见)。完全不带密钥时也会出现同样的 403。把出口流量改道别处,或更换提供方。 | +| `Missing dependencies for SOCKS support` | 环境中设置了 SOCKS 代理,但该 venv 里没有安装 `PySocks`。 | +| `recent_sessions` 显示的时间戳很旧 | 索引器最近没有成功运行过。手动运行 `session-recall index` 并阅读输出。 | +| Cursor 使用自定义配置目录 | 设置 `SESSION_RECALL_CURSOR_DB=/path/to/User/globalStorage/state.vscdb`。 | + +## 开发 + +```bash +git clone https://github.com/AbsoluteMode/session-recall.git +cd session-recall +python -m venv .venv +.venv/bin/pip install -e ".[dev]" +.venv/bin/pytest -q +``` + +不用插件、改为手动注册 MCP 服务器: + +```bash +claude mcp add session-recall --scope user -- /absolute/path/.venv/bin/session-recall-mcp +``` + +工程决策依据与不变量记录在 [`docs/decisions/`](docs/decisions/) 中。建议从这些读起: + +- [统一的 Claude Code + Codex 索引](docs/decisions/2026-07-10-unified-claude-codex-index.md) +- [Cursor 作为持久的原始数据源](docs/decisions/2026-08-03-cursor-durable-raw-source.md) +- [按项目范围的召回](docs/decisions/2026-06-26-recall-project-scope.md) +- [P2P 分享的安全门禁](docs/decisions/2026-07-30-p2p-sharing-v1-security-gate.md) +- [Meta docs——活的项目记忆](docs/decisions/2026-07-31-metadocs-living-project-memory.md) + +## 路线图 + +- **托管/团队索引**——为团队提供一个共享索引,而不是每台机器各存一份。坦率的 + 未解问题:谁搜索谁就得嵌入查询,因此共享向量空间意味着共享嵌入链路。 +- **按联系人绕过审批**——对你完全信任的对等方跳过逐条答案审批;目前每个答案 + 都需要明确批准。 +- **更多历史来源**——Claude Code、Codex 和 Cursor 之外其他智能体的会话记录。 + +## 参与贡献 + +欢迎提交 issue、改进文档、编写宿主适配器和翻译。夹具请保持合成数据,绝不要提交 +真实的会话记录、索引、嵌入或凭据。 + +
+
+ 别再重建上下文。延续它。 +
+
+ 立即开始 +  ·  + MIT 许可证 +
diff --git a/assets/og.webp b/assets/og.webp new file mode 100644 index 0000000000000000000000000000000000000000..7fdf2a5f0794d6188e989dac135d703fd3ff7e0e GIT binary patch literal 106300 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 z z&J5wj8hScQjRFPwE&5Z%u)@{Vu-4Y`=bl#K+qIS>HHHYw0dFPb;7SS&dSIZ#GSZc_ zOxF{T=7=yHC@*2#{GT4U%=h}o6-M|K&lr^u3APm|zxbG)z1NXjT}hb#F!bPRyJ=qI zca(X@M#fB~N-#ahD5WFl4key^`U;;1VL9#cQ4wgo$?xO5%xKtJD8_beY%zR|Dlqa_ zd)^YR=PN7lDtc~YsHO7Xt%z*$q7{SAyyk_T?lbKA{a4j3=?7%k`ME!2;U3^nKsDVB zd^jo7Q#mI(!Kt7pV+ZffK*~bfWQ(rbwzAAJeI`U;am> zGDKCDC-?4uSPX(y`|$aYC;KadWs`1Q-VJI=*TQ2!kxD8!J{L z%e|0BdJn_$U6WK3ReZA#Ve~d{tn7brX1SQ{k0fNHWAY16Cu3rOM(2EB9lU`UFYj#3 z4VWR+2B8zkO$elPPGR$cWN^Q{ZyIk18~lb-t4IF&hX;(|NdJhV8AQdjUws3RB*s+Gi zsg76xQf`hkOMQqz=iWy0gREmr6H=*u4Jqb76n22N9&A}8abB50lbGGpzqRB1-SS5W ze@-iQm(BDGZw8zY0|&e8CB88xW50|aGZZ!-bhDQRWlbkV;cTVme8(|#JO{B3af|Ra zH$u?LB{ilDtFEI}(XVZfuBQY(f6I_C4PBu5`}bxy2CG^z zi-ZBhCzna_Cm9pVz`rfN+%DnTsp<4Qa9Ly7G#&i3`SlmVwmU=7qB*UKBz{pvQEJ&M zO0ET$76?-mcy9ad$&2Yxkkg0166&&i63`7|eQTL7yt8WBGYlX>=&rE=v{x3DiI_Umw^3{jK;$CFO$R zD!+|!{fBVqL+KkdG>`Iknpc}W!$uTKu0Af**Tzp}OvNQ>3rCqxoG<~9ccItnf>BNH zy|ZA%P#xHY1`I;Asy1`;QK^e(yW_lK=kC4cdn0khp|b#(kO%ia_ly21Lvl6%h6a?Y z9!Mb{9HrbES9STQKM##A$grOH_+bq0_)G{+%Wjsn*5PY%g+Bjnn1(zvMp|tNOxia? ze2H2Nbs`~y+8xxYB40Rjoq)yd+2yRx4Wn#nb7Z0O@&B}L8Yc@`)6U|aD%QX`b6Xa* zItg{ikQCL!s`y62wSHSQFPZM;=+?(mi^MKDgS?S;x4+?vdI;~Kp%l02k1JARl!|`J zP4Ge)W~T(?x8GsBO9DQrV&Wr8hQSC(O~@6%_O(Klm-wDTzgnE@i0)!(g7vFpFbEci zgJ0JMiuZhtd%}4QDyGF>?vVl!!trlceHY-6%wQoi-hwEwn=TsX&?N#Ugyqr2`P^B7 zP{uisM{T4kh}*3 za2u@l2uF}GM|QIU3lTRJJjAKPRmKVu+w4-Af)P*z8uw#T0>WouXI%pG;r?{eqB&)T zf%n&U0e+X98zz)CuMQPjw~DrdH`~68i#I_sr_$vEu5bQ^nvIxR1J%iU&;J&(cm<`A za?g6fPTo%f*q?tjx1u?(4GLtn(>(R8?4{3|#o^)8i9vQn_lX^Ipv$H}{7a-a!JMXf zCJK=Xq+2gIrI@7CMRWJW9;Gu3q&X+t^;70_YdmXE`$xerMa-kw<)>7Vx|SSA!FlVi zo8Gi?c+^;8m#ra;(H8p~yrm5y6&I?vo>k%3%;dZ#WKR@ZV$>{!15WWwnvf8=riv#+(`Dh#E&Mb7_795rbSM#{K-)V2xUK!A zn&Cwr?88pxAwz(_&-3!}B!7DWdY~yeBml{(**uTVE;oH$X%L8hvpK4Aov$WAowqZ} zVA}^gKA`nua~9%V`{*AEP1jD&_K#;tUt=htz~1wRUs0li00aHo7IE7JM23I>0Mzjg AxBvhE literal 0 HcmV?d00001 diff --git a/docs/README.ru.md b/docs/README.ru.md index 7c934b9..b9a0558 100644 --- a/docs/README.ru.md +++ b/docs/README.ru.md @@ -1,88 +1,596 @@ -# session-recall — память для твоих кодинг-агентов - -*Full documentation in English: [README.md](../README.md)* - -Привет, ребят! - -Я давно делаю инструменты для себя — и вот впервые получился инструмент, -который нашёл отклик у коллег. - -**session-recall** векторизует все сессии кодинг-агентов на вашей машине и -складывает их в поисковый индекс. Это значит, что любая новая сессия может -быстро и с высокой точностью найти информацию из любой прошлой. Индексируются -истории Claude Code, Codex и Cursor; искать по ним может любой агент с -поддержкой MCP. - -## Когда это полезно - -Спустя два месяца активного использования — самые живые для меня кейсы. - -1. **Онбординг сессии.** Новая сессия сразу в контексте: - - у вас несколько подписок у одного провайдера — история общая; - - часто меняете кодинг-агентов — Claude читает то, что вчера придумал Codex; - - параллельно брейнштормите/ресёрчите решения в других сессиях; - - нужно вернуться к задаче, которую «когда-то обсуждали». - -2. **Баги.** session-recall — великолепный инструмент для отлова регрессий! - Прежде чем чинить баг, мой агент всегда ищет: «был ли уже этот баг», «как - чинили», «почему посчитали, что проблема решена» — и это просто вау. Даже - я забываю, что подобный рецидив уже случался, а он улавливает — и понимает, - что нужно углубиться в компонент. - -3. **Действия.** Мне не приходится дважды объяснять, что именно я имею в - виду, когда прошу посмотреть трейс, заполнить за меня рабочие таблички, - разобрать распределение токенов по задаче у конкретной LLM. Любое действие, - которое мы однажды успешно прошли, он воспроизводит без вопросов. - -4. **Причинно-следственные связи.** Я говорю «давай изменим вот это решение» — - а он смотрит на момент, когда решение принималось, и отвечает: «так, стоп, - мы тогда приняли X ради совместимости с Y — прежде чем менять, надо - убедиться, что всё будет окей». - -## Team: прийти в сессию коллеги - -Я работаю в командах — и внедрил механизм, с которым я могу прийти в историю -коллеги, не получив никакой личной информации, и быстро собрать нужное под -свою задачу. - -Например, я пишу агенту коллеги: «когда ты столкнулся с проблемой локального -запуска X — как ты её решил?» И через approve gate на его стороне агент -отвечает: «нужно сделать так-то и так-то, чтобы проблема не появлялась». -Агенты сами уточняют детали друг у друга — сложность коммуникации просто -исчезает. - -Приватность здесь — механика, а не обещание: запросы летят зашифрованными -конвертами через relay, отвечает изолированный read-only воркер строго по -разрешённому скоупу, каждый ответ проходит сканер секретов и ваше явное -одобрение, а любой контакт можно поставить на паузу. - -## meta docs: дистиллированная память проекта - -Сырой recall отвечает «что говорилось». А ежедневная джоба meta docs -перегоняет каждую сессию в живые документы в git-репо: - -- **bugs** — как поняли, что это баг, чем лечили, как убедились, что вылечено; -- **actions** — пошаговые процедуры, по которым агент воспроизводит действие сам; -- **decisions** — спорные решения и почему выбрали именно так; -- **USER** — карта, где что лежит и как это искать (только инструкции поиска, - никогда сами значения). - -Записи попадают в тот же поисковый индекс, ревью — обычный git-дифф, а -поделиться памятью с командой = запушить репо. - -## Настройки - -- **эмбеддинг-модель** — подключаемая: без ключей используется встроенная - локальная ONNX-модель; Voyage даёт более точное ранжирование, также можно - подключить любой OpenAI-совместимый endpoint; -- **solo / team** — можно ли устанавливать контакт с вашим MCP: share выключен - по умолчанию и включается явно; -- **поисковый индекс — локальный** (SQLite на вашей машине); облачный - командный индекс — в планах; -- **Cursor определяется автоматически** по стандартному локальному пути; - нестандартный профиль можно указать через `SESSION_RECALL_CURSOR_DB`; -- **approve gate по контактам** — сейчас каждый исходящий ответ подтверждается - вручную; bypass для доверенных контактов — в будущем. - -Я вижу огромный потенциал в «предыдущих» пользовательских сессиях — и считаю, -что именно они станут основой личных skills пользователей. + + +--- + +Кодинг-агенты помнят текущий чат. А работа живёт в месяцах чатов — возобновлённые сессии, +параллельные подписки, worktree, разные агенты. + +Session Recall превращает эту историю в единый local-first индекс и отдаёт её обратно через +пять узких MCP-инструментов. Свежая сессия может восстановить то, что вчера придумал Codex, +и то, что Claude Code отверг три месяца назад — со ссылками на реальные реплики, вывод +инструментов и рассуждения. Это не summary-файл, который кто-то ведёт руками: источником +истины остаётся сам разговор. + +> **вы:** мы чинили конфликт auth-токенов между двумя сервисами — на чём остановились? +> +> **агент:** *(recall_search → expand_around)* Оба сервиса сидели на одном OAuth-аккаунте, а +> провайдер ротирует refresh-токены на уровне аккаунта, поэтому каждый refresh инвалидировал +> копию соседа. Патч с общей директорией кредов вы отвергли — слишком жёсткая связка — и +> остановились на keeper-сервисе, который владеет сессией. Спека так и не была написана — +> это и был следующий шаг. + +## Что это даёт + +| | Возможность | Что это меняет | +|---|---|---| +| **Одна память** | Claude Code, Codex и Cursor пополняют один индекс | Смена агента не сбрасывает историю проекта | +| **Семантический поиск** | Поиск по смыслу, а не только по точным словам | Находятся решения, которые описать можно, а процитировать — нет | +| **Глубокая навигация** | Открываются сырые реплики: вызовы инструментов, вывод, рассуждения | Ответ можно проверить, а не верить пересказу | +| **Честная деградация** | Отказ семантики репортится явно | Буквальный fallback никогда не притворяется семантическим поиском | +| **Локальность по умолчанию** | Встроенные ONNX-эмбеддинги и локальный SQLite | Старт без ключа, сервера и аккаунта | +| **Recall со скоупом** | Фильтры по репозиторию, источнику и локальным календарным датам | Посторонние проекты не попадают в ответ | +| **Командные ответы** | Вопрос к локальной памяти коллеги — с его одобрения | Выстраданный контекст передаётся без раскрытия сырых сессий | + +## Где это окупается + +- **Онбординг сессии.** Свежая сессия стартует уже в контексте — и при нескольких + параллельных подписках, и при прыжках между агентами, и при возврате к задаче, которую + «когда-то обсуждали». +- **Баги и регрессии.** Прежде чем что-то чинить, агент спрашивает историю: *был ли уже этот + баг? как его чинили? почему поверили, что починили?* Рецидив перестаёт выглядеть свежим + багом — а фикс превращается из заплатки в раскопки внутри компонента. +- **Процедуры.** Достаточно один раз объяснить процесс — как читать трейс, как разложить + расход токенов по задаче — и любая следующая сессия воспроизведёт его без повторного + инструктажа. +- **Причинно-следственные связи.** Стоит сказать «давай изменим это решение» — и агент + поднимет момент, когда оно принималось: *«мы выбрали X ради совместимости с Y — прежде + чем менять, надо убедиться, что Y это переживёт»*. + +## Пять инструментов, один воркфлоу + +Интерфейс намеренно остаётся маленьким: + +| MCP-инструмент | Когда использовать | +|---|---| +| `recall_search(query)` | В памяти идея, а не формулировка | +| `expand_around(session_id, uuid)` | Якорь найден, нужны доказательства вокруг него | +| `step(session_id, uuid, direction)` | Нужна соседняя сырая реплика без нового поиска | +| `grep(pattern)` | Известны точная ошибка, символ, путь или идентификатор | +| `recent_sessions()` | Нужна самая свежая работа — и свежесть самого индекса | + +```mermaid +flowchart LR + Q["describe the old problem"] --> S["recall_search"] + S --> A["ranked anchor"] + A --> E["expand_around"] + E --> T["step next / prev"] + Q -. exact identifier .-> G["grep"] + R["what is current?"] --> RS["recent_sessions"] +``` + +Каждый поисковый инструмент принимает необязательный `source` (`claude` | `codex` | +`cursor`), `scope_cwd` — сузить выдачу до текущего репозитория (worktree схлопываются к +корню репо) — и локальные календарные даты (`on_date` либо `start_date` / `end_date`, плюс +`timezone` в формате IANA). Ранжированные якоря несут происхождение и человекочитаемый +таймстемп. `grep` сканирует **все** проиндексированные транскрипты по требованию — включая +подкапотные реплики (вывод инструментов, размышления), которые так и не стали поисковыми +чанками. Только по требованию: никакой проактивной инъекции контекста в каждый промпт. + +
+Посмотреть полный вызов + +```json +{ + "query": "why did refresh tokens conflict?", + "scope_cwd": "/work/keeper", + "source": "codex", + "start_date": "2026-05-01", + "end_date": "2026-06-30", + "timezone": "Europe/Moscow" +} +``` + +`recall_search` отвечает `{"anchors": [...], "degraded": null | "reason"}`. Если `degraded` +заполнен, провайдер эмбеддингов был недоступен и отработало только буквальное совпадение — +агент может сказать об этом прямо, а не принять лексический промах за пустую историю. + +
+ +## Быстрый старт + +Две части: Python-CLI (он же несёт MCP-сервер) и плагин, который подключает его к агенту. +По времени — около двух минут плюс первый прогон индексации. + +### 1. Установить CLI и построить индекс + +```bash +pipx install git+https://github.com/AbsoluteMode/session-recall +session-recall setup # one question (interaction language), then the first index +``` + +Ключ не нужен: без всякой настройки индексация идёт на встроенной CPU-модели — она +скачивается один раз и выбирается по языку общения. Первый прогон проходит всю историю +целиком: минуты на месяцы транскриптов, дальше — секунды. Скриптовая установка: +`session-recall setup --lang en --yes`. + +```console +$ session-recall index +indexed 2175 chunks from changed transcripts + +your history: 1053 sessions spanning 168 days, 40,037 searchable fragments + Claude Code 372 · Codex 680 · Cursor 1 + busiest: sidekey, trend_detection, glitch +``` + +Облачные эмбеддинги Voyage ранжируют заметно лучше встроенной модели; чтобы включить их, +достаточно экспортировать `VOYAGE_API_KEY` до индексации — см. +[Провайдеры эмбеддингов](#провайдеры-эмбеддингов). + +### 2. Подключить агентов + +`pipx` кладёт `session-recall` и `session-recall-mcp` в `~/.local/bin` — ровно туда, где их +ищут манифесты плагинов. + +
+Claude Code + +```text +/plugin marketplace add AbsoluteMode/session-recall +/plugin install session-recall +``` + +Дальше — новая сессия: MCP-серверы, скиллы и хук SessionStart подгружаются на старте +сессии, а не в момент установки. Хочется, чтобы агент довёл дело сам? Фраза +`set up session-recall` (или команда `/session-recall:setup`) — и он задаст вопросы +онбординга прямо в чате, сам выполнит команды и закончит health-проверкой и настоящим +поиском по вашей истории. + +
+ +
+Codex + +В репозитории лежит нативный [`.codex-plugin/plugin.json`](../.codex-plugin/plugin.json) — +готов лечь в локальное репо или личный маркетплейс; см. +[гайд по установке локального плагина](https://learn.chatgpt.com/docs/build-plugins#install-a-local-plugin-manually). +Codex также один раз попросит отревьюить свежеустановленные хуки через `/hooks`. + +
+ +
+Cursor + +Нужен Cursor 2.5+ (плагины появились именно там). Репозиторий подключается как маркетплейс: + +```bash +cursor-agent plugin marketplace add https://github.com/AbsoluteMode/session-recall.git +``` + +Дальше в Cursor Agent — `/add-plugin session-recall` и одно одобрение локального stdio +MCP-сервера, чтобы инструменты могли стартовать. Для разработки плагина запускается +`cursor-agent --plugin-dir /absolute/path/to/session-recall` вместо установки кешированной +копии. + +Cursor находится автоматически по стандартному пути данных macOS/Linux, и запущенным ему +быть не обязательно. Portable или кастомный профиль? База указывается напрямую: +`SESSION_RECALL_CURSOR_DB=/path/to/User/globalStorage/state.vscdb`. + +
+ +### 3. Проверить, что работает + +```bash +session-recall search "something you actually discussed last week" +``` + +Хиты со `score` означают, что семантический поиск жив. В агенте `claude mcp list` должен +показывать `session-recall ✔ Connected`, а вопрос о прошлой работе — триггерить +`recall_search`. Больше настраивать нечего: каждый плагин несёт стартовый хук своего хоста +и переиндексирует в фоне, так что общий индекс сам поспевает за всеми тремя историями. + +## Как это устроено + +```mermaid +flowchart TB + subgraph Sources["local history sources"] + CC["Claude Code JSONL"] + CX["Codex JSONL"] + CU["Cursor SQLite"] + end + + CC --> I["incremental indexer"] + CX --> I + CU -->|"consistent WAL snapshot"| I + I --> V["conversation surface → embeddings"] + I --> R["raw trace, kept local"] + V --> DB["SQLite · sqlite-vec KNN · FTS5"] + R --> DB + DB --> MCP["five on-demand MCP tools"] + MCP --> A["Claude Code · Codex · Cursor · any MCP client"] +``` + +Эмбеддится только «поверхность» разговора — промпты пользователя и текстовые ответы +ассистента. Вызовы инструментов, результаты, рассуждения и остальные данные трейса никогда +не отправляются провайдеру эмбеддингов, но остаются доступными по требованию через +`expand_around`, `step` и `grep`. Сайдчейны Claude и сессии порождённых сабагентов +пропускаются намеренно: это подкапотная механика, а не разговор. + +Cursor читается из его SQLite-хранилища через online backup API, так что живая WAL-база +снимается консистентно и без блокировки редактора. Его bubbles нормализуются в долговечные +content-addressed JSONL-снапшоты в директории данных — глубокая навигация продолжает +работать после того, как Cursor закрылся, обновился или был удалён. + +Индексация инкрементальная и дешёвая на живых транскриптах: они append-only, поэтому +неизменённые чанки матчатся по хешу содержимого, а их векторы переиспользуются — до +провайдера эмбеддингов доходят только новые реплики. Переезд rollout-файла Codex в архив +тоже переиспользует его векторы. Каждый файл индексируется в собственной транзакции; +сбойный файл логируется и повторяется на следующем прогоне, никогда не прерывая остальные. + +## Шпаргалка по CLI + +```bash +# Refresh every history, or one source +session-recall index +session-recall index --source cursor + +# Semantic search — unified by default, scopable to a repo +session-recall search "why did we choose the keeper service?" +session-recall search "deployment work" --source codex --scope /work/keeper + +# Local calendar dates, any IANA timezone (defaults to this computer's) +session-recall recent --date 2026-07-14 +session-recall search "deployment work" \ + --start-date 2026-07-14 --end-date 2026-07-16 \ + --timezone Asia/Yekaterinburg + +# Exact raw scan — no embedding call, caps at 100 matches by default +session-recall grep "invalid_grant" --limit 100 + +# Housekeeping +session-recall prune # drop rows for transcripts deleted from disk +session-recall health # the whole chain, verdict GREEN/AMBER/RED +``` + +`search`, `recent`, `grep` и `prune` принимают `--source claude|codex|cursor`; без него — +единая история. Фильтры дат включительные, любую из границ можно опустить. + +## Провайдеры эмбеддингов + +Ничего не прибито к одному вендору. `SESSION_RECALL_EMBED=` задаёт endpoint, +модель, размерность и reranker разом, потому что эти четыре параметра — не независимые +выборы: + +| Пресет | Где работает | Модель | Разм. | Реранкер | +|---|---|---|---:|---| +| `builtin-en` | **в комплекте, бесплатно** | `bge-small-en-v1.5` | 384 | — | +| `builtin-zh` | **в комплекте, бесплатно** | `bge-small-zh-v1.5` | 512 | — | +| `builtin-multi` | **в комплекте, бесплатно** | `paraphrase-multilingual-MiniLM-L12-v2` | 384 | — | +| `ollama` | **локально, бесплатно** | `nomic-embed-text` | 768 | — | +| `lmstudio` | **локально, бесплатно** | `nomic-embed-text-v1.5` | 768 | — | +| `voyage` | в облаке, нужен ключ | `voyage-4-large` | 1024 | `rerank-2.5` | +| `openai` | в облаке, нужен ключ | `text-embedding-3-large` | 1024 | — | + +Без пресета Session Recall берёт Voyage, когда есть `VOYAGE_API_KEY`, затем пробует найти +уже слушающий локальный сервер, а иначе запускает встроенную ONNX-модель — из коробки +работает всегда. Встроенный вариант следует за языком общения, выбранным на онбординге +(`SESSION_RECALL_LANG=en|zh|…`: маленький специалист по английскому или китайскому, иначе — +мультиязычная модель). Первое использование один раз скачивает модель в директорию данных +(70–240 МБ), дальше — CPU-инференс. Ранжирование заметно грубее облачного Voyage — это +стартовая точка, а не потолок. Локальные пресеты идут без реранкера, так что ранжирование — +только KNN + FTS. + +**Бесплатно и локально, от начала до конца:** + +```bash +ollama pull nomic-embed-text +export SESSION_RECALL_EMBED=ollama +session-recall index +``` + +**Свой endpoint** — любой сервер, говорящий на `/v1/embeddings` (llama.cpp, vLLM, +корпоративный gateway). Отдельные переменные всегда бьют пресет, так что их можно свободно +смешивать: + +```bash +export SESSION_RECALL_EMBED_PROVIDER=openai-compatible +export SESSION_RECALL_EMBED_BASE_URL=https://embeddings.internal/v1 +export SESSION_RECALL_EMBED_MODEL=your-model +export SESSION_RECALL_EMBED_DIM=1024 +``` + +**Другому эмбеддеру нужен свой индекс.** Векторные таблицы фиксированной ширины, поэтому +смена модели или размерности означает пересборку: удалить +`~/.local/share/session-recall/index.db` и заново прогнать `index`. Session Recall снимает +отпечаток пространства эмбеддингов у каждого проиндексированного файла и отказывается +смешивать пространства — семантический поиск выключается с явным сообщением, вместо того +чтобы возвращать обманчивое ранжирование. + +
+Заметки о лицензиях моделей + +`nomic-embed-text` — локальный дефолт, потому что он под Apache-2.0 и ставится одной +командой. Есть маленькие модели и сильнее — `jina-embeddings-v5-text-nano` для своего +размера набирает куда больше, — но они **CC BY-NC**: индексируя рабочую историю, такую +лицензию нарушал бы каждый, и никто бы ему об этом не сказал. Если использование +действительно некоммерческое — можно направить переменные выше на такую модель. Если +история не только на английском, `qwen3-embedding:0.6b` (Apache-2.0) справляется с +мультиязычностью куда лучше `nomic`. + +
+ +## Как индекс остаётся свежим + +Если установлен плагин, это уже решено: встроенный хук `SessionStart` запускает +`session-recall index` в фоне на каждом старте сессии, а инкрементальная индексация делает +это дёшево. + +
+MCP-сервер зарегистрирован вручную? Хук добавляется так + +В `~/.claude/settings.json`: + +```json +"hooks": { + "SessionStart": [ + { "hooks": [ { + "type": "command", + "command": "sr=/abs/path/.venv/bin/session-recall; pgrep -f \"$sr index\" >/dev/null 2>&1 || (VOYAGE_API_KEY=... \"$sr\" index >/tmp/sr-index.log 2>&1 &)" + } ] } + ] +} +``` + +Гард на `pgrep` не даёт прогонам накладываться; `( … & )` отцепляет процесс, чтобы старт +сессии не ждал. Хук на уровне хоста должен оставаться синхронным — шелл и так уводит +индексер в фон, а Codex игнорирует Claude-расширение `async`. Таймер `launchd`/cron тоже +подходит. + +
+ +## Командный режим — спросить историю коллеги + +Тот же recall, но между машинами: один раз создать пару с коллегой — и ваш агент сможет +спрашивать его агента о его прошлой работе. + +> **вы → агенту коллеги:** когда ты столкнулся с проблемой локального запуска X — как ты её +> решил? +> +> **его агент** *(после того как коллега одобрил ответ)*: закрепи конфиг на …, затем … — и +> проблема больше не возвращается. + +То, что раньше было тредом в Slack и полузабытым объяснением, становится одним вопросом и +одним обоснованным ответом. Сырую историю коллеги вы не видите никогда — только ответ, +который он одобрил. + +Приватность здесь — механика, а не политика: + +- вопросы и ответы ездят **end-to-end зашифрованными конвертами**; relay хранит слепые + блобы, прочитать которые не может; +- ответы собирает **изолированный read-only воркер**, ограниченный проектами, которые этому + контакту явно выдали (`share allow`); +- каждый кандидат в ответ проходит **сканер секретов**, а затем **явное одобрение + владельца** (Telegram-бот или локально `share approve`), прежде чем покинуть машину; +- контакт можно в любой момент поставить на паузу (`share pause`), пира — отозвать + (`share revoke`). + +Поиск по индексу пира не требует настройки эмбеддингов на вашей стороне: запрос едет +текстом, а воркер владельца эмбеддит его своим провайдером по своему индексу. + +
+Выбрать транспорт (один раз, до пейринга) + +У свежей установки **нет транспорта**, и она никогда не ходит на сервер, который вы не +выбирали. Relay слепой — всё, что он переносит, запечатано и подписано на клиентах, — так +что выбор транспорта — это координация между пирами, а не вопрос доверия. + +**Общая папка — ноль инфраструктуры.** Два аккаунта на одной машине или любая папка, +которую синхронизируют оба пира (Syncthing, Dropbox, NFS-маунт): + +```bash +export SESSION_RECALL_SHARE_TRANSPORT_DIR=~/Sync/sr-share # both peers, same folder +``` + +**Свой relay в LAN.** Одна машина его запускает, все остальные на него смотрят. Конверты в +любом случае зашифрованы end-to-end, но это голый HTTP — пускать его стоит только в сеть, +которой вы доверяете: + +```bash +session-recall share relay --port 8787 --host 0.0.0.0 # on the relay machine +export SESSION_RECALL_RELAY_URL=http://192.168.1.20:8787 # on every peer +``` + +**Свой relay в интернете.** Relay нарочно биндится на localhost и ждёт перед собой +TLS-терминатор (Caddy — вариант в две строки): + +```bash +session-recall share relay --port 8787 # binds 127.0.0.1 +``` + +```text +relay.example.com { + reverse_proxy 127.0.0.1:8787 +} +``` + +Затем на каждом пире: `export SESSION_RECALL_RELAY_URL=https://relay.example.com`. Relay +хранит только запечатанные блобы, а mailbox опустошается при fetch. +`SESSION_RECALL_RELAY_URL=none` намеренно держит установку в сетевом молчании. `export` +стоит положить в профиль шелла, чтобы агенты и таймеры тоже его видели. + +
+ +
+Пейринг и вопросы + +Пейринг — одноразовая церемония с короткой SAS-проверкой, дальше вопрос — одна команда: + +```bash +session-recall share init # once per device, both sides +session-recall share invite # you: prints a one-time code +session-recall share join # colleague: accepts it +session-recall share complete # you: finish the handshake +session-recall share trust # both: confirm the SAS matched, name the peer +session-recall share allow +session-recall share notify # owner side: worker + approval loop + +session-recall share ask "how did you fix the local X launch?" +session-recall share fetch # collect the answers +``` + +
+ +## Meta docs — память проекта, записанная текстом + +Сырой recall отвечает, *что говорилось*. Meta docs отвечает на то, что агенты реально +спрашивают посреди задачи: *этот баг уже чинили? как выполнить это действие? почему решили +именно так?* Ежедневная джоба отдаёт диалог каждой сессии — сообщения пользователя и +финальные ответы, никогда шум инструментов — агенту-дистиллятору, который ведёт +Markdown-записи в выбранном Git-репозитории: + +- `/bugs/` — реально починенные баги: как баг распознали, как диагностировали, чем + чинили и как доказали, что починено; +- `/actions/` — процедуры, шаг за шагом, написанные так, чтобы агент, которого + попросили снова, справился по одной записи; +- `/decisions/` — спорные выборы: что решили, почему именно так и что отвергли; +- `USER/` — глобальная карта: где живёт ваша информация и *как её найти* (команды поиска и + места хранения — никогда сами значения). + +```bash +session-recall metadocs init ~/meta-docs --from-today # memory starts now +session-recall metadocs run # one pass now +session-recall metadocs enable # daily job: launchd (macOS) / systemd user timer (Linux) +session-recall metadocs status +session-recall metadocs index-history --days 30 # opt-in: distill the past, once +``` + +Весь мир дистиллятора — четыре MCP-глагола: `search / create / edit / delete`, а несущие +правила — это механика сервера, а не просьбы в промпте: `create` отклоняется, пока агент не +сделал `search` (дедупликация обязательна), записи сканируются на секреты до того, как хоть +байт коснётся диска, а `delete` требует причину. Прогоны инкрементальные, и каждый +изменённый проект получает свой локальный коммит — ревью это дифф, откат это revert, а +поделиться памятью с командой — значит просто запушить репо в приватное место. Ничего не +пушится без явного `--push`; движок и модель берутся только из конфига +(`init --engine claude-cli|codex --model …`) — ничего не выбирается втихую. + +## Приватность — жёсткий инвариант + +Это публичный репозиторий. **В него попадает только код.** Рантайм-данные живут в +`~/.local/share/session-recall/`, вне дерева репо — их физически нельзя закоммитить. + +| Остаётся на вашей машине | Уходит только по вашему выбору | +|---|---| +| Оригинальные транскрипты Claude Code и Codex | Текст поверхности разговора → настроенный вами облачный эмбеддер | +| SQLite-хранилище Cursor и его нормализованные снапшоты | Явно одобренный ответ командного режима | +| Вызовы инструментов, вывод, рассуждения — весь сырой трейс | Ничего — на встроенном/локальном пути эмбеддингов | +| SQLite-индекс и сохранённые векторы | | + +- API-ключи — только переменные окружения; `.gitignore` блокирует `.env`. +- Тесты используют синтетические фикстуры и никогда — реальный кусок сессии. +- Встроенный провайдер держит весь путь индексации на устройстве. Если выбран облачный — + это должен быть тот, кому вы доверяете текст поверхности своих транскриптов. + +## Диагностика + +Начинать отсюда: команда проверяет всю цепочку и выходит с ненулевым кодом, когда что-то +действительно сломано, — поэтому годится и для таймера: + +```console +$ session-recall health +[ok ] Freshness 2 minutes behind +[warn] Embedder responded in 5828 ms + → slow provider will make indexing crawl +[ok ] Vector space builtin/BAAI/bge-small-en-v1.5/384 +[ok ] Corpus 1054 sessions (claude 373, codex 680, cursor 1) +[ok ] Sources claude, codex, cursor present + +verdict: AMBER (voyage/voyage-4-large, index at ~/.local/share/session-recall/index.db) +``` + +Freshness сравнивает самый свежий транскрипт на диске с самой свежей репликой в индексе, +поэтому индексер, который запускается на каждой сессии и каждый раз падает, всё равно +отображается отстающим — ровно тот сбой, который иначе невидим. + +| Симптом | Причина / что делать | +|---|---| +| `recall_search` отвечает с заполненным `degraded` | Провайдер эмбеддингов недоступен — отработало только буквальное совпадение. Результаты настоящие, но промах ничего не доказывает. | +| `degraded` говорит «embedder changed» | Индекс построен в другом пространстве эмбеддингов. Прогнать `session-recall index`, чтобы переэмбеддить; до этого семантическое ранжирование выключено — намеренно. | +| Индексер логирует `HTTP code 403` с HTML-телом | Дело не в ключе: WAF режет ваш IP (типично для VPN и датацентровых выходов). Тот же 403 приходит и вовсе без ключа. Вывести трафик другим путём или сменить провайдера. | +| `Missing dependencies for SOCKS support` | В окружении задан SOCKS-прокси, а `PySocks` в этом venv не установлен. | +| `recent_sessions` показывает старый таймстемп | Индексер давно не завершался успешно. Запустить `session-recall index` руками и прочитать вывод. | +| Cursor живёт в кастомном профиле | Задать `SESSION_RECALL_CURSOR_DB=/path/to/User/globalStorage/state.vscdb`. | + +## Разработка + +```bash +git clone https://github.com/AbsoluteMode/session-recall.git +cd session-recall +python -m venv .venv +.venv/bin/pip install -e ".[dev]" +.venv/bin/pytest -q +``` + +Чтобы зарегистрировать MCP-сервер вручную, минуя плагин: + +```bash +claude mcp add session-recall --scope user -- /absolute/path/.venv/bin/session-recall-mcp +``` + +Инженерные обоснования и инварианты живут в [`docs/decisions/`](decisions/). Начать стоит +с этих: + +- [Единый индекс Claude Code + Codex](decisions/2026-07-10-unified-claude-codex-index.md) +- [Cursor как долговечный сырой источник](decisions/2026-08-03-cursor-durable-raw-source.md) +- [Recall со скоупом проекта](decisions/2026-06-26-recall-project-scope.md) +- [Security gate для P2P-шаринга](decisions/2026-07-30-p2p-sharing-v1-security-gate.md) +- [Meta docs — живая память проекта](decisions/2026-07-31-metadocs-living-project-memory.md) + +## Роадмап + +- **Облачный / командный индекс** — один общий индекс на команду вместо копий на каждой + машине. Честный открытый вопрос: тот, кто ищет, должен заэмбеддить запрос, так что общее + векторное пространство подразумевает общий путь эмбеддингов. +- **Bypass одобрения по контактам** — пропуск поштучного одобрения для полностью доверенных + пиров; сегодня каждый ответ одобряется явно. +- **Больше историй** — транскрипты других агентов, помимо Claude Code, Codex и Cursor. + +## Контрибьютинг + +Issues, улучшения документации, адаптеры хостов и переводы — добро пожаловать. Фикстуры — +только синтетические; реальные транскрипты, индексы, эмбеддинги и креды не коммитятся +никогда. + +
+
+ Хватит пересобирать контекст. Продолжи работу. +
+
+ Начать +  ·  + Лицензия MIT +
+ + + Session Recall — ваши кодинг-агенты забывают, Session Recall — нет. Одна память для Claude Code, Codex и Cursor. + + +
+
+ +Общая семантическая память для Claude Code, Codex и Cursor.
+Найди старое решение по смыслу. Открой сырые доказательства. Продолжи работу. + +
+
+ +[![MIT License](https://img.shields.io/badge/license-MIT-b8f84f?style=flat-square&labelColor=11120f)](../LICENSE) +[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-b8f84f?style=flat-square&labelColor=11120f)](../pyproject.toml) +[![MCP native](https://img.shields.io/badge/MCP-native-b8f84f?style=flat-square&labelColor=11120f)](../src/session_recall/server.py) +[![Tests](https://img.shields.io/github/actions/workflow/status/AbsoluteMode/session-recall/test.yml?branch=main&style=flat-square&label=tests&labelColor=11120f)](https://github.com/AbsoluteMode/session-recall/actions/workflows/test.yml) + +
+ +[English](../README.md) · Русский · [Español](../README.es-ES.md) · [中文](../README.zh-CN.md) + +*Эталонная версия — английская; переводы могут отставать.* + +