Vi a un agente incorporarse a un servicio de pagos desconocido cuatro veces en una sola tarde. El mismo servicio, cuatro sesiones nuevas, cuatro veces rastreó el manejador de webhooks desde cero, volvió a descubrir que los reintentos eran idempotentes gracias a una clave de deduplicación, y volvió a concluir que el prefijo legacy_ en tres funciones significaba "no tocar". Cada sesión hizo buen trabajo. Cada sesión lo tiró a la basura cuando se cerró la ventana de contexto.

El agente tenía un mapa. Podía buscar en el código y encontrar cualquier cosa. Lo que no tenía era una memoria. Así que seguía redibujando conclusiones que ya había sacado, porque un mapa te dice dónde están las cosas — no te dice qué decidiste sobre ellas el martes pasado.

De eso trata este artículo. La búsqueda semántica de código y la memoria persistente resuelven dos mitades del mismo problema, y casi todo el mundo usa exactamente una. Aquí explico por qué quieres ambas, y cómo hacer que se combinen.


Dos modos de fallo, una mitad ausente

Dale a un agente búsqueda sin memoria y obtienes el bucle de las cuatro veces por tarde. Puede localizar el manejador de webhooks al instante, pero el significado de ese manejador — por qué está estructurado así, qué es seguro cambiar, cuál es la ruta heredada — no vive en ningún lugar duradero. Cada sesión lo rededuce. El agente es competente y amnésico.

Dale a un agente memoria sin búsqueda y obtienes el fallo opuesto, que es más silencioso y peor. Recuerda la conclusión — "la clave de deduplicación en el manejador de webhooks hace que los reintentos sean idempotentes" — pero seis semanas después no puede relocalizar el código al que se refiere esa conclusión. El manejador se refactorizó, la función se renombró, el archivo se dividió en dos. La memoria es ahora una frase segura que apunta a la nada. El agente confía en ella y actúa sobre un hecho que ya no es cierto.

Las dos herramientas cubren los puntos ciegos de la otra:

Encuentra código Recuerda decisiones Sin la otra
Búsqueda semántica no Rededuce el contexto en cada sesión
Memoria persistente no Recuerda conclusiones que no puede relocalizar

Un mapa y una memoria. Usa una, te falta la mitad.

En Muvon publicamos ambas mitades como servidores MCP de código abierto — Octocode para el mapa, Octobrain para la memoria — y el anfitrión que los ejecuta juntos, Octomind. Ambos son Apache-2.0. Este artículo es la guía de flujo de trabajo para usarlos como pareja. Si quieres los análisis a fondo, la búsqueda semántica de código de Octocode y la presentación de Octobrain cubren cada herramienta por separado. Las referenciaré en lugar de repetirlas.


Qué expone realmente cada lado

Primero el fundamento, porque la combinación solo tiene sentido si conoces la superficie real de herramientas. Estas son las herramientas MCP, no una lista de deseos.

Octocode indexa tu repositorio con análisis AST de tree-sitter — símbolos reales, no fragmentos de texto plano — y expone cuatro herramientas al agente:

Herramienta Qué hace
semantic_search Búsqueda orientada al recall por concepto o comportamiento. "Dónde se maneja la autenticación", "código que reintenta peticiones fallidas". Encuentra código aunque los nombres no coincidan con tus palabras.
structural_search Coincidencia de patrones AST y búsqueda exacta de símbolos. Precisa y barata cuando conoces el nombre, la cadena o los sitios de llamada.
view_signatures Extrae firmas de funciones y definiciones de tipos sin los cuerpos — la forma más barata de mapear un archivo antes de leerlo.
graphrag Consultas al grafo de conocimiento sobre imports, calls, implements, extends. "Qué depende del módulo de pagos".

El índice tiene alcance de proyecto y es local-first. Lo construyes una vez con octocode index y se mantiene actualizado. La idea que conviene retener: Octocode responde "¿dónde está esto y cómo está conectado?" — y solo refleja el código tal como existe ahora mismo.

Octobrain le da al agente memoria a largo plazo, con alcance por proyecto según la URL normalizada del remoto de Git (host/org/repo). Cuatro herramientas MCP:

Herramienta Qué hace
memorize Almacena un insight, una decisión o un hecho. Acepta title, content, un memory_type (architecture, decision, bug_fix, security, …), importance, tags, related_files y related_to[] para enlaces inline a otras memorias.
remember Búsqueda semántica sobre las memorias almacenadas. Devuelve automáticamente los vecinos del grafo a 1 salto. Admite created_after / created_before para consultas temporales.
forget Elimina una memoria por memory_id o por consulta — irreversible, requiere confirm=true.
knowledge Una base de conocimiento separada para indexar y buscar documentos y URLs externas (search, store, read, match, delete).

La propia descripción de memorize le dice al agente que llame primero a remember para evitar duplicados, y que marque los hechos como user_confirmed (alta importancia) frente a agent_inferred (más baja). Octobrain autoenlaza memorias semánticamente similares al estilo Zettelkasten, y admite relaciones supersedes para que un hecho corregido se clasifique por encima del obsoleto que reemplaza — el antiguo sigue consultable para el historial. Retén esto: Octobrain responde "¿qué decidimos sobre esto, y cuándo?" — y es el único lado que persiste entre sesiones.

Fíjate en la simetría. Octocode conoce el código actual pero olvida cada conversación. Octobrain recuerda cada conversación pero no conoce el código. Ningún campo — related_files en una memoria, una ruta de archivo en un resultado de búsqueda — tiene sentido sin la otra herramienta para resolverlo.


Cómo se combinan: el bucle

Las dos herramientas no solo coexisten — forman un bucle, y el bucle es toda la técnica. Cuatro movimientos:

1. Buscar para localizar. El agente no sabe dónde vive la lógica de idempotencia del webhook. Llama a semantic_search("webhook retry idempotency dedupe"). Octocode devuelve el manejador y la comprobación de la clave de deduplicación. El agente ya tiene una ubicación y código actual.

2. Memorizar la decisión, no la ubicación. Tras razonar sobre ese código — confirmar que los reintentos son idempotentes, identificar la ruta heredada, acordar contigo un límite de alcance — el agente llama a memorize. Lo crucial: lo que almacena es la conclusión y el razonamiento, etiquetados con related_files, no una copia del código ni un número de línea. memory_type = "architecture", importance alta si tú lo confirmaste. La decisión ya es duradera.

3. Recordar la próxima vez. Nueva sesión, ventana de contexto vacía. Antes de tocar nada, el agente llama a remember("webhook payments idempotency"). Octobrain devuelve la decisión almacenada más sus vecinos a 1 salto — la nota de seguridad relacionada, la advertencia enlazada sobre la ruta heredada. El agente empieza la sesión sabiendo ya lo que antes costó cuatro sesiones averiguar.

4. Volver a buscar para verificar. Este es el movimiento que la gente se salta, y es lo que mantiene honesta a la memoria. La memoria dice "la idempotencia vive en el manejador de webhooks". Antes de actuar sobre ello, el agente llama a semantic_search o view_signatures sobre los related_files para confirmar que el código aún coincide con la memoria. Si el manejador se refactorizó y la clave de deduplicación se movió, la nueva búsqueda revela el desajuste. El agente actualiza la memoria — memorize con un enlace supersedes a la antigua — y continúa sobre la verdad actual.

        ┌─────────────────────────────────────────────┐
        │                                             │
        ▼                                             │
  semantic_search ──► razonar ──► memorize ──► remember
   (localizar)      (decidir)   (almacenar    (próxima sesión:
        ▲                        la decisión)  recargarla)
        │                                          │
        └────────── volver a buscar para verificar ◄┘
            (¿el código aún coincide con la memoria?)

El mapa mantiene la memoria anclada a código real. La memoria evita que el agente rededuzca lo que ya sabe. Búsqueda sin los pasos 2 y 3 es el agente amnésico. Memoria sin los pasos 1 y 4 es el agente seguro-pero-equivocado. El bucle es ambas mitades haciendo su trabajo.

La razón por la que el paso 4 importa tanto: una memoria es una afirmación sobre el código en un momento dado, y el código se mueve. Octobrain puede clasificar un hecho corregido con supersedes por encima del obsoleto, pero algo tiene que notar primero la obsolescencia. Octocode es ese algo. La nueva búsqueda es el contraste de realidad del agente frente a su propia memoria.


Registrar ambos servidores para que el agente los tenga juntos

Ninguna herramienta ayuda si el agente solo puede alcanzar una. Todo el sentido está en tener el mapa y la memoria disponibles en la misma sesión para que el agente ejecute el bucle sin que tú hagas de intermediario. Aquí está el cableado.

Octomind declara servidores MCP en su configuración bajo [[mcp.servers]]. Los servidores integrados (core, runtime, agent, orchestration) siempre están; añades Octocode y Octobrain como dos servidores stdio:

[[mcp.servers]]
name = "octocode"
type = "stdio"
command = "octocode"
args = ["mcp", "--path=."]
timeout_seconds = 240
tools = []

[[mcp.servers]]
name = "octobrain"
type = "stdio"
command = "octobrain"
args = ["mcp"]
timeout_seconds = 60
tools = []

tools = [] significa "expón todas las herramientas de este servidor". --path=. limita Octocode al repositorio actual; Octobrain se limita a sí mismo automáticamente por la URL normalizada del remoto de Git (host/org/repo), así que las memorias que un agente almacena en un proyecto quedan aisladas de otro. El timeout de Octocode es generoso porque la primera búsqueda tras un índice puede traer un modelo de embeddings local a la caché; el de Octobrain es más corto porque las consultas de memoria son baratas.

Si usas Claude Code en lugar de Octomind, son los mismos dos servidores, registrados a la manera de la CLI:

claude mcp add octocode  -- octocode mcp --path .
claude mcp add octobrain -- octobrain mcp

Claude Desktop, Cursor y Windsurf aceptan el mismo bloque JSON mcpServers — una entrada por servidor. La mecánica está documentada por herramienta; lo único que importa aquí es ambos, en el mismo agente. Un agente con Octocode pero sin Octobrain es un mapa sin memoria. Un agente con Octobrain pero sin Octocode es una memoria sin mapa. Quieres la pareja.

Un requisito previo antes de la primera ejecución: indexa el repositorio. octocode index en la raíz del proyecto. Octobrain no necesita nada — arranca vacío y se llena a medida que el agente almacena decisiones. Si tu repositorio es lo bastante grande como para que indexar sea una operación seria, el artículo hermano sobre indexar un código base grande para búsqueda semántica localmente cubre cómo hacerlo sin claves de API.


Ejemplo práctico: incorporar un agente a un servicio desconocido

Aquí está el servicio de pagos del principio del artículo, ejecutado correctamente esta vez. Observa cómo el bucle hace el trabajo que cuatro sesiones amnésicas no pudieron.

Sesión 1 — arranque en frío, construir el mapa y las primeras memorias.

El agente nunca ha visto este servicio. Empieza mapeando, no leyendo:

view_signatures("src/payments/**/*.rs")
  → webhook.rs: handle_webhook, verify_signature, dedupe_key
  → charge.rs:  create_charge, capture, legacy_settle, legacy_refund, legacy_void

Tres funciones con prefijo legacy_ saltan a la vista. El agente pregunta; tú confirmas que son una ruta de liquidación obsoleta que un cron job todavía llama y que nadie tiene permitido "limpiar". Esa es una decisión que vale la pena conservar — así que va a la memoria, no al vacío:

memorize(
  title: "legacy_ settlement path in charge.rs is load-bearing",
  content: "legacy_settle/refund/void are called by the nightly
            reconciliation cron. Deprecated but NOT dead. Do not remove
            or refactor without checking cron/reconcile.rs.",
  memory_type: "architecture",
  importance: 0.9,
  source: "user_confirmed",
  tags: ["payments", "legacy", "cron"],
  related_files: ["src/payments/charge.rs", "src/cron/reconcile.rs"]
)

Luego rastrea la idempotencia con semantic_search("webhook retry idempotency"), aterriza en dedupe_key, razona sobre ella y almacena una segunda memoria — memory_type: "architecture", enlazada a la primera con related_to para que ambas afloren juntas más tarde. La sesión termina. Dos memorias persisten; el índice de código persiste. El razonamiento de la tarde ya no es desechable.

Sesión 2 — arranque en caliente, tres semanas después.

Ventana de contexto nueva. En lugar de volver a rastrear, el agente carga primero lo que sabe:

remember("payments webhook legacy settlement")
  → "legacy_ settlement path is load-bearing" (importance 0.9, CONFIRMED)
  → vecino a 1 salto: "webhook idempotency via dedupe_key"

Una llamada, y el agente empieza donde terminó la sesión 1 — incluyendo el vecino enlazado que no pidió explícitamente. Ahora el paso de verificación. La memoria apunta a src/payments/charge.rs, así que antes de confiar en ella el agente vuelve a buscar:

view_signatures("src/payments/charge.rs")
  → create_charge, capture, settle_v2, refund_v2, void_v2

Las funciones legacy_ ya no están. Alguien lanzó settle_v2 y eliminó la ruta heredada. La memoria está ahora obsoleta — y como el agente verificó contra el mapa en lugar de confiar a ciegas en la memoria, lo detectó. Reemplaza la decisión antigua en lugar de actuar sobre un hecho caducado hace tres semanas:

memorize(
  title: "settlement path migrated to settle_v2",
  content: "legacy_settle/refund/void removed in the v2 migration.
            cron/reconcile.rs now calls settle_v2. Earlier 'do not remove'
            note no longer applies.",
  memory_type: "architecture",
  related_to: [{ target_id: <old_memory_id>, relationship_type: "supersedes" }],
  related_files: ["src/payments/charge.rs", "src/cron/reconcile.rs"]
)

Las futuras llamadas a remember clasifican ahora el hecho corregido por encima del obsoleto, mientras la nota antigua sigue consultable para quien pregunte "¿qué hacía esto antes?". El agente se incorporó una sola vez, conservó el resultado y se autocorrigió cuando la realidad cambió — que es exactamente lo que el agente de las cuatro sesiones por tarde nunca pudo hacer, porque tenía un mapa y ninguna memoria.


Antipatrones

El bucle es simple. Las formas de romperlo son específicas. Evita estas:

Memorizar números de línea volátiles. "El bug está en charge.rs:142" deja de valer en el momento en que alguien añade un import encima. Almacena qué y por qué, anclado con related_files y nombres de símbolos — deja que el paso de re-búsqueda relocalice el dónde. Octocode encuentra dedupe_key esté en la línea 142 o en la 90; un número de línea memorizado es solo una mentira con marca de tiempo.

Sobreindexar en la memoria. La descripción de memorize de Octobrain es explícita: omite el estado transitorio y las cosas fáciles de rededucir. Si semantic_search puede encontrarlo en una llamada, no pertenece a la memoria — la memoria es para conclusiones y decisiones, no para hechos que el mapa ya contiene. Memorizar "el módulo de auth está en src/auth" desperdicia un espacio en algo que el mapa responde gratis, y diluye el recall de las memorias que de verdad importan. La memoria almacena lo que el código no puede decirte: el porqué, el límite de alcance, el "no toques esto" que solo sabes porque alguien lo dijo.

Confiar en memorias obsoletas sin volver a verificar. Este es el que más duele, y por eso existe el paso 4. Una memoria es una afirmación sobre el código en un momento dado. El código se mueve. Vuelve a buscar siempre los related_files antes de actuar sobre una decisión antigua; cuando el código haya derivado, reemplaza (supersede) la memoria en lugar de forzar la conclusión antigua sobre código nuevo. Una memoria que nunca verificas es deuda técnica que te responde.

Dejar que las memorias reemplazadas se pudran en lugar de enlazarlas. Cuando un hecho cambia, no te limites a memorize el nuevo y dejar huérfano el antiguo — enlázalos con supersedes. Octobrain clasifica el hecho actual por encima del obsoleto y mantiene el historial consultable. Las correcciones huérfanas dejan dos memorias contradictorias igual de seguras y ninguna forma de saber cuál es la actual.

Usar solo una mitad y darlo por hecho. La búsqueda sola rededuce para siempre. La memoria sola se desincroniza del código. El valor no está en ninguna herramienta — está en el bucle entre ellas. Si solo registras un servidor, has construido medio cerebro.


La versión corta

Un agente de IA en un código base desconocido necesita dos cosas que un humano da por sentadas: la capacidad de encontrar el código relevante y la capacidad de recordar qué se decidió sobre él. La búsqueda semántica es la primera. La memoria persistente es la segunda. Úsalas como pareja — buscar para localizar, memorizar la decisión, recordar la próxima vez, volver a buscar para verificar — y el agente dejará de reincorporarse en cada sesión y de confiar en conclusiones que ya no puede ubicar en el código.

Un mapa te dice dónde están las cosas. Una memoria te dice qué decidiste sobre ellas. Dale al agente ambas y conecta el bucle entre ellas. Esa es toda la técnica. Si la higiene de la memoria es la parte que quieres ampliar, el artículo hermano sobre memoria de agentes sin el ruido profundiza en qué vale la pena conservar.

— Don


Octocode y Octobrain son de código abierto bajo Apache-2.0, y se ejecutan juntos dentro de Octomind. ¿Encontraste un borde afilado en el bucle? Abre un issue — el flujo de trabajo mejora cuando la gente nos dice dónde se rompe.