Skip to content

misMEM 0.6.0

Latest

Choose a tag to compare

@iDiagoValeta iDiagoValeta released this 29 Oct 21:13
· 45 commits to main since this release
f3b0755

El olvido respeta before_days también al borrar memorias

  • forget borraba los episodios de una memoria de saliencia baja aunque alguno fuera más nuevo que before_days. Con los defaults de mismem-forget (90 y 30 días), un episodio de 40 días atado a una memoria olvidable se borraba antes de los 90 que promete before_days. Ahora la memoria sólo es olvidable cuando todos sus episodios ya superaron before_days; mientras quede uno más nuevo, la memoria y sus episodios esperan. Las memorias sin episodios o con todos viejos se borran igual que antes, y sus episodios con ellas para que ON DELETE SET NULL no los devuelva a pendientes.
  • El conteo de dry_run sale de las mismas consultas que el borrado, y un test compara lo que anuncia con lo que después borra.

Embeddings: mejores valores por defecto para quien instala misMEM (#72)

  • Con nomic-embed-text el corte semántico de 0,5 no cortaba nada: las 10 preguntas ajenas de la
    medición traían 5 recuerdos cada una, así que el hook de inyección añadía recuerdos sin relación a
    cada prompt. Sin MISMEM_EMBEDDING_MIN_SCORE, el corte por defecto ahora depende del modelo: 0,65
    para nomic-embed-text (también con etiqueta, p. ej. :latest) y 0,5 para el resto. Con 0,65 las
    preguntas ajenas bajan a 0 recuerdos sin perder aciertos. Un valor explícito, incluido 0, sigue
    ganando siempre.

  • scripts/install.sh: si hay llama-server, ofrece EmbeddingGemma 2 como opción recomendada.
    Con memorias en castellano encontró 34 de 50 paráfrasis frente a 4 de 50 de nomic-embed-text
    (medición de la issue). Baja el GGUF (296 MB) sólo si el usuario dice que sí, deja llama-server
    como servicio de systemd de usuario (mismem-embed, en CPU; --embeddings-gpu o la pregunta
    interactiva lo pasan a la GPU) y escribe en ~/.config/mismem/env el proveedor, los prefijos y el
    corte 0,65. nomic-embed-text por Ollama queda como alternativa ligera. Con --yes el modelo no
    cambia salvo que se pida con --embeddings gemma|nomic: re-ejecutarlo conserva tal cual cualquier
    configuración de embeddings que ya hubiera (otro modelo, otro servidor, prefijos, corte) y no duplica el
    servicio. Si se vuelve a nomic, el servicio mismem-embed que montó el instalador se para y se
    desactiva para no dejar ocupados el puerto y la memoria. Con EmbeddingGemma 2 elegido pero sin servidor
    arrancado no se escribe MISMEM_EMBEDDINGS=off: la búsqueda degrada a palabras y se activa sola cuando
    el servidor arranca.

  • README y DEPLOY.md dicen claro que con memorias que no estén en inglés la opción es EmbeddingGemma 2,
    que con nomic-embed-text el corte es 0,65 y no hay que esperar paráfrasis, y que el corte de
    EmbeddingGemma 2 recomendado pasa de 0,68 a 0,65 (misma efectividad medida, con más recuerdos por
    pregunta).

  • El timeout de embeddings y el puerto HTTP dejan de aceptar valores imposibles: un negativo, basura, 0 o vacío vuelven a 3000 ms y a 3200 (antes MISMEM_EMBEDDING_TIMEOUT_MS=-5 hacía que AbortSignal.timeout lanzara RangeError y un MISMEM_HTTP_PORT con basura llegaba a listen como NaN); prepublishOnly usa pnpm.

jscpd sube a 5.4.1, y sus dos clones de más desaparecen

  • jscpd pasa de 4.3.0 a 5.4.1 (versión exacta). La 5 compara dos bloques que la 4 no veía: recall y
    recallHybrid montaban igual la lista de hits, ahora en finalizeHits, y MemoryList/PendingList
    repetían el <ul> y el pie de "cargar más", ahora en PagedList. pnpm dup sigue con umbral 0 y no
    encuentra clones; no hay cambios de comportamiento ni visibles.

AGENTS.md como contrato de trabajo

  • AGENTS.md deja de ser las antiguas instrucciones de Copilot: mapa del código actual, comandos, reglas duras con
    su porqué, convenciones y trampas propias del repo, y enlaces al resto de la documentación en vez de copiarla.

Un estándar de contribución escrito, y comprobado

  • Contribuir no tenía forma: ni formularios de issue, ni plantillas de PR, ni normas escritas, y el
    README mezclaba la presentación con material de referencia y con la historia de cada iteración. Ahora
    CONTRIBUTING.md es la puerta de entrada (entorno, flujo trunk-based, estándar de issue y de PR,
    etiquetas, releases) y docs/standards/ fija ramas, commits, comentarios, estilo, tests, seguridad y
    releases, cada norma con su forma de comprobarla. docs/README.md dice qué documento es para quién y
    qué no se documenta.
  • .github/ gana cuatro formularios de issue (error, mejora, documentación y medición de recall, cada
    uno con su criterio de cierre), los issues en blanco desactivados con un enlace al reporte privado de
    vulnerabilidades, una plantilla de PR por defecto y tres más (release, dependency, documentation),
    release.yml para agrupar las notas por etiqueta y CODEOWNERS.
  • Las etiquetas que citan los formularios, las plantillas, release.yml y CONTRIBUTING.md se declaran
    en .github/labels.json. engine/tests/contribution-standard.test.ts falla si alguno cita una que no
    existe, o si un tipo se queda sin formulario ni excepción razonada; el workflow labels.yml compara
    ese fichero con las etiquetas que tiene GitHub.
  • Dependabot deja de proponer saltos de versión mayor de node en el Dockerfile: los majors se mueven a
    mano a una LTS.
  • El README pasa a ser una guía: qué es, un diagrama del sistema, el Quickstart primero y un mapa de la
    documentación. Lo que era referencia se mueve sin perder nada: el comportamiento del recall y la
    búsqueda por significado a docs/recall.md, los hooks a docs/hooks.md, y las capas, los componentes,
    la saliencia y el índice hebbiano a docs/architecture.md. El roadmap por fases de engine/README.md
    desaparece (la historia es este fichero) y la consolidación deja de repetir el despliegue de DEPLOY.md.

zod 4

  • zod pasa de 3.25.76 a 4.6.7 (versión exacta): .int() rechaza enteros fuera del rango seguro de JS (y el inputSchema de forget lo anuncia), los límites de longitud cuentan code points en vez de unidades UTF-16 y cambian los textos de ZodError; tests/tool-schemas.test.ts compara con z.toJSONSchema en vez de leer internos de zod 3.

Un solo diálogo de confirmación en el detalle

  • MemoryView y TraitView repetían el pie del diálogo de confirmación y la forma de sus props. Ambos viven ahora en DetailParts.tsx (ConfirmDialog y DetailViewProps), sin cambios visibles, y pnpm dup pasa a exigir umbral 0: ningún bloque copiado de 10 o más líneas.

@vitejs/plugin-react sube a 5.2.0

  • @vitejs/plugin-react pasa de 4.7.0 a 5.2.0, fijado exacto. ui/vite.config.ts llama a react()
    sin opciones y la 5.x sólo deja de añadir react/react-dom a resolve.dedupe, que aquí sobra.

Linter con Biome, y fuera las directivas de ESLint que no hacían nada

  • No había linter ni formateador. Ahora Biome 2 es el único, sólo como linter: pnpm lint y un step
    de CI justo después de pnpm dup. El formato no se toca a propósito. biome.json limita el
    análisis a src/, ui/src/ y tests/ en TypeScript, y deja fuera ui/src/components/ui, que es
    shadcn vendorizado, además de CSS, JSON y fixtures.
  • Las cinco directivas de supresión de ESLint no desactivaban nada: cuatro no-console en
    server-http.ts y used-cli.ts, y un no-explicit-any en viewer-api.test.ts. Ese any
    escondía dos huecos: el mock no implementa ServerResponse y el cuerpo de la respuesta no tenía
    tipo. Ahora el mock se ensancha una sola vez al pasarlo al handler y el cuerpo tiene ApiBody.
  • Reglas recomendadas desactivadas, todas por patrones deliberados del repo: noNonNullAssertion
    (los guardas y los tests afirman con ! justo después de comprobar longitud o nulidad; reescribir
    los 24 usos serían cambios cosméticos), useSemanticElements (los roles search, group y
    meter son ARIA válido sobre elementos con estilos; cambiarlos por fieldset, meter o
    search cambiaría el marcado y el CSS sin arreglar nada) y noAutofocus (el formulario de
    edición enfoca el campo al abrirse). useExhaustiveDependencies sigue activa pero no avisa de
    dependencias de más: GraphView repite size.width a propósito para reaplicar el estado cuando
    el canvas termina de montarse. biome.json es JSON estricto y Biome no acepta comentarios ahí,
    así que el porqué queda en esta entrada.
  • Los errores que sí eran defectos, arreglados: ocho let sin tipo en los CLI y el entries de
    walk.ts, tres return que devolvían un void en viewer-static.ts, la comprobación de
    content-type sin cadena opcional en http-body.ts, un break con etiqueta que no hacía nada en
    consolidation/run.ts, los any de los tests y las claves por índice de los esqueletos de carga.

Errores, claves y ayudas en el idioma del producto

  • Los errores que ven un cliente MCP o el viewer salían en inglés (Unknown tool, not found,
    is required, invalid layer) mientras el resto del producto responde en español. Ahora están en
    español, con el mismo contenido y el mismo código de estado.
  • La confirmación de olvido mezclaba claves en los dos idiomas (impacto, memorias_que_cita,
    rasgos_que_la_citan, episodios). Pasan a impact, memories_cited, traits_citing y
    episodes; la UI y los tests leen las nuevas claves.
  • Las ayudas de mismem-consolidate, mismem-forget, mismem-stats, mismem-migrate-engram,
    mismem-obsidian y mismem-embed usan los encabezados Uso: y Variables:, y la de
    mismem-ingest, que estaba entera en inglés, también está en español. Los encabezados de las ayudas
    pasan a misMEM: CLI de ..., el progreso de mismem-migrate-engram y la cabecera de mismem-stats
    están traducidos, y los fallos de todos los CLIs salen con el prefijo ERROR: en vez de FATAL: o
    Error:. Los flags y lo que hace cada comando no cambian.
  • El servidor HTTP responde en español el 415 de /mcp, el error de parseo de JSON-RPC (mismo
    -32700), las acciones y endpoints desconocidos y los 404, también los del viewer. Los códigos de
    estado, los identificadores y los nombres de tools no cambian.

Una conversación de ChatGPT sin texto no se importa

  • ingestChatGPTExport decidía si saltarse una conversación mirando la cadena ya montada, y
    flattenConversation siempre emite la cabecera # título y Created:. Una conversación con
    todos los mensajes vacíos o de sistema se importaba como un episodio con sólo título y fecha.
    Ahora la decisión se toma sobre los mensajes con texto y esa conversación cuenta como skipped.

Una sola versión, y comprobada

  • La versión estaba escrita a mano en engine/package.json, server.json (dos campos) y
    engine/src/server-core.ts, y nada comparaba las cuatro. Un release podía anunciar por MCP una
    versión distinta de la publicada. Ahora server-core.ts lee engine/package.json al cargarse
    (createRequire, que funciona igual desde src/ con tsx que desde dist/), y tests/version.test.ts
    falla si el serverInfo que ve un cliente MCP o cualquiera de los dos campos de server.json deja de
    coincidir con el paquete.
  • DEPLOY.md recuerda que los tags van anotados (git tag -a vX.Y.Z -m 'misMEM X.Y.Z').

Higiene del repositorio: reportar en privado, dependencias y secretos

  • SECURITY.md dice cómo reportar una vulnerabilidad en privado (GitHub Private Vulnerability
    Reporting), que sólo la última versión publicada recibe arreglos y qué cuenta como
    vulnerabilidad acá: la autenticación del modo HTTP, y esa DB que tiene toda la memoria. Antes
    no había ningún canal privado, así que el reporte terminaba en un issue público.
  • Las convenciones pasan de .github/copilot-instructions.md a AGENTS.md, que leen todos los agentes (Claude Code a través de CLAUDE.md, que lo importa); el fichero de Copilot desaparece.
    install.sh configura Claude Code y opencode, y hasta ahora sólo Copilot cargaba esas
    convenciones: los otros dos agentes trabajaban sin ellas.
  • .github/dependabot.yml saca la actualización de dependencias de la mano de nadie: npm en
    engine/ (minor y patch agrupados en un PR), los actions, que están fijados por SHA y sin
    esto no se mueven nunca, y el node:22-alpine del Dockerfile.
  • El job secret-scan de CI corre gitleaks sobre todos los commits en cada PR y en cada push a
    main, con la imagen fijada por digest. Nada buscaba una clave commiteada antes de que llegara
    a main.
  • .gitignore cubre *.db-journal, el diario que SQLite deja al lado de la DB cuando no
    trabaja en modo WAL.

Ningún CLI se traga un flag mal escrito

  • mismem-embed, mismem-bench, mismem-stats y mismem-migrate-engram ignoraban los flags que no
    entendían: un --scop daimon reindexaba o contaba todos los ámbitos, un --jsn dejaba la salida en
    texto, un --dryrun migraba de verdad y un --batch abc caía al valor por defecto. mismem-ingest
    hacía lo mismo con los posicionales de más. Ahora cada uno parsea en su <cli>-args.ts, como
    consolidate y forget, y un flag o posicional inesperado corta con argumento desconocido: <x> y
    exit 64. Un valor ausente (--scope, --batch, --limit, --source) también corta, un posicional
    de ingest en blanco cuenta como ausente, y un subcomando de mismem-ingest que no existe se rechaza
    en vez de imprimir la ayuda.
  • mismem-stats abría la base y, si una consulta fallaba, moría con la base abierta y sin mensaje.
    Ahora la cierra en un finally y el fallo sale por stderr como [stats] FATAL: ... con exit 1.

La imagen y el paquete dejan de arrastrar lo que no usan

  • Sin engine/.dockerignore, un docker build local mandaba al daemon node_modules, dist,
    coverage y cualquier .db que hubiera en engine/. El Dockerfile no los usa: sólo copia
    package.json, pnpm-lock.yaml, pnpm-workspace.yaml, tsconfig.json, src y ui.
  • Las dos etapas fijaban la base por tag (node:22-alpine), así que un rebuild meses después
    compilaba contra otra Alpine sin que nadie tocara el repositorio, y better-sqlite3 es nativo.
    Ahora van por digest, con el tag legible al lado.
  • La etapa runtime copiaba node_modules entero, con vite, typescript, react y vitest dentro de una
    imagen que sólo ejecuta dist/server-http.js. Tras pnpm build se hace pnpm prune --prod;
    better-sqlite3 es dependencia de producción y se queda (probado con docker build).
  • El paquete npm declaraba MIT sin llevar el texto de la licencia. engine/LICENSE es copia
    literal de la raíz y entra en files. En CI, cmp LICENSE engine/LICENSE falla si se separan, y
    hadolint revisa el Dockerfile con la imagen fijada por digest.

mismem-http escucha sólo en loopback por defecto

  • Sin MISMEM_AUTH_*, el servidor escuchaba en 0.0.0.0 y la guarda de Host sólo frena el
    DNS rebinding del navegador: cualquier cliente de la LAN puede mandar Host: localhost. Ahora el
    valor por defecto de MISMEM_HTTP_HOST es 127.0.0.1. Para mantener el comportamiento anterior,
    MISMEM_HTTP_HOST=0.0.0.0 junto con MISMEM_AUTH_USER y MISMEM_AUTH_PASS. Los contenedores no
    cambian: el Dockerfile y el ejemplo de compose ya fijan 0.0.0.0.
  • El fichero SQLite, con toda la memoria, se creaba con el umask por defecto. Ahora queda en 0600
    (y sus -wal y -shm), y el directorio, si lo crea mismem, en 0700. Un directorio que ya existía
    no se toca.

Fuera el código muerto

  • Se retiran src/tools/dump-engram.ts, ingest-engram-dump.ts y smoke-recall.ts: scripts de una sola vez
    que nada importaba ni documentaba, pero se compilaban dentro del paquete npm, y uno apagaba la
    verificación TLS antes de mandar la cabecera Authorization. Para migrar desde Engram sigue
    mismem-migrate-engram, que lee un engram.db. ENGRAM_URL y ENGRAM_AUTH salen de .env.example.
  • pnpm knip corre en CI: falla con ficheros, exports o dependencias sin uso.

El índice hebbiano empieza a existir

  • reinforce recibía los memory_ids juntos —sabe que esos recuerdos se usaron en el mismo
    turno— y lo colapsaba en un bump de saliencia por recuerdo. Tiraba justo la co-ocurrencia, que
    es la señal hebbiana de verdad. Ahora la guarda: memories.use_count cuenta los turnos en que
    cada recuerdo se usó, y co_use(memory_a, memory_b, n, last_at) cuenta cada par que se usó junto.
  • Se escribe desde reinforce y no desde reinforceMemories, porque a esa última la llama
    también el camino de recall, donde aparecer no es haberse usado. Con la reinyección por aparición
    apagada (MISMEM_REINFORCE_ON_RECALL=off) la señal arranca limpia: use_count en 0 para todos.
  • Hacía falta porque salience hoy mide recencia, no uso: la pasada de decay escribe
    salience_updated_at en las 3.738 memorias, así que el campo no distingue "se usó" de "se
    marchitó". Medido sobre producción: de 1.351 memorias de más de 60 días sólo 14 tienen saliencia
    por encima de 0,85, y entre ésas hay tres marcadores de ruido [pareto:l0]. Ordenar por
    saliencia hoy pone arriba lo nuevo, no lo importante.
  • Migración aditiva, verificada sobre copia de la DB de producción: 15 ms, integrity_check y
    foreign_key_check ok, 3.738 memorias y 216 rasgos intactos. Los pares se van por cascada
    cuando el recuerdo se borra.
  • Para qué: es la fase 1 del ordenador de memoria.

El refuerzo por uso ve lo que la inyección adelantó

  • Los dos hooks no se enganchaban: mismem-used sólo reconocía resultados de recall, y lo que
    mismem-inject adelanta llega como adjunto hook_additional_context, no como tool_result. Un
    recuerdo inyectado y usado nunca sumaba saliencia. Medido sobre un transcript real: la inyección
    había dejado 6 ids en el turno y se veían 0.
  • parseInjection es lo inverso de formatInjection y vive en el mismo archivo, para que el
    formato y su lectura no puedan derivar.
  • reinforce cuenta los rasgos aparte (traits) en vez de meterlos en unknown. La inyección
    también adelanta rasgos, así que mezclarlos dejaba esa alarma —la de "alguien manda ids
    inventados"— sonando desde el primer día.
  • Aportado por @iDiagoValeta en #48, con el hallazgo de los
    rasgos que quedaba abierto ahí.

El README dice lo que el código hace

  • Seis gestos, no cinco: faltaba reinforce, y recall ya no refuerza siempre.
  • Los binarios de los hooks (mismem-inject, mismem-used) y una sección que explica para qué
    sirven, qué no sale de la máquina y cómo se enciende el refuerzo por uso.
  • Dos lugares seguían diciendo cinco invocaciones mientras el título decía seis. Ahora los tres
    dicen lo mismo.
  • El viewer: la API de sólo lectura ya expone los ámbitos, el origen de cada recuerdo, los rasgos
    que lo citan y el grafo (/api/{scopes,memories,memory/:id,trait/:id,graph}), y el README
    mencionaba sólo los tres endpoints originales.
  • La consolidación acepta OpenAI y cualquier servidor compatible desde #67, y tanto los
    componentes como la sección de privacidad hablaban sólo de DeepSeek y OpenRouter. Importa
    porque es lo que permite consolidar sin que el texto salga de la red propia.
  • Una sección para el índice hebbiano.
  • El README y DEPLOY.md mandaban a docker pull ghcr.io/lucasmella-stack/mismem-brain:latest como
    camino recomendado, y esa imagen nunca se publicó: las tres corridas de docker.yml fallan
    al empujar con denied: permission_denied: read_package, así que el pull responde denied.
    Cualquiera que siguiera el README se chocaba con eso. Ahora el camino documentado es el build
    local, que sí funciona, y la imagen publicada queda marcada como pendiente.
  • Una sección de agradecimiento con lo que trajo @iDiagoValeta,
    que a esta altura es buena parte de lo que hace a misMEM instalable y auditable por alguien que
    no sea su autor.

La memoria se puede exportar como bóveda de Obsidian

  • mismem-obsidian escribe la memoria como bóveda en <bóveda>/misMEM/: una nota por memoria,
    rasgo y ámbito, enlazadas por origen y por parecido.
  • Re-exportar sólo borra las notas cuyo frontmatter sigue declarando el id del manifiesto, y se
    niega si la base no tiene nada que exportar o el manifiesto no se puede leer: así una bóveda
    migrada a mano no se vacía por error.

La interfaz deja de ser de sólo lectura

  • UI React con shadcn y TanStack Query: listas, detalle, grafo 2D con filtros y búsqueda dentro
    del grafo.
  • viewer-actions.ts expone POST desde el propio origen para corregir una memoria, deshacer su
    consolidación, cristalizar y lanzar una consolidación.
  • Olvidar una memoria (con sus episodios) o un rasgo exige MISMEM_VIEWER_ALLOW_DELETE=1 y dos
    pasos: el primero devuelve qué se perdería y un token de 60 s, y borra el segundo.

La búsqueda léxica rescata paráfrasis

  • Sin capa semántica, una consulta estricta (todas las palabras) que no encontraba nada dejaba
    fuera la paráfrasis que sí estaba. Ahora recall reintenta con una consulta OR y conserva sólo
    los candidatos que comparten al menos dos términos informativos (una con una consulta de una
    palabra, el 30 % en las largas), con un tope de 3 por capa (rasgos, episodios y memorias).
  • El rescate sólo entra cuando la estricta no devolvió nada y no hay vectores comparables que
    puntuar, para no alterar una búsqueda que ya funcionaba.

Instalación en Linux con un solo script

  • scripts/install.sh instala y configura misMEM: elige dónde vive la DB y con qué proveedor
    consolidar, guarda la clave en ~/.config/mismem/env con permisos 600 y deja los clientes
    llamando a ~/.local/bin/mismem-run, sin credenciales en su configuración.

El servidor HTTP se protege sin auth

  • MISMEM_ALLOWED_HOSTS: sin auth, sólo se atienden Host de loopback y el resto responde 403,
    contra DNS rebinding.
  • MISMEM_HTTP_MAX_BODY_BYTES: POST /mcp corta el cuerpo en 4 MiB con 413 sin juntarlo en
    memoria, y un Content-Type que no es JSON responde 415 antes de leer.

El conectoma se puede mirar

  • /api/graph?co_use=1 suma una segunda capa lateral. La que ya había, ?related=1, une memorias
    parecidas: sale de lo que cada una dice. Esta sale de lo que pasó: qué recuerdos usó junto
    un mismo turno. Las dos juntas valen más que cada una —parecidas que nunca se usan juntas son
    candidatas a que una reemplace a la otra— pero eso es otra entrada.
  • El peso no es el conteo crudo, es NPMI. Un recuerdo que se usa en todos los turnos co-ocurre
    con todo, así que por conteo sería el centro del grafo sin orientar a nada: el mismo error de la
    saliencia midiendo recencia. Con NPMI, 0 es "indistinguible del azar" y 1 "nunca aparecen
    separados". Hay un test que construye el caso y falla si el ranking se ordena por grado;
    comprobado mutando el comparador.
  • Hacía falta un denominador y no existía: la tabla meta guarda el total de turnos de uso medido,
    y se suma dentro de la misma transacción que los conteos de co_use para que no pueda quedar
    desfasado. Migración aditiva.
  • Con pocos datos la fuerza es ruido por construcción —dos recuerdos usados una sola vez, esa vez
    juntos, dan 1—, así que cada arista viaja con su n, ?co_use_min_n=k filtra por soporte y el
    resumen informa el tamaño de la muestra. Los costados de la interfaz tienen que poder decir "esto
    todavía no significa nada" en vez de pintar ruido.
  • Sin el parámetro la respuesta no cambia: las aristas verticales no reciben campos nuevos y hay un
    test que compara las dos respuestas.
  • Sobre copia de producción: migración en 18 ms, integrity_check y foreign_key_check ok, 3.805
    memorias y 216 rasgos intactos, y las seis aristas existentes dan fuerza 0 porque el contador nace
    ahora. Es correcto: son turnos que nadie midió, y 0 dice "no sé" en vez de inventar un número.

El contador de turnos nace con la cota que los datos permiten

  • Un cerebro que ya venía midiendo co-uso con la versión anterior guarda n y use_count de turnos
    que nadie contó. Con el contador en 0 toda fuerza vale 0, que es correcto —dice "no sé"—, pero
    desde el primer turno medido después de actualizar el cociente n / turnos se acota a 1 y cada
    par heredado se lee como asociación máxima: justo el club de los ricos que el peso existe para
    evitar. Duraba hasta que el contador pasaba por arriba de cada n.
  • Al abrir, si el contador no existe, se siembra con el uso más alto registrado (max(use_count) y
    max(n) de co_use). Es una cota inferior de los turnos que hubo, porque cada uso ocupó un turno:
    subestima, nunca infla. Sólo cuando falta: un contador andando es la medición de verdad y no se pisa.
  • Sobre copia de producción (2.527 memorias, 2.651 episodios, esquema previo a use_count): migración
    en 19 ms, nada perdido, y no siembra nada porque ahí no hay uso medido —un cerebro viejo no recibe un
    contador inventado—. Con 40 turnos reinyectados sobre ids reales, el par identidad+acompañante da
    0,112; sin semilla, el primer turno tras el deploy lo sube a 1; con semilla (38, la cota) vuelve a
    0, que es la respuesta correcta para algo que acompaña a un recuerdo que está en todos los turnos.
    El par poco usado se queda en 1 con n de 3, y para eso está ?co_use_min_n=k.
  • El test que lo cubre afirma las dos lecturas, la de antes y la de después, así que el artefacto queda
    escrito en la suite en vez de en un comentario.

La imagen de GHCR existe

  • ghcr.io/lucasmella-stack/mismem-brain se publica de verdad: 0.5.0, 0.5 y latest, multi-arch
    (linux/amd64 y linux/arm64), con pull anónimo. Es la primera imagen que llega al registro desde
    que existe el repo, y latest deja de apuntar al 0.2.0 de julio de 2026.
  • No hacía falta cambiar nada del workflow. Los push de v0.1.0 y v0.2.0 habían creado el paquete en
    julio, GitHub no lo vinculó al repositorio, y desde v0.3.0 todo push moría en denied: permission_denied: read_package. El paquete tampoco se veía en la lista de la cuenta por estar
    privado y sin vincular. Dos cambios en Package settings: el repo con rol Write en Manage
    Actions access
    , y la visibilidad a Public.
  • El Quickstart de Docker del README pasa a ser docker run ghcr.io/... en vez de clonar y construir,
    y en DEPLOY.md la imagen publicada pasa a ser la opción A: el build en el servidor queda como la
    alternativa para probar un cambio sin pasar por un release. El diagnóstico queda escrito ahí, con el
    comando de gh api que hace visible un paquete privado sin vincular y el docker logout ghcr.io
    para las máquinas con credenciales viejas.

What's Changed

Correcciones

  • fix: read the last two numeric env vars through env.ts and publish with pnpm by @iDiagoValeta in #148
  • fix(forget): keep a memory while any of its episodes is newer than before_days by @iDiagoValeta in #135

Novedades y cambios

  • feat(embeddings): better defaults for whoever installs misMEM by @iDiagoValeta in #137

Documentación

  • docs: standardize the contribution flow and rewrite the docs as a guide by @iDiagoValeta in #136
  • docs: rewrite AGENTS.md as the operating contract for agents by @iDiagoValeta in #145

Limpieza e investigación

  • refactor(viewer): share the confirmation dialog and allow no duplicated code by @iDiagoValeta in #133
  • build: upgrade jscpd to 5.4.1 and remove the two clones it finds by @iDiagoValeta in #146

Dependencias

  • build(deps): Bump better-sqlite3 from 12.11.1 to 13.0.3 in /engine by @dependabot[bot] in #112
  • build: upgrade @vitejs/plugin-react to 5.2.0 by @iDiagoValeta in #132
  • build: migrate to zod 4 by @iDiagoValeta in #134
  • build(deps): Bump the actions group with 8 updates by @dependabot[bot] in #138
  • build(deps): Bump the engine-minor group in /engine with 6 updates by @dependabot[bot] in #139
  • build(deps-dev): Bump vitest from 2.1.9 to 5.0.4 in /engine by @dependabot[bot] in #140
  • build(deps-dev): Bump @types/better-sqlite3 from 7.6.13 to 9.6.0 in /engine by @dependabot[bot] in #141
  • build(deps-dev): Bump vite from 6.4.5 to 8.3.4 in /engine by @dependabot[bot] in #142

Otros cambios

  • ci(docker): latest sale sólo del tag estable más alto by @lucasmella-stack in #26
  • feat(hooks): el hook de inyección se enciende solo cuando el servidor está listo by @lucasmella-stack in #27
  • fix(hooks): el envoltorio quedó commiteado sin permiso de ejecución by @lucasmella-stack in #28
  • fix(mcp): recall declara reinforce en su inputSchema by @lucasmella-stack in #29
  • fix(hooks): el refuerzo por uso ve los recuerdos que la inyección adelantó by @lucasmella-stack in #55
  • fix(consolidación): cada llamada al LLM tiene un tope de 120 s by @iDiagoValeta in #50
  • fix(http): con una sola variable de auth el servidor no arranca by @iDiagoValeta in #31
  • fix(http): sin auth sólo se atiende Host de loopback by @iDiagoValeta in #33
  • feat: absorber los aportes de iDiagoValeta sobre el refuerzo por uso by @lucasmella-stack in #70
  • feat(consolidación): OpenAI o cualquier API compatible, además de DeepSeek y OpenRouter by @iDiagoValeta in #67
  • feat(install): instalar y configurar misMEM en Linux con un solo script by @iDiagoValeta in #69
  • fix(http): POST /mcp corta el cuerpo en 4 MiB con 413 by @iDiagoValeta in #35
  • fix(pareto): un hecho corto llega al LLM en vez de caer como ruido by @iDiagoValeta in #37
  • fix(consolidación): "sin señal" suma un strike en vez de mandar la tanda a ruido by @iDiagoValeta in #39
  • fix(forget-cli): un flag que no se entiende corta en vez de purgar by @iDiagoValeta in #42
  • fix(consolidate-cli): flags inválidos cortan con error de uso by @iDiagoValeta in #44
  • feat(viewer): API de sólo lectura para ver de dónde sale cada recuerdo by @iDiagoValeta in #52
  • feat(hebbiano): contar el uso y el co-uso de los recuerdos (fase 1 del ordenador de memoria) by @lucasmella-stack in #73
  • feat(viewer): interfaz v2 con shadcn/ui y grafo 2D al estilo Obsidian by @iDiagoValeta in #71
  • feat(obsidian): exportar la memoria como bóveda de Obsidian by @iDiagoValeta in #65
  • fix(recall): sin coincidencia exacta, rescate por dos palabras y raíz aproximada by @iDiagoValeta in #46
  • fix(http): bind loopback by default and make the database owner-only by @iDiagoValeta in #90
  • ci: type-check the tests and gate publishing on the suite by @iDiagoValeta in #91
  • chore: remove dead code and keep knip watching for it by @iDiagoValeta in #92
  • ci: pin actions, scope tokens and lint the workflows by @iDiagoValeta in #93
  • docs: make the user docs describe the current code by @iDiagoValeta in #94
  • docs: make comments, tool descriptions and agent rules match the code by @iDiagoValeta in #95
  • build: pin the base image, ship only production deps and the license by @iDiagoValeta in #96
  • fix(cli): reject unknown flags in every CLI by @iDiagoValeta in #98
  • test: cover the MCP dispatch, the HTTP server and the importers by @iDiagoValeta in #100
  • chore: add SECURITY.md, AGENTS.md, Dependabot and a secret scan by @iDiagoValeta in #99
  • ci: build the image on main too and start it before calling it good by @iDiagoValeta in #102
  • chore: read the version from package.json and check server.json agrees by @iDiagoValeta in #103
  • docs: stop versioning personal design notes by @iDiagoValeta in #105
  • refactor: define each helper, type and schema once by @iDiagoValeta in #106
  • docs: keep the agent conventions in AGENTS.md only by @iDiagoValeta in #115
  • test: stop the MIN_AGE_HOURS=0 case from racing the clock by @iDiagoValeta in #118
  • fix(ingest): skip ChatGPT conversations that carry no message text by @iDiagoValeta in #116
  • fix: user-facing messages in Spanish, JSON keys in English by @iDiagoValeta in #119
  • chore: lint with Biome and drop the eslint directives that did nothing by @iDiagoValeta in #121
  • chore(dependabot): only propose releases at least 14 days old by @iDiagoValeta in #123
  • refactor: split the largest modules along their responsibilities by @iDiagoValeta in #126
  • feat(conectoma): la capa lateral del grafo, pesada por asociación y no por conteo by @lucasmella-stack in #120
  • fix(conectoma): el contador de turnos nace con la cota que los datos permiten by @lucasmella-stack in #149
  • docs(docker): la imagen publicada pasa a ser el camino por defecto by @lucasmella-stack in #150
  • chore(release): 0.6.0 by @lucasmella-stack in #151

New Contributors

Full Changelog: v0.5.0...v0.6.0