Construir un agente de IA que nunca olvida (Parte 2): Teletransportación
Parte 2 de un agente de IA que nunca olvida: teletransporte de memoria entre sesiones, habilidades, deterioro y memoria con alcance por ramas para Claude Code y agentes similares.
Las notas siguientes reconstruyen un camino práctico para abordar “Crear un agente de IA que nunca olvida (Parte 2): Teletransportación, habilidades, deterioro y memoria por ámbito de ramas”. Se da énfasis en los contratos, las verificaciones y los marcadores de posición para código listo para usar, en lugar de en un enfoque motivacional. Al trabajar en la etapa de descripción general, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de un fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando se pasa de la demostración a entornos compartidos.
Donde lo dejamos
La etapa “Donde lo dejamos” funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y causan interrupciones en la continuación del proceso.
Paso 5: Teletransporte de memoria
La etapa de Teletransporte de Memoria del Paso 5 funciona mejor cuando se trata como una superficie medible. Capture un registro dorado, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mantenga el estado del gráfico simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación después de las interrupciones.
El flujo de trabajo
La etapa del flujo de trabajo funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado. Mantenga el estado del grafo simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y causan problemas al reanudar después de interrupciones.
Agent A: snapshot("for_review.vdb")
| send file to Agent B
v
Agent B: open("for_review.vdb") -> work -> add new memories
| send file back
v
Agent A: merge("for_review.vdb")
-> new memories imported
-> conflicts detected and resolved
La etapa del flujo de trabajo funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos.
Instantánea
En la fase de Snapshot, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Coloque la aprobación humana en las acciones que generan gastos o modifican datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial.
class MemoryTeleport:
def __init__(self, db: vectlite.Database):
self.db = db
def snapshot(self, path: str) -> str:
"""Create a portable copy of the entire database."""
self.db.snapshot(path)
return path
Diferencias: vista previa sin modificar
Para la vista previa de Diff sin modificar la etapa, defina las entradas, el responsable del paso y los criterios de finalización antes de cambiar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de correos no entregados forman parte del producto, no son mejoras posteriores. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en los datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.
def diff(self, remote_path: str,
namespaces: list[str] | None = None) -> MergeResult:
"""Preview what a merge would do, without writing anything."""
return self._merge_internal(remote_path, namespaces, dry_run=True)
Fusión: la parte interesante
En la fase de “Merge the interesting part”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Incluya la aprobación humana en aquellos casos en que se gastan fondos o se modifican datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial. En la fase de “Merge the interesting part”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de la versión de demostración al entorno compartido.
Resolución de conflictos.
def merge(self, remote_path: str,
namespaces: list[str] | None = None,
strategy: ConflictStrategy = ConflictStrategy.NEWER_WINS
) -> MergeResult:
"""Merge memories from a remote .vdb into the local database."""
return self._merge_internal(
remote_path, namespaces,
dry_run=False, strategy=strategy,
)
Estrategias de resolución de conflictos
Al trabajar en la etapa de estrategias de resolución de conflictos, anote primero el contrato: los insumos requeridos, la señal de éxito y qué ocurre en caso de un fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones en el código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Haga un punto de control después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intente nuevamente un nodo posterior.
class ConflictStrategy(str, Enum):
LOCAL_WINS = "local_wins" # keep local version
REMOTE_WINS = "remote_wins" # keep remote version
NEWER_WINS = "newer_wins" # keep whichever has a more recent updated_at
KEEP_BOTH = "keep_both" # import remote as a new record
El motor de fusión
Al trabajar en la etapa del motor de fusión, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no de mejoras posteriores. Haga un punto de control después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador vuelve a intentar un nodo posterior.
def _merge_internal(self, remote_path, namespaces=None,
dry_run=False, strategy=ConflictStrategy.NEWER_WINS):
result = MergeResult()
remote_db = vectlite.open(remote_path, dimension=DIMENSION)
try:
ns_list = namespaces or self._discover_namespaces(remote_db)
local_index = self._build_index(self.db, ns_list) for ns in ns_list:
remote_records = remote_db.list(namespace=ns, limit=10_000) for record in remote_records:
rec_id = record["id"]
remote_meta = record.get("metadata", {})
remote_hash = remote_meta.get("content_hash", "") if rec_id in local_index:
local_info = local_index[rec_id] if local_info["content_hash"] == remote_hash:
result.skipped_duplicates += 1 # same content
continue # Conflict -- same ID, different content
conflict = MergeConflict(
record_id=rec_id,
namespace=ns,
local_content=local_info.get("content", ""),
remote_content=remote_meta.get("content", ""),
local_updated=local_info.get("updated_at", ""),
remote_updated=remote_meta.get("updated_at", ""),
)
if not dry_run:
self._resolve_conflict(conflict, record, strategy)
result.conflict_resolutions += 1
result.conflicts.append(conflict)
else:
# New record -- import
if not dry_run:
self._import_record(record, ns)
result.imported += 1
finally:
remote_db.close() return result
Viendo cómo funciona
Al trabajar en la fase de “Ver cómo funciona”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Prefiera unidades pequeñas y verificables a scripts extensos. Cuando un paso falla, el fallo debe referirse a una sola responsabilidad y no a un proceso complicado. Haga una verificación después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.
from agentmem.store.memory_store import MemoryStore
from agentmem.services.teleport import MemoryTeleport
# Agent A: has been working for weeks
store_a = MemoryStore("agent_a.vdb")
tp = MemoryTeleport(store_a.db)# Create a snapshot for Agent B
tp.snapshot("for_agent_b.vdb")# ... send for_agent_b.vdb to Agent B's machine ...# Agent B works with the snapshot, adds new memories
store_b = MemoryStore("for_agent_b.vdb")
store_b.store(Memory(
id="mem_new_finding",
content="The payment API requires idempotency keys for all POST requests",
memory_type=MemoryType.REFERENCE,
importance=0.8,
))
store_b.save()
store_b.close()# ... send for_agent_b.vdb back to Agent A ...# Agent A merges the new knowledge
result = tp.merge("for_agent_b.vdb")
print(result.summary())
# -> Imported: 1 new memories
# Skipped: 47 duplicates
# Conflicts: 0
Al trabajar en la fase de “Ver cómo funciona”, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la demostración a entornos compartidos.
Por qué .vdb como formato de transporte funciona
.vdb funciona mejor como formato de transporte cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos confidenciales y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo plano y tipado; los bloques anidados ocultan qué nodo escribió qué campo y causan interrupciones en la continuación del proceso.
Paso 6: Motor de habilidades
La etapa del Motor de Habilidades del Paso 6 funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mantenga el estado del gráfico simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación posterior.
---
description: Docker best practices
glob_patterns: ["Dockerfile", "docker-compose*.yml"]
trigger_keywords: ["docker", "container", "compose"]
---
## Docker Guidelines
- Always use multi-stage builds
- Pin base image versions
- Never run as root in production
Las tres señales de activación
La etapa de las tres señales de activación funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado. Mantenga el estado del gráfico simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
User message: "deploy the containers to staging"
|
v
+-- SkillEngine.activate() ----------------------------+
| |
| 1. Hybrid search (semantic + keyword via VectLite) |
| -> matches "docker" skill (score 0.91) |
| -> matches "devops" skill (score 0.78) |
| |
| 2. Glob match (if file path detected in message) |
| -> "Dockerfile" mentioned? -> boost "docker" |
| |
| 3. Keyword boost |
| -> "containers" matches docker triggers |
| |
| 4. Weighted combination -> top-k activated skills |
+------------------------------------------------------+
|
v
Injected into system prompt as ## Active skills
La etapa de las tres señales de activación funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos.
El modelo de datos de habilidades
En la fase del modelo de datos The Skill, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Prefiera salidas estructuradas con validación de esquema sobre texto libre cuando el siguiente paso sea código o una llamada a una herramienta.
@dataclass
class Skill:
name: str # "docker", "python", "security"
content: str # Full Markdown instructions
glob_patterns: list[str] # ["Dockerfile", "*.py"]
trigger_keywords: list[str] # ["container", "docker"]
tools_allowed: list[str] # ["docker_build", "docker_push"]
importance: float = 0.7 # Base weight
tags: list[str] = field(default_factory=list)
description: str = ""
Registro: indexación de habilidades en VectLite
Para las habilidades de indexación en la fase de registro, defina los insumos, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en los datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.
NAMESPACE = "__skills__"
class SkillEngine:
def __init__(self, db: vectlite.Database,
max_active: int = 3, min_score: float = 0.30):
self.db = db
self.max_active = max_active
self.min_score = min_score def register(self, skill: Skill) -> str:
skill_id = f"skill_{skill.name}_{uuid.uuid4().hex[:6]}" # Rich embedding text: name + description + keywords + content
embed_text = (
f"{skill.name}: {skill.description or ''} "
f"{' '.join(skill.trigger_keywords)} "
f"{skill.content[:500]}"
)
vector = embed(embed_text)
sparse = vectlite.sparse_terms(embed_text) self.db.upsert(
skill_id, vector, skill.to_metadata(),
sparse=sparse, namespace=NAMESPACE,
)
return skill_id
Activación: la lógica central de coincidencia
En la fase central de coincidencia para la activación, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Incluya la aprobación humana en aquellas tareas que implican gastos o modificaciones en datos de producción. La conexión en tiempo de compilación no equivale a la completitud del proceso empresarial. En la fase central de coincidencia para la activación, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de la versión de demostración a la compartida.
entornos.def activate(self, message: str, file_path: str | None = None,
k: int | None = None) -> list[ActivatedSkill]:
k = k or self.max_active
# 1. Hybrid search via VectLite
vector = embed(message)
sparse = vectlite.sparse_terms(message)
results = self.db.search(
vector,
k=min(k * 3, self.count()), # over-fetch for re-ranking
sparse=sparse, fusion="rrf",
namespace=NAMESPACE,
) # Build candidates with normalized semantic scores
candidates = {}
max_score = max((r.get("score", 0) for r in results), default=1) or 1
for r in results:
meta = r.get("metadata", {})
name = meta.get("name", "")
if name:
candidates[name] = _Candidate(
skill=self._meta_to_skill(meta),
semantic_score=r.get("score", 0) / max_score,
) # 2. Keyword boost
msg_lower = message.lower()
for name, cand in candidates.items():
hits = sum(1 for kw in cand.skill.trigger_keywords
if kw.lower() in msg_lower)
if hits > 0:
cand.keyword_score = min(0.5, hits * 0.2) # 3. Glob match
detected_paths = self._detect_paths(message)
if file_path:
detected_paths.append(file_path)
for name, cand in candidates.items():
for pattern in cand.skill.glob_patterns:
for path in detected_paths:
if fnmatch.fnmatch(os.path.basename(path), pattern):
cand.glob_score = 0.35
break # 4. Weighted combination
activated = []
for name, cand in candidates.items():
final_score = (
cand.semantic_score * 0.55
+ cand.keyword_score * 0.25
+ cand.glob_score * 0.15
+ cand.skill.importance * 0.05
)
if final_score >= self.min_score:
activated.append(ActivatedSkill(
skill=cand.skill,
score=final_score,
activated_by=..., # "semantic", "keyword", "glob", "combined"
)) activated.sort(key=lambda a: a.score, reverse=True) # Gap filter: drop skills scoring < 75% of top skill
if activated:
top = activated[0].score
activated = [a for a in activated if a.score >= top * 0.75] return activated[:k]
Carga de habilidades desde archivos Markdown
Al trabajar en la fase de carga de habilidades desde archivos Markdown, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Haga un punto de control después de pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.
def load_from_directory(self, dir_path: str) -> list[str]:
"""Load skills from .md files with optional YAML-like frontmatter."""
ids = []
for md_file in sorted(Path(dir_path).glob("*.md")):
skill = self._parse_skill_file(md_file)
if skill:
ids.append(self.register(skill))
return ids
---
description: Python best practices
glob_patterns: ["*.py", "*.pyx", "pyproject.toml"]
trigger_keywords: ["python", "pip", "venv", "pytest"]
tools_allowed: ["python_run", "pytest"]
importance: 0.8
tags: ["language", "backend"]
---
## Python Guidelines
- Use type hints for all function signatures
- Prefer pathlib over os.path
- Use pytest with fixtures, not unittest
- Format with black, lint with ruff
Inyección de prompts
Al trabajar en la etapa de inyección de comandos, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no de mejoras posteriores. Almacene en caché las instrucciones estables del sistema y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de problemas.
def format_for_prompt(self, activated: list[ActivatedSkill]) -> str:
if not activated:
return ""
lines = ["\n\n## Active skills\n"]
for act in activated:
lines.append(f"### {act.skill.name}")
if act.skill.tools_allowed:
lines.append(f"**Allowed tools:** {', '.join(act.skill.tools_allowed)}")
lines.append(act.skill.content)
lines.append("") return "\n".join(lines)
Viendo cómo funciona
Al trabajar en la etapa de “Ver cómo funciona”, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Prefiera unidades pequeñas y verificables a scripts extensos. Cuando un paso falla, el fallo debe referirse a una sola responsabilidad y no a un proceso complicado. Haga una verificación después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior.
engine = SkillEngine(db)
engine.register(Skill(
name="docker",
content="## Docker\n- Always use multi-stage builds\n- Pin versions",
glob_patterns=["Dockerfile", "docker-compose*.yml"],
trigger_keywords=["docker", "container", "image", "compose"],
))engine.register(Skill(
name="python",
content="## Python\n- Use type hints\n- Prefer pathlib\n- Use pytest",
glob_patterns=["*.py", "pyproject.toml"],
trigger_keywords=["python", "pip", "venv"],
))# Semantic + keyword match
activated = engine.activate("deploy the containers to staging")
# -> [ActivatedSkill(skill=<docker>, score=0.87, activated_by="combined")]# Glob match
activated = engine.activate("review the Dockerfile")
# -> [ActivatedSkill(skill=<docker>, score=0.91, activated_by="combined")]# Semantic match only (no exact keywords)
activated = engine.activate("optimize the CI/CD pipeline for faster builds")
# -> [ActivatedSkill(skill=<docker>, score=0.52, activated_by="semantic")]
Al trabajar en la etapa de “Ver cómo funciona”, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la demostración a entornos compartidos.
Paso 7: Decadencia de la memoria y resolución de conflictos
La etapa de decadencia de la memoria del Paso 7 funciona mejor cuando se trata como una superficie medible. Capture una transcripción clave, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
La fórmula de decadencia
La etapa de la fórmula de degradación funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mantenga el estado del gráfico simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación después de las interrupciones.
new_importance = current_importance * 2^(-elapsed_hours / half_life)
The MemoryDecayEngine
La etapa MemoryDecayEngine funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando falla un paso, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Mantenga el estado del grafo simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
class MemoryDecayEngine:
def __init__(self, store: MemoryStore,
half_life_hours: float = 168.0, # 1 week
tombstone_threshold: float = 0.05,
conflict_threshold: float = 0.80):
self.store = store
self.half_life_hours = half_life_hours
self.tombstone_threshold = tombstone_threshold
self.conflict_threshold = conflict_threshold
Aplicando el deterioro
La etapa de decadencia aplicada funciona mejor cuando se trata como una superficie medible. Capture una transcripción exitosa, un caso de fallo y la nota de reversión antes de ampliar el alcance. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Mantenga el estado del gráfico plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación posterior.
def apply_decay(self, now: datetime | None = None) -> DecayReport:
"""Run exponential decay over all memories."""
now = now or datetime.now(timezone.utc)
report = DecayReport()
for mtype in MemoryType:
memories = self.store.recall_by_type(mtype, k=500)
for mem in memories:
if mem.superseded_by:
continue # already replaced report.total_scanned += 1
last_accessed = self._parse_time(
mem.last_accessed or mem.created_at
)
elapsed_hours = max(
(now - last_accessed).total_seconds() / 3600.0, 0.0
) # The core formula
decay_factor = math.pow(
2.0, -elapsed_hours / self.half_life_hours
)
new_importance = mem.importance * decay_factor if new_importance < self.tombstone_threshold:
# Below threshold -> auto-delete
self.store.update_importance(mem.id, 0.0)
self.store.forget(mem.id)
self.store.compact()
report.pruned += 1
elif abs(new_importance - mem.importance) > 0.001:
# Importance decayed -> update
self.store.update_importance(mem.id, new_importance)
report.decayed += 1
else:
report.untouched += 1 return report
Detección de conflictos
La etapa de detección de conflictos funciona mejor cuando se trata como una superficie medible. Capture un transcripto ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Mantenga el estado del grafo en formato plano y tipado de forma consistente. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
def check_conflicts(self, new_memory: Memory) -> list[ConflictResult]:
"""Find existing memories that contradict the new one."""
# Get candidates via VectLite search
similar = self.store.find_similar(
new_memory.content,
memory_type=new_memory.memory_type,
k=5, min_score=0.0,
) new_hash = self.store._hash(new_memory.content)
new_vector = embed(new_memory.content) conflicts = []
for existing, _ in similar:
if existing.id == new_memory.id:
continue
if existing.superseded_by:
continue # Skip exact duplicates (same hash = not a conflict)
if self.store._hash(existing.content) == new_hash:
continue # Compute direct cosine similarity
existing_vector = embed(existing.content)
cosine_sim = self._cosine_similarity(new_vector, existing_vector) if cosine_sim >= self.conflict_threshold:
conflicts.append(ConflictResult(
existing=existing,
new_content=new_memory.content,
similarity=cosine_sim,
resolution=self.default_resolution,
)) return conflicts
Almacenamiento con resolución de conflictos
La tienda con etapa de resolución de conflictos funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo plano y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
def store_with_conflict_check(self, memory: Memory,
resolution=None) -> tuple[str, list]:
resolution = resolution or self.default_resolution
conflicts = self.check_conflicts(memory)
if not conflicts:
return self.store.store(memory), [] for conflict in conflicts:
conflict.resolution = resolution if resolution == ConflictResolution.SUPERSEDE:
mem_id = self.store.store(memory)
self.store.mark_superseded(conflict.existing.id, mem_id) elif resolution == ConflictResolution.KEEP_BOTH:
mem_id = self.store.store(memory) elif resolution == ConflictResolution.KEEP_EXISTING:
mem_id = "" # don't store the new one return mem_id, conflicts
Viendo cómo funciona
La etapa de ver cómo funciona el sistema rinde mejor cuando se trata como una superficie medible. Capture un caso exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Mantenga el estado de los gráficos simple y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación del proceso.
store = MemoryStore("decay_demo.vdb")
decay = MemoryDecayEngine(store)
# Store two memories
store.store(Memory(id="m1", content="The database is PostgreSQL 14",
memory_type=MemoryType.PROJECT, importance=0.7))# Later, the project migrates
new_mem = Memory(id="m2", content="The database is PostgreSQL 16",
memory_type=MemoryType.PROJECT, importance=0.8)mem_id, conflicts = decay.store_with_conflict_check(new_mem)
print(conflicts[0])
# -> Conflict: [project] sim=0.953 -> supersede
# m1 is now marked as superseded by m2# Weeks later, run decay
report = decay.apply_decay()
print(report)
# -> Decay: 15 scanned, 8 decayed, 2 pruned, 5 untouched
Paso 8: Memoria consciente del árbol de trabajo
La etapa de memoria consciente del Worktree de Paso 8 funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado. Mantenga el estado del grafo simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y causan interrupciones en la continuación del proceso. La etapa de memoria consciente del Worktree de Paso 8 funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de una demostración a entornos compartidos.
La estrategia de namespace
En la fase de estrategia de namespace, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Coloque la aprobación humana en las conexiones que generan gastos o modifican datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.
# Global -- shared across all branches
GLOBAL_TYPES = {MemoryType.USER, MemoryType.FEEDBACK}
# Scoped -- isolated per branch
SCOPED_TYPES = {MemoryType.PROJECT, MemoryType.REFERENCE}
Memory Type Branch VectLite Namespace
-----------------------------------------------------
user (any) "user"
feedback (any) "feedback"
project main "project:main"
project feature/auth "project:feature/auth"
reference main "reference:main"
reference feature/auth "reference:feature/auth"
The WorktreeManager
Para la etapa The WorktreeManager, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Incluya la aprobación humana en aquellos procesos que generen gastos o modifiquen datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.
BRANCH_SEP = ":"
class WorktreeManager:
def __init__(self, db: vectlite.Database,
branch: str | None = None):
self.db = db
self._branch = branch or self._detect_git_branch() def resolve_namespace(self, memory_type: MemoryType) -> str:
"""Map a MemoryType to its VectLite namespace."""
if memory_type in GLOBAL_TYPES:
return memory_type.value # "user", "feedback"
return f"{memory_type.value}{BRANCH_SEP}{self._branch}"
# -> "project:main", "reference:feature/auth" def recall_namespaces(self) -> list[str]:
"""All namespaces to search for the current branch."""
namespaces = []
for mt in MemoryType:
if mt in GLOBAL_TYPES:
namespaces.append(mt.value)
else:
namespaces.append(f"{mt.value}{BRANCH_SEP}{self._branch}")
return namespaces
# -> ["user", "feedback", "project:main", "reference:main"]
Detección automática de la rama git
Para la detección automática de la etapa de rama git, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.
@staticmethod
def _detect_git_branch() -> str:
"""Auto-detect the current git branch."""
try:
result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
capture_output=True, text=True, timeout=5,
)
if result.returncode == 0:
branch = result.stdout.strip()
if branch and branch != "HEAD":
return branch
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
return "main" # fallback
Para la detección automática de la etapa de rama git, defina las entradas, el propietario del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos.
Cambiar ramas
Al trabajar en la etapa de conmutación de ramas, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Haga un punto de control después de los pasos costosos. La reanudación no debe volver a facturar la misma llamada al LLM cuando un operador vuelve a intentar un nodo posterior.
def switch_branch(self, branch: str) -> str:
"""Switch to a different branch context. Returns previous branch."""
prev = self._branch
self._branch = branch
return prev
Fusión de ramas (como git merge para memorias)
Al trabajar con la función de combinación de ramas como git stage, primero escribe el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documenta junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Haz un punto de control después de los pasos costosos. Resume no debe volver a facturar la misma llamada al LLM cuando un operador vuelve a intentar un nodo posterior.
def merge_branch(self, source: str,
into: str | None = None) -> BranchMergeResult:
"""Merge branch-scoped memories from source into target."""
target = into or self._branch
result = BranchMergeResult(source=source, target=target)
for mt in SCOPED_TYPES: # only project + reference
src_ns = f"{mt.value}{BRANCH_SEP}{source}"
tgt_ns = f"{mt.value}{BRANCH_SEP}{target}" src_records = self.db.list(namespace=src_ns, limit=500) # Build dedup set from target
tgt_records = self.db.list(namespace=tgt_ns, limit=500)
tgt_hashes = {
r.get("metadata", {}).get("content_hash", "")
for r in tgt_records
} for record in src_records:
content_hash = record.get("metadata", {}).get("content_hash", "")
if content_hash in tgt_hashes:
result.skipped_duplicates += 1
continue # Import with new ID into target namespace
new_id = f"mem_{uuid.uuid4().hex[:12]}"
new_meta = dict(record.get("metadata", {}))
new_meta["merged_from"] = source self.db.upsert(
new_id, record["vector"], new_meta,
sparse=record.get("sparse", {}),
namespace=tgt_ns,
)
result.imported += 1 return result
Diferencia y eliminación
Al trabajar en la fase de diferencias y eliminación, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Haga una verificación después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intenta nuevamente un nodo posterior. Al trabajar en la fase de diferencias y eliminación, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos.
def diff_branch(self, source: str,
target: str | None = None) -> BranchDiffResult:
"""Like git diff -- preview without modifying anything."""
# Same logic as merge_branch but without writing
...
def delete_branch(self, branch: str) -> int:
"""Delete all branch-scoped memories. Global types untouched."""
total_deleted = 0
for mt in SCOPED_TYPES:
ns = f"{mt.value}{BRANCH_SEP}{branch}"
records = self.db.list(namespace=ns, limit=500)
for record in records:
self.db.delete(record["id"])
total_deleted += 1
if total_deleted > 0:
self.db.compact()
return total_deleted
Viendo todo el ciclo de vida
La etapa de “Ver todo el ciclo de vida” funciona mejor cuando se trata como una superficie medible. Capture una transcripción clave, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el grafo. Mantenga el estado del grafo simple y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y causan interrupciones en la continuación después de las interrupciones.
wt = WorktreeManager(db, branch="main")
# Store memories on main
store.store(Memory(content="FastAPI backend", memory_type=MemoryType.PROJECT))
# -> stored in namespace "project:main"store.store(Memory(content="I prefer type hints", memory_type=MemoryType.USER))
# -> stored in namespace "user" (global)# Switch to feature branch
wt.switch_branch("feature/auth")store.store(Memory(content="OAuth2 with PKCE for mobile clients",
memory_type=MemoryType.PROJECT))
# -> stored in namespace "project:feature/auth"# Recall on feature/auth -- sees global + branch-scoped
namespaces = wt.recall_namespaces()
# -> ["user", "feedback", "project:feature/auth", "reference:feature/auth"]
# "I prefer type hints" is visible (global)
# "FastAPI backend" is NOT visible (it's in project:main)
# "OAuth2 with PKCE" is visible (feature branch)# Merge feature into main
result = wt.merge_branch("feature/auth", into="main")
# -> Imported: 1, Skipped: 0# Now on main, "OAuth2 with PKCE" is also visible
wt.switch_branch("main")
# -> recall sees: "FastAPI backend" + "OAuth2 with PKCE" + all globals# Clean up
wt.delete_branch("feature/auth")
# -> project:feature/auth and reference:feature/auth emptied
Estructura final del proyecto
La etapa final de estructuración del proyecto funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el de recuperación juntos. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son ajustes realizados posteriormente. Mantenga el estado de los gráficos simple y tipado. Los bloques anidados ocultan qué nodo escribió qué campo y provocan interrupciones en la continuación después de las interrupciones.
agentmem/
core/
memory_types.py # MemoryType + Memory dataclass
embedder.py # embed() / embed_batch()
store/
memory_store.py # MemoryStore -- VectLite persistence
services/
extractor.py # Background memory extraction
session.py # Session summary
compaction.py # 4-level cascading compaction
swarm.py # Multi-agent swarm
teleport.py # Snapshot / merge / diff
skills.py # Skill engine
decay.py # Exponential decay + conflict resolution
worktree.py # Branch-scoped namespaces
agent.py # Agent -- the full loop
mcp_server.py # MCP server (bonus)
La imagen completa
La etapa The Complete Picture funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado. Mantenga el estado del gráfico simple y tipado; los bloques anidados ocultan qué nodo escribió qué campo y causan interrupciones en la continuación del proceso. La etapa The Complete Picture funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos.
User message
|
v
+-- Agent.chat() ----------------------------------------+
| |
| RECALL: hybrid search (HNSW + BM25 + RRF) [P1] |
| SKILLS: semantic + keyword + glob activation [P2] |
| BUILD PROMPT: memories + skills + session summary |
| COMPACT: snip -> micro -> collapse -> auto [P1] |
| LLM RESPONSE |
| EXTRACT: background sub-agent learns facts [P1] |
| SESSION: update summary if threshold crossed [P1] |
| |
| TELEPORT: snapshot / merge / diff [P2] |
| DECAY: exponential forgetting + conflict detect [P2] |
| WORKTREE: branch-scoped namespaces [P2] |
| SWARM: multi-agent via shared namespaces [P1] |
| |
| Storage: single .vdb file (VectLite) |
+--------------------------------------------------------+
Qué permite VectLite
Para la etapa que permite What VectLite, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea desde un punto de control conocido sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Coloque la aprobación humana en las acciones que generan gastos o modifican datos de producción. La conexión establecida en tiempo de compilación no equivale a la completitud del proceso empresarial.
Comparación con Claude Code
Para determinar cómo se compara con la etapa correspondiente, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Incluya la aprobación humana en aquellos casos que impliquen gastos o cambios en los datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.
Pruébalo
En la fase de “Pruébalo”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Incluya la aprobación humana en aquellas tareas que implican gastos o modificaciones en datos de producción. La configuración en tiempo de compilación no equivale a una solución completa para el negocio.
pip install vectlite sentence-transformers
git clone https://github.com/mcsedition-hub/agentmem-vectlite.git
cd agentmem-vectlite
python demo.py
En la fase de “Pruébalo”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad sobre los costos desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos.
Lista de verificación operativa
En la fase de lista de verificación operativa, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto.
Considere esta fase como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas.
Implique la aprobación humana en aquellas tareas que implican gastos o modificaciones en datos de producción. La configuración en tiempo de compilación no equivale a la completitud del proceso empresarial.
Escriba un breve manual de operaciones: cómo rotar claves, cómo vaciar la cola y cómo revertir la última inserción.