Octobrain 0.9.0: El conocimiento se vuelve multijugador

Tu equipo ya escribió la documentación. El runbook de despliegue, las reglas de "nunca toques esto sin una migración", las notas ganadas a pulso sobre por qué el servicio de pagos reintenta como lo hace. Viven en una wiki que nadie lee, en un mensaje fijado de Slack y en la cabeza de tres personas. Cada nueva sesión de agente — y cada nuevo compañero — empieza desde cero y lo reaprende por las malas.

0.7.0 hizo que la memoria de cada agente se mantuviera sola — sleep consolidation, decay, plegado anclado a objetivos. Eso iba sobre una memoria, volviéndose más lista por su cuenta.

0.9.0 va sobre la otra mitad del problema: conocimiento que no debería reaprenderse en cada máquina. Hace que la capa de conocimiento de Octobrain sea compartible — versionada en git, acotada por nombre, sincronizada automáticamente — y afina el recall para que el hecho correcto gane más a menudo. (También plegamos aquí los cimientos de 0.8.0.)


Knowledge boxes respaldadas por git — Entrega conocimiento como código

Este es el titular. Una knowledge box es un repositorio git lleno de markdown — reglas, docs, playbooks, notas de arquitectura — que Octobrain clona, indexa y mantiene sincronizado. Cúralo una vez, versiónalo como código, y cada agente en cada máquina suscrita obtiene el mismo conocimiento buscable.

Hay dos variantes, y cubren los dos casos reales:

Local al proyecto — solo commitea un directorio .box/. Vuelca el conocimiento vivo de tu proyecto en .box/ en la raíz del repo. Cualquiera que clone el repo y ejecute Octobrain lo obtiene indexado automáticamente — sin comando extra, sin remoto que configurar. Viaja con el código porque está en el código.

Remota — suscríbete a una box compartida. Apunta Octobrain a una URL de git y hace un shallow-clone de la box y la indexa bajo un scope:

# Subscribe to your org's shared knowledge box
octobrain box import https://github.com/acme/engineering-knowledge

# Index it at the global scope — visible in every project on this machine
octobrain box import https://github.com/acme/conventions --global

# Pull + smart-reindex every subscribed box and the local .box/
octobrain box sync

# See what you're subscribed to, or drop one
octobrain box list
octobrain box remove github.com/acme/engineering-knowledge

Cada chunk proveniente de una box recibe un source URI estable box://<host>/<org>/<repo>/..., así que está agrupado, es atribuible y se puede eliminar limpiamente — quita una box y todas sus filas se van con ella. Las re-sincronizaciones son basadas en hash e inteligentes: solo los archivos cambiados se vuelven a embeber, así que un sync sobre una box sin cambios cuesta casi nada.

Y bajo MCP, ni siquiera llamas a sync — el servidor descubre el .box/ de cada proyecto, sondea por una box de organización, y refresca los remotos suscritos en segundo plano, una vez por sesión, con single-flight para que nunca bloquee una petición. Tu agente simplemente empieza a encontrar el conocimiento del equipo en sus búsquedas.

[knowledge]
chunk_size = 1200   # boxes index through the same chunker as everything else

El punto: el runbook deja de vivir en la cabeza de alguien. Vive en un repo, es buscable por significado, y aparece en el contexto de tu agente en el momento en que es relevante — para todos.


Los scopes reemplazan a los hashes de proyecto — Memoria con un nombre

Hasta ahora, Octobrain aislaba la memoria por proyecto haciendo hash de la URL de tu remoto Git en un directorio SHA-256 opaco. Funcionaba, pero no podías leerlo, no podías apuntarle, ni podías compartirlo.

0.9.0 reemplaza eso con scopes: strings legibles, derivados automáticamente de tu remoto Git (o de la ruta local como fallback), que de verdad puedes ver y a los que puedes apuntar.

# Auto-detected from the git remote — nothing to configure
octobrain memory remember "rate limiting approach"

# Or target a scope explicitly
octobrain memory remember "rate limiting approach" --scope acme/payments

# Store something that should be visible everywhere, not just this project
octobrain memory memorize --title "Team convention: trunk-based, no long-lived branches" \
  --content "..." --global

Los scopes son lo que hace coherentes a las boxes: una box remota se enlaza por defecto a un scope de nivel organización, un .box/ se enlaza al scope de su proyecto, y --global pone conocimiento o memoria frente a cada proyecto de la máquina. Un único sistema de nombres, usado en todas partes — las rutas de almacenamiento, los filtros de búsqueda, las stats y los logs ahora se leen en lenguaje claro en vez de hashes.

Este es el único cambio que rompe y que te afecta: la flag --project y los parámetros de herramienta project ahora son --scope. Mismo comportamiento, nombre legible. Ver Actualizar.


Recall multi-consulta que de verdad vale la pena — Fusión RRF

Octobrain ha soportado desde hace tiempo la búsqueda multi-consulta — descomponer una pregunta difusa en varias más afiladas y buscarlas juntas:

octobrain memory remember "authentication" "session security" "jwt expiry"

El problema era la fusión. La lógica vieja conservaba el mejor score individual de cada recuerdo y le sumaba un 10% fijo por cada coincidencia extra — mezclando scores por consulta calculados en escalas incomparables. Un recuerdo que rankeaba sólidamente en las tres consultas podía perder contra un acierto puntual en una sola consulta. La descomposición en realidad no ayudaba; a veces perjudicaba.

0.9.0 reemplaza eso con Reciprocal Rank Fusion — la misma fusión basada en rangos con k=60 que Octobrain ya usa para combinar BM25 y búsqueda vectorial in-store. RRF es basada en rangos y libre de escala: un recuerdo que se posiciona bien en muchas consultas gana a uno que solo destaca en una sola consulta. Ahora la descomposición de consultas vale la pena como debería. Es pura matemática — sin LLM, sin config extra — y rrf_fuse es una función pequeña, con tests unitarios, en la que puedes confiar.


El recall sabe qué es lo vigente — Ranking consciente de Supersedes

Los hechos se vuelven obsoletos. Decidiste usar Postgres, y seis semanas después te pasaste a SQLite. Ambas notas están en memoria, ambas coinciden con "qué base de datos estamos usando" — y la respuesta vieja no tiene por qué ganar.

Octobrain siempre ha tenido un tipo de relación Supersedes, pero estaba definido, parseado y almacenado — y luego nunca se leía en la recuperación. Una capacidad construida, muerta sin usar.

0.9.0 la conecta. Cuando una arista activa X Supersedes M marca un resultado como desactualizado, la recuperación baja suavemente en el ranking al obsoleto (×0.1 — nunca se borra), así que el hecho actual sube a lo más alto mientras el historial sigue siendo consultable. Dos salvaguardas deliberadas:

  • Las aristas se respetan, nunca se auto-crean. Detectar una contradicción genuina requiere juicio semántico, y Octobrain mantiene eso fuera del hot path a propósito. Tú (o tu agente) marcas el supersede; la recuperación lo respeta. Por MCP, la herramienta knowledge ahora guía activamente a los agentes a crear enlaces supersedes cuando actualizan un hecho.
  • Best-effort. Un fallo al buscar la relación deja el resultado intacto en vez de descartarlo. En el peor caso vuelves al comportamiento antiguo, nunca peor.

La decisión actual gana; el rastro de vuelta a la vieja sobrevive.


Recall acotado en el tiempo — Filtros temporales y de relevancia

"¿Qué decidí la semana pasada?" es una pregunta que los humanos hacen constantemente, y la búsqueda semántica por sí sola la responde mal — la relevancia no sabe nada del tiempo.

MemoryQuery ya soportaba created_after / created_before (empujados directamente hasta el índice created_at) y min_relevance — pero la herramienta MCP remember nunca los poblaba. Otra capacidad construida pero muerta. 0.9.0 expone ambos:

  • created_after / created_before — fecha ISO-8601 (2026-06-01) o timestamp RFC3339 completo. Una fecha sola significa medianoche UTC; cualquier cosa imparseable simplemente omite el filtro en vez de dar error.
  • min_relevance — un suelo por llamada para la calidad de los resultados, que anula el valor por defecto global.

La herramienta le dice al agente que calcule la ventana él mismo — ya conoce la fecha de hoy — así que no hay un frágil parseo de fechas en lenguaje natural enterrado en la herramienta. "Qué decidí la semana pasada" se convierte en un escaneo de rango indexado limpio, que es exactamente el tipo de pregunta anclada en el tiempo que LongMemEval mide.


Le pusimos números — Benchmarks de recuperación BEIR

0.7.0 añadió LongMemEval para la memoria a largo plazo. 0.9.0 añade un harness de recuperación BEIR para poder puntuar la capa de ranking — embedding + fusión BM25 + reranking — sobre datasets estándar con qrels reales, totalmente local, sin juez LLM.

Usando el embedder local por defecto (bge-small-en-v1.5, 384-dim, 33M params), medido como nDCG@10:

Dataset Octobrain vector Octobrain hybrid BM25 bge-small-en-v1.5
SciFact (5.2K docs, 300 q) 0.722 0.742 0.665 0.713
NFCorpus (3.6K docs, 323 q) 0.341 0.363 0.325 0.343

La columna solo-densa reproduce los números BEIR publicados del embedder — eso es el harness validándose a sí mismo. La columna hybrid (BM25 + vector fusionados con RRF, el valor por defecto de Octobrain) añade +2 nDCG@10 sobre el embedding pelado y le gana al BM25 clásico en ambos datasets. Reprodúcelo tú mismo: cd benches && bash scripts/run_retrieval.sh. Las afirmaciones de recall de arriba son afirmaciones que podemos sostener.


Por debajo del capó

Los cambios más silenciosos que mantienen las cosas rápidas y predecibles a medida que el corpus crece:

  • Dos nuevas perillas de búsqueda, en config. search.similarity_threshold (por defecto 0.3) es el suelo global de relevancia para consultas semánticas; search.max_results (por defecto 50) es un techo duro que ningún llamador puede superar. Ambos están ahora conectados de punta a punta en vez de anunciados-pero-ignorados.

    [search]
    similarity_threshold = 0.3   # min relevance for semantic queries (0.0–1.0)
    max_results          = 50    # hard ceiling on results from any search
    
  • Timeouts para embeddings y reranker. Una llamada a un proveedor lenta o colgada ya no atasca una búsqueda — timeout_secs (por defecto 30) acota ambos, con un ejemplo en la config del reranker.

  • Inicialización en segundo plano. Las tareas pesadas de arranque se lanzan fuera del hot path, así que la primera petición tras el arranque no paga el calentamiento del índice.

  • Descubrimiento automático de scope y override de config (desde 0.8.0). Octobrain deriva tu scope del directorio de trabajo automáticamente, y OCTOBRAIN_CONFIG_PATH te deja apuntar a un archivo de config personalizado — útil para CI, contenedores y setups multi-tenant. El locking de proyecto y de rol se desacoplaron para que las sesiones no relacionadas dejen de competir.

  • Docker multi-arch. El release ahora construye y publica imágenes multi-arch, así que arm64 (Apple Silicon, Graviton) y amd64 son ambos de primera clase.

  • Matemática de ranking endurecida. Arreglos al límite de candidatos del reranker, a la normalización de RRF, a la exclusión de memoria archivada y una salvaguarda PRF afinan los casos límite en el nuevo camino de recuperación. La búsqueda de knowledge ahora también devuelve la sección padre completa, no un fragmento truncado.

No echarás mano de la mayoría de estos directamente. Ese es el punto.


Cómo se ve 0.9.0 en la práctica

Un nuevo ingeniero se une al equipo de pagos:

  1. Clona el repo. Su directorio .box/ — runbook de despliegue, reglas de "no te saltes la capa de idempotencia", las notas de por-qué-reintentamos — se indexa automáticamente la primera vez que su agente se ejecuta. Cero setup.
  2. Su máquina se suscribe a la box de la organización (octobrain box import …), así que las convenciones de toda la empresa aparecen bajo un scope global, en cada proyecto.
  3. Le hace a su agente una pregunta vaga — "¿cómo manejamos los fallos de webhook aquí?" La descomposición multi-consulta la abre en abanico, RRF fusiona los rangos, y la entrada del runbook del equipo aterriza en lo más alto.
  4. Una decisión cambió el mes pasado — el equipo dejó una cola que solía recomendar. La nota vieja está marcada como superseded, así que el enfoque actual gana y el historial sigue a una sola consulta de distancia.
  5. Pregunta "¿qué cambiamos la semana pasada?" — filtro temporal, rango indexado limpio, sin ruido de hace seis meses.

Nadie volvió a explicar el sistema. El conocimiento ya estaba escrito — Octobrain solo lo hizo encontrable, vigente y compartido.


Actualizar

Desde 0.7.x o 0.8.x, la migración es pequeña.

Config — --project pasa a ser --scope. Este es el único cambio que rompe. Donde sea que pasaras --project <id> en la CLI, o un parámetro project a una herramienta MCP, usa --scope <name> en su lugar. La gran mejora: el valor ahora es un string legible (auto-derivado de tu remoto Git), no un hash — así que la mayoría de las veces puedes quitar la flag por completo y dejar que el descubrimiento se encargue.

octobrain memory remember "api design" --scope acme/web   # was: --project <hash>

Storage — nada que hacer. La columna scope y su índice se añaden en el primer arranque, y los recuerdos existentes migran en su sitio. Sin pasos manuales, sin downtime.

La nueva config es aditiva. search.similarity_threshold, search.max_results, y el timeout_secs de embedding/reranker vienen todos con valores por defecto que funcionan. Añádelos solo si quieres ajustarlos.

Las knowledge boxes son opt-in. Nada cambia hasta que commitees un directorio .box/ o ejecutes octobrain box import. Las fuentes ya indexadas quedan intactas.

Clientes MCP — refresca tu lista de herramientas. remember ahora acepta created_after, created_before y min_relevance; memorize toma --global. Sin renombrados en las cinco herramientas centrales — solo nuevos campos opcionales que tu agente puede usar.

Código, binarios e imágenes Docker en github.com/muvon/octobrain. Si algo se rompe, abre un issue — los leemos.