Observations
Una observation es el átomo de memoria en iLAB Memory. Cada hecho, preferencia, decisión, bugfix o pattern que persistís es una observation. Las session agrupan observations; los topic_key las hacen evolucionar; el scoring las rankea. Pero la observation es el ladrillo indivisible.
Anatomía
Cada observation tiene estos campos (el modelo Pydantic Observation):
| Campo | Tipo | Notas |
|---|---|---|
id | int | Asignado por el store al insertar (None para drafts). |
session_id | str | La sesión de la última actualización material (Braess #4). |
user_id | str | Scope de propiedad. Toda lectura/escritura es por usuario. |
type | str | Uno de los types permitidos — ver abajo. |
title | str | Corto, legible para humanos. |
content | str | El cuerpo completo. Las regiones <private> se strippean antes de persistir. |
topic_key | str | None | Key opcional estable para upserts (ver Topic keys). |
normalized_hash | str | None | SHA-256 del content post-strip. Interno. |
revision_count | int | Se incrementa en cada UPDATE (default 1). |
created_at, updated_at | str | ISO 8601, UTC, con timezone. |
created_at se setea en el primer INSERT y nunca cambia. updated_at se refresca en cada UPDATE — eso alimenta el componente recency del scoring.
Los 5 types user-facing
La librería trae un set frozen de tipos de observation. v0.1.0 no soporta agregar tipos en runtime con la config default; te suscribís pasando Config(observation_types=(...)).
profile
Datos estables del usuario: nombre, rol, atributos. Máxima prioridad en ContextScore (1.0).
preference
Preferencias explícitas: "prefiere TypeScript sobre JavaScript", "responde en español". Prioridad 0.9.
decision
Decisiones arquitectónicas o de diseño tomadas durante la conversación. Prioridad 0.7.
discovery
Bugfixes, gotchas, edge cases aprendidos a la mala. Prioridad 0.5.
pattern
Convenciones de naming, estructura o estilo. Prioridad 0.6.
summary es un type reservado que usa internamente mem_session_summary y el path de auto-close. Llamar mem_save(type="summary", ...) levanta ValueError. Usá mem_session_summary(...) en su lugar.
Compact vs full vs public
iLAB Memory expone tres variantes del mismo registro. Es a propósito — diferentes superficies tienen diferentes restricciones de costo y de leak.
| Variante | Usado por | Incluye | Excluye |
|---|---|---|---|
Observation | Callers de la librería (lectura completa) | Todo | — |
ObservationCompact | mem_search, memories[] en mem_session_start | id, type, title, topic_key, score, snippet, updated_at | content, normalized_hash |
ObservationPublic | Responses de la API HTTP | Todo lo visible | normalized_hash (interno) |
Progressive disclosure: las listas devuelven ObservationCompact (~100 tokens). Cuando necesites el cuerpo, hidratá una observation por vez con mem_get_observation(observation_id).
Save, search, hidratación
- Save
- Search
- Hydrate
from ilab_memory import ILabMemory
mem = ILabMemory.from_path("./mem.db")
result = mem.mem_save(
user_id="alice",
type="preference",
title="Saludo preferido",
content="Alice prefiere que la saluden como 'Ali'.",
topic_key="user/alice/greeting",
)
print(result.id, result.outcome) # ej. 1 'created'
hits = mem.mem_search(user_id="alice", query="saludo", limit=5)
for compact in hits:
print(compact.id, round(compact.score, 3), compact.title)
# ObservationCompact tiene snippet pero no el content completo
obs = mem.mem_get_observation(user_id="alice", observation_id=result.id)
print(obs.title)
print(obs.content)
print(obs.revision_count)
Outcomes
mem_save siempre devuelve un SaveResult con uno de tres outcomes:
created
Se insertó una fila nueva. El id queda asignado. revision_count arranca en 1.
updated
Una fila existente matcheó (mismo topic_key, content distinto). La fila se actualizó in-place: revision_count incrementado, updated_at refrescado, created_at preservado, session_id sobreescrito con la sesión actual (Braess #4).
deduped
Una fila existente matcheó y el hash del content post-strip es idéntico. No hubo write. Se devuelve el id existente. La sesión igual se toca (D8: mem_save siempre extiende la vida de la sesión activa).
Seguridad cross-user
Cada lectura aplica el scope por user_id en el orchestrator. mem_get_observation(user_id="bob", observation_id=alice_obs_id) devuelve None — nunca levanta excepción, nunca filtra la existencia del dato del otro usuario.
Siguiente
- Sessions — el contenedor que agrupa observations.
- Topic keys — cómo hacer que las observations evolucionen en lugar de duplicarse.
- Scoring — por qué
compact.scorese ve como se ve.