← Volver al catálogo
Agentes IASistemaAvanzadoEn el pase

Gestión de Contexto Persistente para Agentes IA (OpenViking)

Sistema de memoria jerárquica para agentes IA basado en sistema de archivos: organiza contexto por proyecto, tarea y subtarea con persistencia entre sesiones y capacidad de autoevolución. Incluye implementación Python lista para integrar con LangChain u otros frameworks de agentes.

Comprobando acceso…

Incluida en el Pase · para Python, LangChain, Claude Code

// resultado_de_ejemplo

""" NutriFlow Agent — Memoria Persistente con OpenViking ContextManager

Python 3.9+ compatible (uses List/Tuple from typing for older runtimes)

Cliente: NutriFlow SaaS (clínicas y dietistas) Stack: Next.js 14 · PostgreSQL 15 · Stripe · Vercel Agente: Asistente dev con memoria jerárquica entre sesiones

Este módulo inicializa el árbol de contexto para el agente de NutriFlow, define las herramientas LangChain para que el agente pueda leer/escribir/buscar contexto, y muestra cómo el agente aprende de sus errores (self-evolution).

Uso: python resultado.py

Dependencias (core no necesita nada externo): pip install langchain-core langchain-openai # sólo para integración LangChain """

from future import annotations

import os import json from pathlib import Path from datetime import datetime, timezone from typing import Optional, List, Tuple

─────────────────────────────────────────────

CORE: ContextManager

─────────────────────────────────────────────

class ContextManager: """Gestión de contexto jerárquico basado en sistema de archivos."""

def __init__(self, root: str = "./context"):
    self.root = Path(root)
    self.root.mkdir(parents=True, exist_ok=True)

def read(self, path: str) -> Optional[str]:
    full = self.root / path
    return full.read_text(encoding="utf-8") if full.is_file() else None

def write(self, path: str, content: str, metadata: Optional[dict] = None):
    full = self.root / path
    full.parent.mkdir(parents=True, exist_ok=True)
    header = ""
    if metadata:
        meta = {**metadata, "updated": datetime.now(timezone.utc).isoformat()}
        header = f"<!-- meta: {json.dumps(meta)} -->\n\n"
    full.write_text(header + content, encoding="utf-8")

def list(self, path: str = "") -> List[str]:
    full = self.root / path
    if not full.is_dir():
        return []
    return [str(p.relative_to(self.root)) for p in sorted(full.rglob("*")) if p.is_file()]

def delete(self, path: str):
    full = self.root / path
    if full.is_file():
        trash = self.root / ".trash" / path
        trash.parent.mkdir(parents=True, exist_ok=True)
        full.rename(trash)

def search(self, query: str, path: str = "") -> List[Tuple[str, str]]:
    results = []
    for filepath in self.list(path):
        content = self.read(filepath)
        if content and query.lower() in content.lower():
            idx = content.lower().index(query.lower())
            snippet = content[max(0, idx - 50):idx + len(query) + 50]
            results.append((filepath, snippet))
    return results

─────────────────────────────────────────────

SCOPED CONTEXT DELIVERY

─────────────────────────────────────────────

class ScopedContext: """Entrega contexto según el alcance actual del agente."""

SCOPE_RULES = {
    "project": [
        "project/README.md",
        "memory/facts.md",
        "memory/preferences.md",
        "skills/coding-style.md",
    ],
    "task": ["task/current.md"],
    "subtask": [],
}

def __init__(self, ctx: ContextManager):
    self.ctx = ctx

def get_context(self, scope: str = "task", subtask_id: Optional[str] = None) -> str:
    parts = []
    for path in self.SCOPE_RULES["project"]:
        content = self.ctx.read(path)
        if content:
            parts.append(f"## {path}\n{content}")
    if scope in ("task", "subtask"):
        for path in self.SCOPE_RULES["task"]:
            content = self.ctx.read(path)
            if content:
                parts.append(f"## {path}\n{content}")
    if scope == "subtask" and subtask_id:
        content = self.ctx.read(f"task/subtasks/{subtask_id}.md")
        if content:
            parts.append(f"## Subtask: {subtask_id}\n{content}")
    lessons = self.ctx.read("memory/lessons.md")
    if lessons:
        parts.append(f"## Lecciones Aprendidas\n{lessons}")
    return "\n\n---\n\n".join(parts)

─────────────────────────────────────────────

SELF-EVOLVING: el agente actualiza su memoria

─────────────────────────────────────────────

class EvolvingAgent: """Agente que actualiza su contexto tras cada tarea completada."""

def __init__(self, ctx: ContextManager):
    self.ctx = ctx

def complete_task(self, task: str, result: str, success: bool, lesson: str = "", facts: str = ""):
    """Cierra una tarea, archiva el contexto y guarda lo aprendido."""
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M")

    # Archivar tarea actual
    current = self.ctx.read("task/current.md")
    if current:
        self.ctx.write(f"task/history/{timestamp}.md", current,
                       metadata={"task": task, "success": success})

    # Registrar lección si falló
    if not success and lesson:
        existing = self.ctx.read("memory/lessons.md") or ""
        self.ctx.write("memory/lessons.md",
                       f"{existing}\n\n### {timestamp} — FALLO\n**Tarea:** {task}\n**Lección:** {lesson}")

    # Guardar nuevos hechos descubiertos
    if facts and "NONE" not in facts.upper():
        existing = self.ctx.read("memory/facts.md") or ""
        self.ctx.write("memory/facts.md",
                       f"{existing}\n\n### Descubierto {timestamp}\n{facts}")

    print(f"  ✔  Tarea archivada: {timestamp}.md  |  éxito={success}")

─────────────────────────────────────────────

LANGCHAIN TOOLS (sin LLM real, sólo definición)

─────────────────────────────────────────────

def build_langchain_tools(ctx: ContextManager): """ Devuelve las tres herramientas LangChain que el agente NutriFlow usará. Para registrarlas: agent = create_react_agent(llm, tools=[...]) """ try: from langchain_core.tools import tool

    @tool
    def read_context(path: str) -> str:
        """Lee un archivo de contexto para recordar info del proyecto, decisiones o lecciones."""
        return ctx.read(path) or f"Sin contexto en {path}"

    @tool
    def write_context(path: str, content: str) -> str:
        """Guarda aprendizajes, decisiones o hechos en el contexto persistente."""
        ctx.write(path, content, metadata={"source": "agent"})
        return f"Guardado en {path}"

    @tool
    def search_context(query: str) -> str:
        """Busca en todos los archivos de contexto información relevante."""
        results = ctx.search(query)
        return "\n".join(f"[{p}] ...{s}..." for p, s in results[:5]) or "Sin resultados."

    return [read_context, write_context, search_context]

except ImportError:
    print("  ⚠  langchain-core no instalado — herramientas omitidas (core funciona sin él)")
    return []

─────────────────────────────────────────────

DEMO: Inicialización del contexto de NutriFlow

─────────────────────────────────────────────

def init_nutriflow_context(ctx: ContextManager): """Carga el árbol de contexto inicial para el agente de NutriFlow."""

ctx.write("project/README.md", """\

NutriFlow SaaS

Plataforma B2B de planes nutricionales para clínicas y dietistas.

Stack

  • Frontend: Next.js 14 (App Router, React Server Components)
  • Base de datos: PostgreSQL 15 con Row Level Security habilitado
  • Pagos: Stripe Billing (suscripciones mensuales/anuales)
  • Email: Resend + React Email templates
  • Deploy: Vercel (preview + prod), Railway (BD)

Arquitectura

  • API Routes en /app/api/*

  • Autenticación: Clerk (no NextAuth)

  • ORM: Drizzle (no Prisma)

  • Validación: Zod en client + server """, metadata={"source": "init"})

    ctx.write("memory/facts.md", """\

Hechos conocidos sobre NutriFlow

Base de datos

  • Usar UUIDs v7 (no v4) — función gen_uuid_v7() disponible como extensión
  • Row Level Security activo en todas las tablas de clientes
  • Índices en clinic_id y created_at para la mayoría de queries
  • Bug conocido: endpoint /api/plans devuelve fechas en local time en lugar de UTC

Infraestructura

  • Variables de entorno en Vercel: nunca en .env.local en producción
  • Edge Runtime NO soportado — usar Node.js runtime para Drizzle
  • Límite de ejecución en Vercel: 10s en Hobby, 60s en Pro

Equipo

  • 3 desarrolladores: Ana (lead), Marcos (backend), Lucia (frontend)

  • Reuniones de sprint los lunes a las 10h

  • Tickets en Linear, no Jira """, metadata={"source": "init"})

    ctx.write("memory/preferences.md", """\

Preferencias del equipo NutriFlow

Testing

  • Vitest (no Jest) para unit tests
  • Playwright para e2e
  • Coverage mínimo: 80% en lógica de negocio

Código

  • TypeScript strict mode obligatorio
  • Server Components por defecto; use client sólo si hay interactividad
  • Fetch nativo (no axios)
  • Error handling con Result<T, E> pattern (no try/catch en lógica)

Revisiones

  • PRs con descripción en español

  • Squash merge en main

  • Rama feature/, fix/, chore/* """, metadata={"source": "init"})

    ctx.write("skills/coding-style.md", """\

Estilo de código NutriFlow

Naming

  • camelCase para variables y funciones
  • PascalCase para componentes y tipos
  • SCREAMING_SNAKE_CASE para constantes de entorno

SQL

  • Siempre filtrar por clinic_id antes de otros predicados (aprovecha RLS)
  • EXPLAIN ANALYZE en cualquier query que toque >10k filas
  • Usar CTEs para queries de >3 joins

Estructura de archivos

  • /app/api/[recurso]/route.ts — endpoints

  • /lib/db/[recurso].ts — queries Drizzle

  • /lib/schemas/[recurso].ts — esquemas Zod """, metadata={"source": "init"})

    ctx.write("task/current.md", """\

Tarea Actual: Optimizar query de historial de planes

Descripción

La query en /lib/db/plans.ts:getPatientHistory() tarda >2s en clínicas con >500 pacientes.

Contexto relevante

  • Tabla plan_history tiene 4M filas en producción
  • Índice actual sólo en patient_id, falta índice compuesto con clinic_id
  • RLS filtra por clinic_id pero DESPUÉS del seq scan

Objetivo

Reducir p95 a <200ms añadiendo índice compuesto y reescribiendo el CTE.

Estado

  • EXPLAIN ANALYZE de la query actual

  • Proponer nuevo índice

  • Reescribir CTE

  • Medir mejora en staging """, metadata={"source": "sprint-23"})

    print(" ✔ Árbol de contexto NutriFlow inicializado")

─────────────────────────────────────────────

ENTRYPOINT

─────────────────────────────────────────────

def main(): base = Path(file).parent / "context_nutriflow" ctx = ContextManager(str(base)) scoped = ScopedContext(ctx) agent = EvolvingAgent(ctx)

print("\n" + "═" * 60)
print("  NutriFlow Agent — OpenViking Context Demo")
print("═" * 60)

# 1. Inicializar contexto del proyecto
print("\n[1] Inicializando contexto del proyecto…")
init_nutriflow_context(ctx)

# 2. Listar todos los archivos de contexto
print("\n[2] Árbol de contexto generado:")
for f in ctx.list():
    print(f"    📄 {f}")

# 3. Obtener contexto con scope 'task'
print("\n[3] Contexto cargado para scope=task (extracto):")
task_ctx = scoped.get_context(scope="task")
lines = task_ctx.split("\n")
for line in lines[:30]:
    print(f"    {line}")
print(f"    … (+{len(lines) - 30} líneas más)")

# 4. Búsqueda de contexto
print("\n[4] Búsqueda: 'UUID'")
results = ctx.search("UUID")
for path, snippet in results:
    print(f"    [{path}] …{snippet.strip()}…")

# 5. El agente completa la tarea con aprendizajes
print("\n[5] Agente completa la tarea de optimización:")
agent.complete_task(
    task="Optimizar query getPatientHistory()",
    result="Añadido índice compuesto (clinic_id, patient_id, created_at DESC). P95 bajó de 2.1s a 87ms.",
    success=True,
    facts="- Índice compuesto (clinic_id, patient_id, created_at DESC) reduce el seq scan 96% en plan_history\n- RLS en Postgres aplica AFTER el plan de ejecución; los índices deben incluir la columna RLS"
)

# 6. Mostrar hechos actualizados
print("\n[6] Hechos actualizados en memory/facts.md:")
facts = ctx.read("memory/facts.md")
if facts:
    for line in facts.split("\n")[-12:]:
        print(f"    {line}")

# 7. Herramientas LangChain disponibles
print("\n[7] Herramientas LangChain disponibles para el agente:")
tools = build_langchain_tools(ctx)
if tools:
    for t in tools:
        print(f"    🔧 {t.name}: {t.description[:60]}…")
else:
    print("    (instalar langchain-core para activar herramientas)")

print("\n" + "═" * 60)
print(f"  Contexto guardado en: {base.resolve()}")
print("  El agente recordará todo esto en la próxima sesión.")
print("═" * 60 + "\n")

if name == "main": main()

// qué_hace

Gestiona el contexto de agentes IA de forma jerárquica y persistente usando un sistema de archivos, con scoping por niveles (proyecto, tarea, subtarea) y autoactualización de memoria.

// cómo_lo_hace

Implementa un ContextManager en Python puro que lee/escribe/busca archivos de contexto estructurados en árbol, con integración LangChain como herramientas de agente y lógica de autoevolución que extrae lecciones y hechos tras cada tarea.

// ejemplo_de_uso

Para agentes IA de larga duración que pierden contexto entre sesiones y necesitan recordar decisiones previas. Ej.: un agente de análisis financiero recuerda qué empresas ya procesó, las lecciones aprendidas y el estado de cada subtarea entre reinicios del servidor.

// plataformas

PythonLangChainClaude CodeOpenAI CodexGemini CLICursor
Categoría
Agentes IA
Tipo
Sistema
Nivel
Avanzado
Licencia
Apache-2.0
Seguridad
seguro · riesgo bajo
Versión
1.0.0

// opiniones_de_la_comunidad

Opiniones

Cargando opiniones…