OpenClawNet

Sesión 3 — Habilidades, Almacenamiento y Memoria

Serie Microsoft Reactor · ~75 min · .NET Intermedio

Mismo agente. Diferente personalidad. Disco más seguro. Memoria más larga.


Bruno Capuano — Principal Cloud Advocate, Microsoft
github.com/elbruno · @elbruno

Pablo Piovano — Microsoft MVP
linkedin.com/in/ppiova

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Dónde quedaron las Sesiones 1–2

  • Sesión 1 — Aplicación Aspire, IAgentProvider, streaming NDJSON, SQLite
  • Sesión 2ITool + MCP, una puerta de aprobación, un runtime
  • 5 herramientas in-process, 5 servidores MCP incluidos, 5 demos
  • 3 ataques bloqueados: path traversal, inyección de comandos, SSRF
  • aspire start → chat con agente usando herramientas en 30 segundos

Hoy agregamos: personalidad, almacenamiento seguro y una hoja de ruta de memoria.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

El alcance de hoy, en una diapositiva

  1. 🛠️ Herramientas extra — Ollama Monitor + Aspire Monitor en tu barra de tareas
  2. 🔧 Actualizaciones de tool-calling — Alineación con OpenAI, sanitizadores, refactorizaciones
  3. 🎭 Sistema de habilidades — Personalidad en Markdown, hot-reload, por agente
  4. 💾 Refactorización de almacenamientoC:\openclawnet\, ISafePathResolver, H-1..H-8
  5. 🧠 Hoja de ruta de memoria — ventanas de contexto, resumenes, embeddings
  6. 🧪 Demos de consolaaspire start, /api/skills, skill drop-in manual
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

El modelo mental de la sesión-3

Comportamiento

  • Las habilidades definen qué hace el agente
  • Markdown + YAML, sin código
  • Hot-reload, habilitación por agente

Límites

  • El almacenamiento define dónde escribe el agente
  • Una raíz: C:\openclawnet\
  • Contención de cierre seguro (fail-closed)

Comportamiento + Límites = un agente personalizado en el que puedes confiar en disco.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

🛠️ Parte 1 — Herramientas Extra

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Por qué necesitábamos herramientas extra

  • Ollama muere silenciosamente → "¿por qué mi agente está lento?"
  • El dashboard de Aspire es genial, pero enterrado en una pestaña del navegador
  • Desarrollo de LLM local = 3 terminales + 2 dashboards abiertos todo el día
  • Los demos se caen en vivo porque algo no estaba corriendo

Queríamos estado visible de un vistazo — no "ve a abrir una pestaña".

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Ollama Monitor — qué es

  • 📦 Herramienta dotnet de NuGetdotnet tool install -g OpenClawNet.Tools.OllamaMonitor
  • 🟢 Ícono de bandeja del sistema con salud codificada por colores
  • 📊 Métricas en tiempo real — modelo cargado, capas GPU, tokens/seg
  • 🔔 Notificaciones toast cuando Ollama se cae
  • 🪟 Windows primero (funciona en bandejas macOS/Linux también)
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Ollama Monitor — características

  • ⚡ Sondeos de salud cada 5s (configurable)
  • 📈 Últimos 60 segundos de rendimiento en un sparkline
  • 🧠 Modelo cargado + tamaño + cuantización
  • 🎯 Contador de solicitudes activas
  • 🛑 Detener / iniciar rápido desde el menú de la bandeja

Códigos de color

  • 🟢 Saludable, < 200ms latencia
  • 🟡 Saludable, > 200ms latencia
  • 🟠 Degradado (timeouts)
  • 🔴 Inalcanzable

Disparadores de toast

  • Salida del proceso
  • Falla del sondeo de salud (3x)
  • Descarga del modelo
  • Recuperación
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Ollama Monitor — instalación

# Instalar una vez
dotnet tool install -g OpenClawNet.Tools.OllamaMonitor

# Ejecutar bajo demanda
ollama-monitor

# O auto-iniciar con Windows
ollama-monitor --autostart
  • La configuración vive en %APPDATA%\OpenClawNet\OllamaMonitor\settings.json
  • Sobrescribe la URL de Ollama con --endpoint http://host:11434
  • Los logs van a %LOCALAPPDATA%\OpenClawNet\OllamaMonitor\logs\
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Ollama Monitor — demo

🟢 Ollama (llama3.2:3b)            127.0.0.1:11434
   Loaded model:   llama3.2:3b-instruct-q4_K_M
   Size:           2.0 GB
   GPU layers:     33 / 33
   Throughput:     78 tok/s  ▁▂▅▇▇▆▅▃▂▁
   Active reqs:    2
   ───────────────────────────
   Open dashboard      [Ctrl+D]
   Restart Ollama      [Ctrl+R]
   Settings…
   Exit

Clic derecho en el ícono de la bandeja → ve el latido del agente.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Aspire Monitor — qué es

  • 📦 Herramienta dotnet de NuGetdotnet tool install -g OpenClawNet.Tools.AspireMonitor
  • 🟢 Compañero de bandeja del sistema de Windows para aspire start
  • 📁 Vigilancia de carpeta de trabajo — auto-detecta qué aplicación estás corriendo
  • 📌 Mini ventana de recursos fijados — tus endpoints favoritos
  • ⏯️ Controles de Iniciar / Detener desde la bandeja
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Aspire Monitor — características

  • 🔍 Auto-descubrimiento de proyectos *.AppHost en la carpeta vigilada
  • 📊 Estado por recurso (corriendo, iniciando, fallido) con logs de un clic
  • 📌 Mini ventana fijada — mantén 3-5 endpoints siempre visibles
  • 🎯 URL del dashboard copiada al portapapeles al pasar el mouse
  • 🔁 Reiniciar cualquier recurso individual sin reiniciar el host

Qué vigila

  • stdout/stderr del AppHost
  • API del dashboard de Aspire
  • Endpoints de salud de recursos
  • Cambios de archivos en la carpeta de trabajo

Qué expone

  • ✅ Conteo de recursos corriendo
  • ⏱️ Tiempo de inicio
  • 🌐 URLs de endpoint (HTTP + HTTPS)
  • ⚠️ Eventos de crash / reinicio
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Aspire Monitor — instalación

# Instalar una vez
dotnet tool install -g OpenClawNet.Tools.AspireMonitor

# Vigilar la carpeta actual
aspire-monitor

# Vigilar una carpeta específica
aspire-monitor --folder C:\src\openclawnet

# Auto-iniciar con el AppHost
aspire-monitor --auto
  • La configuración vive en %APPDATA%\OpenClawNet\AspireMonitor\settings.json
  • Los ítems fijados sobreviven reinicios
  • Múltiples instancias = múltiples carpetas vigiladas
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Aspire Monitor — demo

🟢 OpenClawNet.AppHost (3 of 3 running)
   ├── 🟢 gateway        https://localhost:7234
   ├── 🟢 ollama         http://localhost:11434
   └── 🟢 dashboard      https://localhost:17000
   ─────────────────────────────────────
   📌 Pinned
      Gateway · /chat
      Skills page
      Aspire dashboard
   ─────────────────────────────────────
   Start    Stop    Restart    Logs…

Un ícono de bandeja, una carpeta vigilada, tres endpoints fijados.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

🔧 Parte 2 — Actualizaciones de Tool-Calling

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

¿Por qué tocar tool-calling en absoluto?

  • La Sesión 2 lanzó un stack funcional — pero...
  • Diferentes proveedores esperan diferentes formatos de llamada de herramientas
  • FileSystemTool había crecido a un solo archivo de 600 líneas
  • Los sanitizadores estaban duplicados en 3 herramientas
  • "Funciona en Ollama" ≠ "funciona en Azure OpenAI"

Objetivo: un formato canónico, sanitizadores reutilizables, herramientas más pequeñas.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Alineación con formato OpenAI

// Llamada de herramienta canónica (coincide con OpenAI / Azure OpenAI / Foundry)
{
  "id": "call_abc123",
  "type": "function",
  "function": {
    "name": "file_system",
    "arguments": "{\"action\":\"read\",\"path\":\"README.md\"}"
  }
}
  • Los argumentos son una cadena JSON, no un objeto (trampa heredada)
  • id es opaco — los proveedores lo generan, nosotros solo lo devolvemos
  • type es siempre "function" para herramientas v1
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Qué cambió desde la Sesión 2

Componente Antes Después
Envoltura de resultado de herramienta {ok, value} ToolResult.Ok/Fail (record)
Análisis de argumentos ad-hoc por herramienta JsonSchema + JsonElement
Formas de error cadenas ToolError(code, message)
Sanitizadores duplicados registro IInputSanitizer
FileSystemTool 1 archivo, 600 LOC 5 archivos, ~120 LOC cada uno

Mismo contrato público. Internos más limpios. Mismos eventos NDJSON.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Refactorización de FileSystemTool

OpenClawNet.Tools/FileSystem/
├── FileSystemTool.cs         (orquestador — 110 LOC)
├── Operations/
│   ├── ReadOperation.cs
│   ├── WriteOperation.cs
│   ├── ListOperation.cs
│   └── DeleteOperation.cs
└── Validation/
    ├── PathValidator.cs       (delega a ISafePathResolver)
    └── ContentValidator.cs    (tamaño, codificación)
  • Una operación por archivo — responsabilidad única
  • Toda resolución de rutas fluye a través de ISafePathResolver (más en la Parte 4)
  • Las pruebas ahora reflejan el diseño de archivos
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Sanitizadores de herramientas — el nuevo contrato

public interface IInputSanitizer<TInput>
{
    SanitizationResult<TInput> Sanitize(TInput input);
}

public sealed record SanitizationResult<T>(
    bool IsAccepted,
    T? Value,
    string? RejectionReason);
  • Una interfaz, muchas implementaciones
  • Sanitizadores de ruta, URL, comando de shell, esquema JSON
  • Componible: encadena PathSanitizerSizeSanitizerEncodingSanitizer
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Tres sanitizadores se lanzan con v1

PathSanitizer

  • Rechaza reparse points
  • Fuerza contención (H-1..H-4)
  • Aplica lista permitida de nombres (H-5)
  • Delega a ISafePathResolver

UrlSanitizer

  • Solo HTTPS por defecto
  • Bloquea rangos IP privados
  • Bloquea hosts de metadatos cloud
  • Limita redirecciones + tamaño de cuerpo

JsonArgumentSanitizer

  • Valida contra el JsonSchema de la herramienta
  • Elimina propiedades desconocidas (opción fail-loud)
  • Coerciona tipos solo cuando es seguro ("42"42 para propiedades int)
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

De extremo a extremo: cómo se ve una llamada de herramienta ahora

LLM emite tool_call
        │
        ▼
┌──────────────────────┐
│ JsonArgumentSanitizer│ → validar contra JsonSchema
└────────┬─────────────┘
         │ válido
         ▼┌──────────────────────┐
│ PathSanitizer        │ → ISafePathResolver
└────────┬─────────────┘
         │ contained
         ▼
┌──────────────────────┐
│ IToolApprovalPolicy  │ → ¿humano en el ciclo?
└────────┬─────────────┘
         │ approved
         ▼
   ToolResult.Ok / .Fail   (auditoría en cada rama)
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Un diff concreto: operación read

// Before (Session 2)
public Task<ToolResult> ExecuteAsync(ToolInput input, CancellationToken ct)
{
    var path = input.Args["path"]!.ToString();
    var full = Path.GetFullPath(Path.Combine(_root, path));
    if (!full.StartsWith(_root, StringComparison.OrdinalIgnoreCase))
        return Fail("path escape");
    return Ok(File.ReadAllText(full));
}
// After (Session 3)
public async Task<ToolResult> ExecuteAsync(ToolInput input, CancellationToken ct)
{
    var args = _argSanitizer.Sanitize(input);          // schema
    if (!args.IsAccepted) return Fail(args.RejectionReason!);

    var resolved = _paths.Resolve(args.Value!.Path, scope: _scope);
    if (!resolved.IsAllowed) return Fail(resolved.Reason!);

    return Ok(await File.ReadAllTextAsync(resolved.FullPath, ct));
}
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Los metadatos por herramienta no cambian

public sealed record ToolMetadata(
    JsonDocument ParameterSchema,
    bool RequiresApproval,
    string Category,
    string[] Tags);
  • ParameterSchema ahora alimenta a JsonArgumentSanitizer
  • RequiresApproval sigue controlando el ejecutor
  • Las herramientas que escribiste en la Sesión 2 siguen funcionando

El contrato no se movió. La implementación se volvió más segura.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Adiciones de eventos NDJSON

{"type":"ToolApprovalRequest","tool":"file_system","args":{...}}
{"type":"ToolCallStart","tool":"file_system","callId":"abc"}
{"type":"ToolSanitizationFailed","tool":"file_system","reason":"reparse-point"}
{"type":"ToolCallComplete","tool":"file_system","callId":"abc","durationMs":12}
{"type":"ContentDelta","text":"File contents..."}
  • Nuevo: ToolSanitizationFailed — se muestra en la UI como advertencia inline
  • Eventos existentes sin cambios — la UI sigue funcionando
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Lo que te aporta

  • ✅ El mismo agente funciona en Ollama, Azure OpenAI, Foundry, Copilot
  • ✅ Los modos de falla por path traversal ahora son un bug, no cinco
  • ✅ Las razones del sanitizer regresan al modelo → menos bucles de reintento
  • ✅ FileSystemTool es 5× más pequeño por archivo → PRs más fáciles

El trabajo "aburrido" de la sesión 3 que permite que existan las próximas 3 partes.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

🎭 Parte 3 — Sistema de Habilidades

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

¿Qué es una habilidad?

Un archivo Markdown con frontmatter YAML que moldea el comportamiento del agente — sin código, sin redeploy.

---
name: dotnet-expert
description: .NET expertise — DI, async, Aspire, EF Core
tags: [dotnet, csharp, aspire]
---

You are a senior .NET architect. When answering:

- Prefer modern C# patterns (records, primary constructors)
- Cite Microsoft Learn for any non-trivial claim
- Show code in full, never abridged with "..."
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

¿Por qué habilidades (vs. herramientas)?

Herramientas Habilidades
Formato código C# (ITool) Markdown + YAML
Autoría ingenieros cualquiera (PM, QA, seguridad…)
Efecto nuevas capacidades nuevo comportamiento
Ciclo de vida compilar + desplegar soltar un archivo, hot-reload
Superficie de riesgo ejecución de código prompt injection

Herramientas = brazos. Habilidades = personalidad. Problemas distintos.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

El bug estrella que arreglamos

OpenClawNet tenía dos cargadores de habilidades paralelos que no compartían estado:

Sistema Lee desde Consumido por
FileSkillLoader propio skills/built-in, skills/samples /api/skills/*
AgentSkillsProvider de MAF config Agent:SkillsPath el agente real

Resultado: click en "Disable" en la UI → 200 OK → el agente sigue usando la habilidad.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

La solución: un cargador, una fuente de verdad

┌──────────────────────────────────────┐
│  ISkillsRegistry  (singleton)        │
│  3-layer discovery + watcher         │
└────────────┬─────────────────────────┘
             │ BuildFor(agentName)
             ▼
┌──────────────────────────────────────┐
│  OpenClawNetSkillsProvider (scoped)  │
│  AIContextProvider · per-request     │
└────────────┬─────────────────────────┘
             │
             ▼
┌──────────────────────────────────────┐
│  MAF AgentSkillsProvider             │
│  (agentskills.io spec compliant)     │
└──────────────────────────────────────┘

Eliminar FileSkillLoader. Eliminar SkillParser. MAF es la fuente de verdad.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Almacenamiento en 3 capas

C:\openclawnet\
└── skills\
    ├── system\                          # Ships with app, read-only
    │   ├── memory\SKILL.md
    │   └── doc-processor\SKILL.md
    ├── installed\                       # From imports, shared
    │   ├── awesome-copilot\dotnet-expert\SKILL.md
    │   └── .install-manifest.json
    ├── agents\{agent-name}\             # Per-agent overrides
    │   ├── enabled.json                 # which skills are visible
    │   └── skills\
    │       └── {skill-name}\SKILL.md
    └── .quarantine\                     # imported, not yet approved
        └── {import-id}\…

Precedencia: agents/{name}/ > installed/ > system/.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

¿Por qué almacenamiento compartido y no por agente?

"La amenaza real es contenido-en-prompt, no contenido-en-disco."

  • La habilitación por agente controla la exposición
  • El almacenamiento por agente significaría N copias de cada habilidad
  • N copias → fatiga de actualización → aprobaciones de trámite → CVE
  • El almacenamiento por agente es teatro; la habilitación por agente es real
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Frontmatter de agentskills.io

---
name: dotnet-expert
description: .NET expertise  DI, async, Aspire, EF Core
license: MIT
metadata:
  openclawnet:
    tags: [dotnet, csharp, aspire]
    category: programming
    examples:
      - "How should I structure DI in a Blazor app?"
      - "Why is my async method blocking?"
---
  • Núcleo conforme a la spec: name, description, license
  • Los extras de OpenClawNet viven bajo metadata.openclawnet.*
  • MAF ignora con elegancia los campos desconocidos
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Qué se descarta del frontmatter antiguo

Campo antiguo Estado Por qué
enabled: true ❌ eliminado reemplazado por enabled.json por agente
category: … ✅ movido ahora metadata.openclawnet.category
tags: […] ✅ movido ahora metadata.openclawnet.tags
examples: […] ✅ movido ahora metadata.openclawnet.examples

Conforme a la spec por encima. Sabor OpenClawNet por debajo.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Hot-reload con FileSystemWatcher

public sealed class SkillsLayerWatcher : IDisposable
{
    private readonly FileSystemWatcher _fsw;
    private readonly Channel<Unit> _coalesce;

    public SkillsLayerWatcher(string root, Action onChange)
    {
        _fsw = new FileSystemWatcher(root, "*.md")
        {
            IncludeSubdirectories = true,
            EnableRaisingEvents = true
        };
        _fsw.Changed += (_, _) => _coalesce.Writer.TryWrite(default);
        // … 500ms debounce loop calls onChange()
    }
}
  • Un watcher por capa (system, installed, agents/{name})
  • Debounce de 500 ms — los guardados son a ráfagas (baile de archivos temporales del editor)
  • Al cambiar → el registry reconstruye el snapshot
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Snapshot por solicitud

public sealed record SkillsSnapshot(
    ImmutableArray<ResolvedSkill> Skills,
    long Version);
  • El registry mantiene el snapshot actual
  • Cada solicitud obtiene una vista estable
  • Ediciones de archivo a mitad de conversación → el siguiente turno toma el cambio
  • Sin lecturas rotas
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Habilitación por agente: enabled.json

{
  "version": 1,
  "skills": {
    "memory": "enabled",
    "doc-processor": "enabled",
    "awesome-copilot/dotnet-expert": "enabled",
    "awesome-copilot/security-auditor": "disabled"
  },
  "default": "use-default"
}
  • Un archivo por agente en agents/{name}/enabled.json
  • Tres estados: enabled, disabled, use-default
  • Las habilidades nuevas por defecto están disabled por seguridad fail-closed
  • Persistido en SQLite; enabled.json es la proyección en disco
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Default = disabled, cierre seguro (fail-closed)

Instalar ≠ activo.

  • Un usuario importa security-auditor desde awesome-copilot
  • El archivo cae en installed/awesome-copilot/security-auditor/
  • Ningún agente lo usa todavía
  • El usuario va a la página de Habilidades → lo activa por agente
  • La habilitación por agente cambia a "enabled" → efectivo en el siguiente turno
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

UI Skills.razor — navegación de nivel superior

Explorar

  • Filtrar por built-in / installed / enabled
  • Columnas de fuente, versión, categoría
  • "Habilitada en: GptAgent, ResearchBot"
  • Click en una fila → expansión de detalle inline

Actuar

  • Toggle de asignación por agente (modal)
  • Pestaña "Install from URL"
  • Disable / enable / remove
  • Estadísticas de uso (últimos 7 días)

Una página para todo lo relacionado con habilidades — no enterrada en Settings.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria
┌─────────────────────────────────────────────┐
│ dotnet-expert                          [✕]  │
├─────────────────────────────────────────────┤
│  Default for new agents:   [✓ Enabled]      │
│                                             │
│  Per-agent overrides                        │
│   GptAgent           [enabled  ▼]           │
│   ResearchBot        [disabled ▼]           │
│   SupportBot         [use default ▼]        │
│                                             │
│  Effective on the next chat turn.           │
│                                  [ Save ]   │
└─────────────────────────────────────────────┘

Toggle por defecto arriba. Overrides explícitos por agente abajo.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Asistente de importación de 4 pasos

  1. Entrada — pega URL o explora colecciones de la allowlist
  2. Vista previa — fuente, SHA, lista de archivos, SKILL.md renderizado, diff vs existente
  3. Progreso — descarga, verifica, extrae (con log detallado)
  4. Resultado — toast: "View / Edit Assignment" o error + retry

Banner amarillo grande: "Este contenido será inyectado en el system prompt del agente."

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

v1 import: solo awesome-copilot

  • Una fuente en allowlist: github.com/github/awesome-copilot
  • Anclado a un commit SHA (no a main, no a un tag)
  • Manifiesto SHA-256 de cada archivo del bundle
  • Agregar más fuentes = editar appsettings (sin UI)

"Solo pega una URL" es una vulnerabilidad, no una característica.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Ruta de autoría manual

No tienes que usar el pipeline de importación. Puedes crear habilidades localmente:

# 1. Pick a folder
mkdir C:\openclawnet\skills\agents\GptAgent\skills\my-tone-of-voice

# 2. Drop a SKILL.md
@"
---
name: my-tone-of-voice
description: Concise, friendly, no buzzwords
---

When responding: short sentences. No "leverage" or "synergize".
"@ | Out-File ...\SKILL.md

# 3. Watcher picks it up; skill is enabled for that agent
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Anatomía de SKILL.md en detalle

---
name: security-auditor
description: Security review focused on .NET + OWASP
license: MIT
metadata:
  openclawnet:
    tags: [security, audit, owasp, dotnet]
    category: security
    examples:
      - "Audit this controller for OWASP Top 10"
      - "Check for SQL injection in this query"
---

You are a security auditor. When reviewing code:

- Identify OWASP Top 10 issues by name
- Flag SQL injection, XSS, path traversal
- Suggest mitigations with code examples
- Sort findings by severity: Critical / High / Medium / Low
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Recursos limitados (S-11)

  • Por habilidad: SKILL.md256 KB
  • Por prompt del sistema del agente: tokens inyectados por habilidades ≤ 8 KB (por defecto)
  • Los más antiguos por orden de carga se descartan si se excede el presupuesto
  • Auditoría WARN cuando ocurre el descarte

Una habilidad que infla tu prompt se degrada elegantemente, no falla.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Invariantes de endurecimiento S-1..S-12

# Qué
S-1 Anclaje de procedencia (URL + SHA de commit + SHA-256 del paquete)
S-2 Lista permitida de tipos de archivo (sin ejecutables, nunca)
S-3 Contención de rutas de almacenamiento (reusar H-1..H-6)
S-4 Reserva de nombres integrados (shell-exec etc. no puede ser suplantado)
S-5 Puerta de aprobación en instalación y cada actualización
S-6 Sin actualización automática desde fuentes externas
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Invariantes de endurecimiento S-1..S-12 (cont.)

# Qué
S-7 Habilitación por agente, almacenamiento compartido
S-8 Sin contenido ejecutable de habilidad en v1
S-9 Pista de auditoría en instalar/actualizar/cargar/invocar
S-10 Revocación efectiva dentro de un turno de chat
S-11 Uso limitado de recursos (256 KB / presupuesto de 8 KB tokens)
S-12 Lista permitida de fuentes, denegar por defecto

Ninguno negociable para v1. Cada PR revisado contra esta lista.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Registro estructurado — 14 eventos

SkillLoaderStarted        SkillDiscovered          SkillLoaded
SkillLoadFailed           SkillInvoked             SkillFunctionReturned
SkillFunctionThrew        ImportRequested          ImportApproved
ImportRejected            ImportCompleted          ImportFailed
SkillEnabled              SkillDisabled
  • Todos vía clases generadas por código con LoggerMessage
  • Correlación de 8 campos: RunId, SkillInvocationId, AgentId, UserId, SkillId, FunctionName, RequestId, Timestamp
  • OTel ActivitySource: OpenClawNet.Skills → panel de Aspire
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Qué NO registramos

  • ❌ Valores de parámetros (PII, claves API, credenciales)
  • ❌ Valores de retorno (lo mismo)
  • ❌ Contenido del cuerpo de SKILL.md (controlado por atacante — inyección de log)
  • ❌ Contenido del chat / respuestas del agente / tokens OAuth

Registrar esquema + tamaño + SHA-256 de los primeros 1 KB. Eso es suficiente.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Pruebas E2E — qué pasa

  • ✅ GET /api/skills devuelve las mismas habilidades que usa el agente
  • ✅ POST /api/skills/{name}/disable toma efecto en el siguiente turno
  • ✅ Recarga en caliente: soltar un archivo → siguiente turno lo ve
  • ✅ Habilitación por agente: activar para AgentA → AgentB no afectado
  • ✅ Vista previa de importación → confirmar → ronda completa de instalación
  • ✅ Instalación de nombre reservado rechazada con error claro
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Olas de implementación

Ola Alcance
K-1 Eliminar cargador paralelo, único ISkillsRegistry, 3 capas + watcher, enabled.json
K-2 14 eventos de log + correlación de 8 campos + filas de habilidad en panel de Actividad
K-3 Tubería de importación (previsualización → confirmar), fetcher awesome-copilot
K-4 Pulido de UI — modal de asignación, asistente, estadísticas de uso

Se encadena después de W-1..W-4 (almacenamiento). Las habilidades no pueden enviarse antes de que el almacenamiento esté endurecido.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

💾 Parte 4 — Refactorización de Almacenamiento

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

La pregunta de Bruno

"¿Dónde están las configuraciones para la ubicación de almacenamiento de OpenClawNet — el lugar predeterminado para almacenar archivos de la aplicación?"

Escenario Ruta esperada
El agente genera un archivo markdown C:\openclawnet\agents\{name}\out.md
La herramienta descarga un modelo local C:\openclawnet\models\
El usuario apunta al agente a una carpeta C:\openclawnet\workspaces\samples\
Predeterminado general C:\openclawnet\
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Qué estaba mal

  • 🟥 Los prompts del agente decían "tu raíz de workspace es bin/Debug/net10.0/"
  • 🟥 FileSystemTool por defecto estaba en la raíz de la solución
  • 🟥 Sin subcarpeta workspaces/ para áreas de scratch nombradas por el usuario
  • 🟥 Las descargas de modelos aterrizaban en ~/.cache/huggingface
  • 🟥 La raíz predeterminada era C:\openclawnet\storage\ — nivel extra que nadie pidió
  • 🟥 ResolvePath permitía cualquier ruta absoluta

La propuesta redirigía escrituras; esta revisión las restringe.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Los nuevos predeterminados

C:\openclawnet\
├── agents\{agent-name}\          # salidas por agente
├── models\                       # modelos locales (Ollama, HF, ONNX)
├── workspaces\{name}\            # scratch nombrado por usuario
├── uploads\                      # subidas de usuario
├── exports\                      # artefactos generados
├── skills\                       # (Parte 3)
└── dataprotection-keys\          # llavero de ASP.NET
  • Configurable vía Storage:RootPath en appsettings
  • O variable de entorno OPENCLAWNET_STORAGE_ROOT (único nombre canónico)
  • Registra ruta resuelta + fuente en INFO al inicio
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Configuración: tres fuentes, un ganador

Prioridad (gana la más alta):
  1. OPENCLAWNET_STORAGE_ROOT  (var de entorno)
  2. Storage:RootPath          (appsettings.json)
  3. Predeterminado integrado  (C:\openclawnet en Windows)
// appsettings.json
{
  "Storage": {
    "RootPath": "D:\\openclaw",
    "AdditionalWritablePaths": [ "C:\\shared\\datasets" ]
  }
}

Registrado en INFO al inicio para que la mala configuración sea visible en Aspire.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

OPENCLAWNET_STORAGE_ROOT — solo un nombre

"No tengas dos variables de entorno. Un atacante que pueda configurar el entorno del proceso podría configurar la inesperada y redirigir silenciosamente el almacenamiento."

  • Elige un nombre → documéntalo → ignora todo lo demás
  • OPENCLAW_STORAGE_DIR no se consulta (incluso si está presente)
  • Bonus: registra la ruta resuelta y su fuente (env / appsettings / predeterminado)
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

ISafePathResolver — un resolvedor, una regla

public interface ISafePathResolver
{
    PathResolution Resolve(string requested, string? scope = null);
}

public sealed record PathResolution(
    bool IsAllowed,
    string? FullPath,
    string? Reason);
  • Toda resolución de rutas va aquí
  • Ninguna herramienta llama a Path.GetFullPath sobre entrada del LLM directamente
  • El resolvedor impone H-1, H-3, H-4, H-5 en un solo lugar
  • Parámetro scope opcional para aislación por agente (H-6)
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

H-1: contención de raíz de almacenamiento, cierre seguro (fail-closed)

// Dentro del resolvedor
var full = Path.GetFullPath(requested);
var allowedRoots = new[] { _root, ..._additional };

if (!allowedRoots.Any(root => IsContained(full, root)))
    return PathResolution.Denied("outside storage root");
  • Las lecturas PUEDEN ser más amplias, las escrituras DEBEN estar dentro de RootPath (+ lista permitida)
  • Rechazar, no reescribir silenciosamente
  • Misma puerta para herramientas ITool y MCP
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

H-2: un sanitizador, un resolvedor

  • ISafePathResolver es el punto de entrada de rutas
  • Ninguna herramienta llama a Path.GetFullPath / Path.Combine sobre entrada del LLM
  • Completamente probado unitariamente con casos positivos y adversarios
  • Auditado contra H-1, H-3, H-4, H-5 en un solo lugar

Si encuentras una herramienta llamando a Path.GetFullPath sobre entrada de usuario, reporta un bug.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

H-3: sin escapes de punto de reanálisis

var info = new FileInfo(fullPath);
var realTarget = info.ResolveLinkTarget(returnFinalTarget: true);
if (realTarget != null && !IsContained(realTarget.FullName, _root))
    return PathResolution.Denied("reparse-point escape");
  • Path.GetFullPath no resuelve uniones / enlaces simbólicos
  • Una unión dentro de RootPathC:\Windows pasaría de otro modo
  • Re-verificar cada padre de la ruta resuelta
  • La creación de enlaces simbólicos por la herramienta misma está prohibida
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

H-4: contención segura de límites

static bool IsContained(string path, string root)
{
    root = Path.TrimEndingDirectorySeparator(root);
    return path.Equals(root, StringComparison.OrdinalIgnoreCase) ||
           path.StartsWith(root + Path.DirectorySeparatorChar,
                           StringComparison.OrdinalIgnoreCase);
}
  • C:\openclawnet es un prefijo de C:\openclawnet-evil
  • StartsWith simple ampliaría silenciosamente el límite
  • La verificación de separador-final-o-fin lo arregla
  • La prueba de regresión en el caso evil se envía en la suite
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

H-5: lista estricta de nombres permitidos

private static readonly Regex SafeName =
    new(@"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", RegexOptions.Compiled);

private static readonly HashSet<string> Reserved = new(
    [ "CON", "PRN", "AUX", "NUL",
      "COM1", "COM2", … "COM9",
      "LPT1", "LPT2", … "LPT9" ],
    StringComparer.OrdinalIgnoreCase);
  • Lista permitida [A-Za-z0-9._-], longitud ≤ 64
  • Rechazar nombres de dispositivos reservados de Windows (insensible a mayúsculas)
  • Rechazar punto inicial, punto/espacio final
  • Misma regla para nombres de agente, workspace, subida, habilidad
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

H-6: costura de alcance por agente

public interface ISafePathResolver
{
    PathResolution Resolve(string requested,
                           string? scope = null);  // ← futura raíz por agente
}
  • scope predetermina a StorageOptions.RootPath
  • Puede configurarse a agents/{name}/ por solicitud
  • No se envía lógica de alcance por agente en v1 — solo la costura
  • Evita romper la API más tarde
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

H-7: endurecimiento de ACL en subdirectorios de credenciales

// Al inicio, después de EnsureDirectories():
var keysDir = Path.Combine(_root, "dataprotection-keys");

if (OperatingSystem.IsWindows())
    SetExplicitDacl(keysDir, currentUser, FullControl,
                    inheritance: false);
else
    File.SetUnixFileMode(keysDir, UnixFileMode.UserRead | UserWrite | UserExecute);
  • Verificar que el usuario actual tiene control total sobre RootPath
  • DACL explícita en dataprotection-keys/, vault/, tokens/
  • POSIX: chmod 0700 en lo mismo
  • Rechazar iniciar servicios de credenciales si falla la verificación de ACL
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

H-8: auditar cada escritura

{
  "type": "FileSystemWriteAudit",
  "agent": "GptAgent",
  "action": "write",
  "path": "C:\\openclawnet\\agents\\GptAgent\\out.md",
  "bytes": 4218,
  "sha256": "9f3c…",
  "source": "llm-suggested",
  "runId": "r-7b4a",
  "timestamp": "2026-05-22T14:03:11Z"
}
  • Cada escritura exitosa → registro de auditoría
  • Cada escritura bloqueada → auditoría WARN con la entrada no resuelta
  • Fundamento para forense, facturación, políticas de retención
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Los ocho, lado a lado

# Qué
H-1 Contención en raíz de almacenamiento, cierre seguro (fail-closed)
H-2 Un sanitizador / un resolvedor (ISafePathResolver)
H-3 Sin escapes por reparse-point
H-4 Verificación de contención segura en límites
H-5 Lista de nombres permitidos estricta
H-6 Alcance de separación por agente
H-7 ACL restrictiva en raíz + subdirectorios de credenciales
H-8 Auditar cada escritura

Ocho invariantes. Un resolvedor. Cierre seguro (fail-closed) por diseño.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Conectándolo

// Program.cs
builder.Services
    .AddOpenClawStorage()         // binds StorageOptions, ensures dirs, ACL
    .AddSafePathResolver()        // ISafePathResolver
    .AddOpenClawTools();          // FileSystemTool uses the resolver

// Anywhere a tool needs a path:
public sealed class MyTool(ISafePathResolver paths) : ITool { … }
  • Un método de extensión por preocupación
  • Resolvedor inyectado por DI — sin estáticos, sin globales
  • Funciona igual en Gateway, AppHost, servidores MCP
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Interfaz de configuración

  • Nueva tarjeta "Storage" en la página /settings
  • Muestra: raíz actual, fuente (env / appsettings / predeterminado), espacio libre
  • Botón "Mover raíz a…" (escribe nueva ruta, requiere reinicio)
  • Salud: estado de ACL por subdir de credenciales
  • Medidor de cuota por subcarpeta de nivel superior

La descubribilidad reemplaza las conjeturas.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Historia de migración

  • Primer arranque después de actualización: detectar raíz antigua (C:\openclawnet\storage\)
  • Ofrecer mover contenidos a C:\openclawnet\ (un clic)
  • O mantener ruta antigua vía anulación Storage:RootPath
  • Saltar migración completamente con --no-migrate

Sin movimientos silenciosos. Sin pérdida de datos. Tú optas por entrar o salir.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

🧠 Parte 5 — Hoja de ruta de memoria

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

El problema de la ventana de contexto

  • Los LLMs tienen límites de tokens — 8K a 128K típico
  • Cada mensaje en el historial se reenvía en cada turno
  • Truncamiento ingenuo = el agente olvida
  • El costo crece linealmente incluso con modelos locales (latencia, GPU)
  • Chat de 100 mensajes a 4K promedio = 400K tokens por turno

"¿Ya discutimos esto?" — tu agente, cada conversación.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Estrategia de resumen

┌──────────────── historial completo ──────────────┐
│                                                   │
│  [viejo]  [viejo]  [viejo]  [reciente]  [reciente]│
│    │       │       │        │          │          │
│    └───────┴───┬───┘        │          │          │
│         resumir             │          │          │
│            │                │          │          │
│            ▼                ▼          ▼          │
│      [resumen]   +   [reciente]    [reciente]     │
└────────────────────────────────────────────────────┘
             │
             ▼
    System prompt: instrucciones + resumen + reciente
  • Mensajes recientes: verbatim (últimos N)
  • Mensajes más antiguos: resumidos en puntos clave
  • Muy antiguos: búsqueda semántica bajo demanda
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Entidad SessionSummary

public sealed class SessionSummary
{
    public Guid Id { get; init; }
    public Guid SessionId { get; init; }
    public string Summary { get; init; } = "";
    public int CoveredMessageCount { get; init; }
    public DateTimeOffset CreatedAt { get; init; }
}
  • Una sesión → muchos resúmenes (ventana rodante a medida que crece)
  • CoveredMessageCount le dice al compositor dónde empezar "reciente"
  • Cascada de eliminación con la sesión padre
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Forma de IMemoryService

public interface IMemoryService
{
    Task<SessionSummary?> GetLatestSummaryAsync(
        Guid sessionId, CancellationToken ct = default);

    Task StoreSummaryAsync(
        SessionSummary summary, CancellationToken ct = default);

    Task<MemoryStats> GetStatsAsync(
        Guid sessionId, CancellationToken ct = default);
}
  • Respaldado por IDbContextFactory<OpenClawDbContext> (patrón async correcto)
  • MemoryStats expone mensajes totales, conteo de resúmenes, tiempo de último resumen
  • Disparado por umbral de conteo de mensajes (predeterminado 20)
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Embeddings locales — sin llamadas API

  • Elbruno.LocalEmbeddings — modelos ONNX, se ejecuta en proceso
  • Embeber texto → vector de 384 dimensiones
  • Similitud de coseno para búsqueda de vecino más cercano
  • Sin red, sin clave API, ningún dato sale de la máquina
var v1 = await _embeddings.EmbedAsync("dependency injection in .NET");
var v2 = await _embeddings.EmbedAsync("how do I configure IoC?");
var sim = CosineSimilarity(v1, v2);  // ~0.82 — coincidencia fuerte
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Búsqueda semántica a través de sesiones pasadas

  • Cada mensaje → embedding → columna de vector SQLite
  • Nueva pregunta → embeber → top-K mensajes pasados más cercanos
  • Fragmentos recuperados → inyectados en el system prompt como contexto
  • Encontrar la conversación sobre "DI" incluso si el usuario escribió "IoC"

El agente recuerda — sin una ventana de contexto de 1M-token.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Panel de memoria transparente

┌─ Memoria ────────────────────────────────────────┐
│ Total mensajes         : 142                     │
│ Resumidos              : 100  (3 resúmenes)      │
│ Recientes (verbatim)   : 42                      │
│ Último resumen         : hace 2 minutos          │
│ Tokens de prompt estimados: 3.8 K  (eran 26 K)   │
└──────────────────────────────────────────────────┘
  • Los usuarios ven lo que el sistema de memoria está haciendo
  • No es una caja negra — cada conteo es auditable
  • Respalda el endpoint GET /api/memory/{sessionId}/stats
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Qué viene después (vista previa de Sesión 4)

  • ✅ Diseñado: SessionSummary, IMemoryService, IEmbeddingsService
  • 🚧 Construyendo: resumidor de grado de producción con política de reintento de modelo
  • 🚧 Construyendo: índice vectorial sobre mensajes pasados (blob SQLite → DuckDB)
  • 🔜 Próximamente: espacios de nombres de memoria por agente + políticas de retención
  • 🔜 Próximamente: memoria exportación / importación para portabilidad

Sesión 4 = nube + programación + memoria a escala.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

🧪 Parte 6 — Demos de consola

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Demo 1 — recorrido de aspire start

cd C:\src\openclawnet
aspire run

# Expected:
# 🟢 OpenClawNet.AppHost (3 of 3 running)
#    ├── 🟢 gateway       https://localhost:7234
#    ├── 🟢 ollama        http://localhost:11434
#    └── 🟢 dashboard     https://localhost:17000
  • Observa el ícono de bandeja de Aspire Monitor ponerse verde
  • Abre el dashboard → confirma línea de log Storage:RootPath
  • Log INFO: Storage root resolved to C:\openclawnet (source: default)
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Demo 2 — curl /api/skills

# List skills the agent actually uses
curl https://localhost:7234/api/skills | jq

# Output:
# [
#   { "name": "memory",        "layer": "system", "enabledFor": ["GptAgent"] },
#   { "name": "doc-processor", "layer": "system", "enabledFor": ["GptAgent"] }
# ]

# Toggle one for a specific agent
curl -X POST https://localhost:7234/api/agents/GptAgent/skills/memory/disable
  • API y agente comparten el mismo registro — sin deriva
  • Desactivar toma efecto en el siguiente turno de chat (S-10)
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Demo 3 — soltar habilidad manual

# 1. Author a skill in your editor
code C:\openclawnet\skills\agents\GptAgent\skills\concise-tone\SKILL.md

# 2. File contents:
@"
---
name: concise-tone
description: Short, friendly responses
---

Keep responses under 3 sentences. No buzzwords.
"@ > SKILL.md

# 3. Watch the gateway log:
# [INFO] SkillDiscovered  name=concise-tone layer=agents:GptAgent
# [INFO] SkillLoaded      name=concise-tone duration=12ms

# 4. Send a chat — agent is now concise.
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

🎯 Cierre

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Perspectivas clave

  1. 🧠 Las habilidades son markdown — cualquiera puede crear, sin código, sin reinicio
  2. 🛡️ El almacenamiento es fail-closed — ocho invariantes, un resolvedor, sin reescrituras silenciosas
  3. 📈 La memoria es transparente — los usuarios ven qué está resumido
  4. 🔧 La llamada de herramientas es portable — mismo agente, cuatro proveedores
  5. 👀 Las operaciones son visuales — monitores Ollama + Aspire en la bandeja

"Las herramientas son brazos. Las habilidades son personalidad. El almacenamiento es el piso."

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Lo que construimos hoy ✅

  • ✅ Dos aplicaciones de bandeja NuGet — Ollama Monitor + Aspire Monitor
  • ✅ Formato de llamada de herramientas alineado con OpenAI
  • ✅ Refactor de FileSystemTool (5 archivos, ~120 LOC cada uno)
  • ✅ Tres sanitizadores reutilizables (path / URL / JSON)
  • ✅ Un solo ISkillsRegistry (eliminado FileSkillLoader paralelo)
  • ✅ Almacenamiento de 3 capas con hot-reload de FileSystemWatcher
  • ✅ Habilitación por agente vía enabled.json (fail-closed)
  • ✅ UI de Skills.razor + modal de asignación + asistente de importación
  • ✅ C:\openclawnet\ predeterminado + variable env OPENCLAWNET_STORAGE_ROOT
  • ✅ ISafePathResolver aplicando H-1..H-8
  • ✅ Endurecimiento de ACL en subdirs de credenciales
  • ✅ Hoja de ruta de memoria: SessionSummary, embeddings locales, búsqueda semántica
OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

Vista previa de Sesión 4

  • ☁️ Proveedores en la nube — Azure OpenAI, Foundry a escala
  • ⏰ Programación de trabajos — expresiones cron, trabajos durables
  • 🧠 Memoria a escala — índice vectorial, políticas de retención
  • 🩺 Health checks + pruebas — endurecimiento de producción
  • 🎬 Final de serie — demo de plataforma completa

Hoy: un agente con personalidad, límites y un plan de memoria.
Siguiente: un agente que se ejecuta mientras duermes.

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

¿Preguntas?

Bruno Capuano — Principal Cloud Advocate, Microsoft
github.com/elbruno · @elbruno

Pablo Piovano — Microsoft MVP
linkedin.com/in/ppiova


elbruno/openclawnet · MIT licensed · contributions welcome
docs/sessions/session-3/ for everything from today

OpenClawNet · Sesión 3 · Habilidades, Almacenamiento y Memoria

SPEAKER NOTES — diapositiva de título. Bienvenidos de nuevo a la Sesión 3. En la Sesión 1 construimos la base. En la Sesión 2 le dimos manos al agente. Hoy le damos personalidad, un lugar seguro para vivir en disco, y una hoja de ruta para memoria a largo plazo. También lanzamos dos nuevas herramientas de desarrollo que querrás en tu barra de tareas. Gran sesión — vamos.

SPEAKER NOTES — recapitulación. Recapitulación rápida para que los que lleguen tarde puedan ponerse al día. Sesión 1 = una aplicación de chat funcional sobre Aspire/Blazor con cinco proveedores. Sesión 2 = el bucle del agente, dos superficies de herramientas, una puerta de aprobación y los primitivos de seguridad que todo framework de herramientas necesita. Ambas sesiones están grabadas y el código está en el repositorio. Si te las perdiste, las diapositivas y demos están en docs/sessions.

SPEAKER NOTES — alcance. Seis capítulos. Los primeros dos son "lo que se lanzó desde la sesión 2 — calidad de vida y pulido de tool-calling". Los capítulos tres y cuatro son la carne — habilidades y almacenamiento. El capítulo cinco mira hacia adelante: hacia dónde va la memoria. El capítulo seis es práctico. Nos moveremos rápido en las partes de recapitulación y más lento en las decisiones de diseño.

SPEAKER NOTES — modelo mental. Toda la sesión gira en torno a esto. Habilidades = comportamiento, almacenamiento = límites. Ambas tratan de dar más autonomía al agente de forma SEGURA. No puedes lanzar habilidades si el agente puede escribir en cualquier lugar del disco; no puedes confiar en el endurecimiento del almacenamiento si cualquiera puede soltar una habilidad maliciosa. Llegan juntas.

SPEAKER NOTES — divisor Parte 1. Dos nuevas herramientas de desarrollo que lanzamos entre la Sesión 2 y la Sesión 3. Viven en la bandeja del sistema y hacen que el desarrollo diario de OpenClawNet sea mucho menos doloroso. Diez minutos en total.

SPEAKER NOTES — puntos de dolor. Esto es lo que todo desarrollador de LLM local experimenta. Olvidas iniciar Ollama, el agente expira, depuras durante diez minutos antes de darte cuenta de que el servidor del modelo ni siquiera está arriba. O tu aplicación Aspire está corriendo pero cerraste la pestaña del dashboard. Construimos dos aplicaciones de bandeja que ponen las respuestas en tu bandeja del sistema.

SPEAKER NOTES — Ollama Monitor. Primera herramienta: Ollama Monitor. Distribuida como una herramienta dotnet global — un comando para instalar, vive en tu bandeja del sistema. Verde = Ollama está arriba y sirviendo. Amarillo = arriba pero lento. Rojo = caído. Haz clic en el ícono y obtienes detalles del modelo, estadísticas de GPU, solicitudes recientes. Las notificaciones toast son la característica asesina — te enteras antes que tu demo.

SPEAKER NOTES — detalle de características. Cada cinco segundos golpeamos /api/tags y /api/ps. El sparkline muestra tokens/seg agregados a través de solicitudes activas. Detener/iniciar rápido usa el CLI de Ollama bajo el capó. Los colores de estado son deliberados — amarillo no significa roto, significa "tu laptop está en batería y el modelo está paginado". Triple falla antes de mostrar toast para no bombardearte en una red inestable.

SPEAKER NOTES — instalación. Tres líneas para instalar y ejecutar. Auto-start agrega una tarea programada de Windows al inicio de sesión — sobrevive reinicios, sin acceso directo de carpeta de inicio que gestionar. La configuración es JSON, puedes sincronizarla entre máquinas si quieres. El directorio de logs usa el patrón estándar de "datos de aplicación local" — el mismo lugar donde todas las otras aplicaciones Windows modernas ponen logs.

SPEAKER NOTES — demo. Demo en vivo si Ollama está corriendo. Clic derecho en el ícono de la bandeja. Muestra el modelo cargado, el conteo de capas GPU — para un 3B cuantizado eso debería ser 33/33 significando completamente en GPU. El sparkline muestra los últimos 60 segundos. Las solicitudes activas suben cuando disparas una solicitud de chat. El hotkey del dashboard abre una ventana más detallada con línea de tiempo por solicitud.

SPEAKER NOTES — Aspire Monitor. Segunda herramienta. Aspire Monitor resuelve un punto de dolor diferente: estás trabajando en múltiples aplicaciones Aspire, olvidas cuál está corriendo, la URL del dashboard cambia en cada reinicio. Esto se ancla a una carpeta, sabe qué AppHost vive allí, y te da iniciar/detener sin volver a la terminal.

SPEAKER NOTES — características de Aspire. La ventana fijada es la característica de la que la gente se enamora. Fijas "Gateway", "Ollama", y "Abrir Dashboard" — esos tres se quedan en una ventana flotante diminuta en la esquina de tu pantalla. Un clic y estás en cualquiera de ellos. Tras bambalinas estamos golpeando la API de recursos del dashboard de Aspire, así que obtenemos salud gratis.

SPEAKER NOTES — instalación de Aspire. Mismo patrón de instalación que Ollama Monitor — herramienta dotnet, global, no necesita admin. La bandera --auto es para CI / grabación de demo: inicia el AppHost en el momento en que se abre el monitor. Puedes correr múltiples copias apuntando a diferentes carpetas y aparecerán como íconos de bandeja separados.

SPEAKER NOTES — demo. Clic derecho en el ícono de la bandeja. Tres recursos, todos verdes, con sus endpoints. Sección fijada en la parte inferior — las tres URLs que abro diez veces al día. Iniciar/Detener/Reiniciar se aplica a todo el AppHost. Logs abre la página de logs del dashboard directamente, no la raíz del dashboard, así que te saltas un clic.

SPEAKER NOTES — divisor Parte 2. Ahora el trabajo bajo el capó. Entre la Sesión 2 y 3 hicimos un trozo de plomería en tool calling — alineación con el formato de OpenAI, una refactorización de FileSystemTool, y tres nuevos sanitizadores. Nada de esto es glamoroso; todo hace que el agente sea más confiable.

SPEAKER NOTES — por qué. Historia de origen honesta. Probamos principalmente en Ollama en la Sesión 2. Tan pronto como golpeamos Azure OpenAI y Foundry con el mismo agente, vimos diferencias sutiles en cómo se serializaban las llamadas de herramientas — orden de argumentos, comillas JSON, formas de error. Lo mismo con el FileSystemTool: se había convertido en un cajón de sastre. Esta parte de la sesión muestra qué cambiamos y por qué.

SPEAKER NOTES — formato. La forma de llamada de herramienta de OpenAI es el estándar de facto. Arguments-as-string es la verruga histórica que todos soportan porque la API original se lanzó así. Microsoft.Extensions.AI nos da la abstracción correcta (AIFunction) pero los proveedores pueden desviarse. Canonizamos todo internamente para que nunca tengamos que hacer casos especiales de "¿es esto Ollama o Azure?" en el runtime.

SPEAKER NOTES — tabla de diferencias. Qué cambió realmente. La interfaz pública ITool es idéntica — tus herramientas personalizadas de la Sesión 2 todavía compilan y corren. Lo que limpiamos es el interior: un ToolResult basado en record para que los llamadores puedan hacer pattern-match, análisis de argumentos impulsado por esquema, códigos de error estructurados, sanitizadores detrás de una interfaz. La refactorización de FileSystemTool es la que más importa para la siguiente diapositiva.

SPEAKER NOTES — refactorización. Esta es la estructura que repetiremos para las otras herramientas en las próximas sesiones. Un orquestador que elige la operación, un archivo por operación, validadores en su propia carpeta. La ganancia es testabilidad — en lugar de hacer mock de toda la herramienta, pruebas ReadOperation contra un IFileSystem falso. El PathValidator es un envoltorio delgado que delega al nuevo ISafePathResolver, que es el puente hacia la Parte 4.

SPEAKER NOTES — contrato de sanitizador. Los sanitizadores ahora son de primera clase. Cada herramienta declara qué sanitizadores necesita y se ejecutan en orden antes de que el cuerpo de la operación vea la entrada. Este es el patrón que queremos para cualquier herramienta futura: nunca confíes en cadenas suministradas por LLM, siempre sanitiza, siempre ten una razón de rechazo estructurada. La razón de rechazo fluye de vuelta al modelo para que pueda corregirse en el siguiente turno.

SPEAKER NOTES — tres sanitizadores. PathSanitizer es el puente a la Parte 4 — es la capa de cara al usuario sobre ISafePathResolver. UrlSanitizer mantiene las defensas SSRF de la Sesión 2 pero como un componente reutilizable. JsonArgumentSanitizer es el que se paga más rápido: cada vez que el modelo inventa un nombre de propiedad o envía un número como cadena, el sanitizador o lo coerciona correctamente o lo rechaza con un mensaje claro. Los tokens ahorrados en reintentos pagan el esfuerzo de ingeniería en una semana.

NOTAS DEL ORADOR — pipeline. Este es el pipeline ahora. Cuatro puertas explícitas, cada una probable de forma independiente. JsonArgumentSanitizer primero porque es gratis y rechaza la mayor parte de la basura. Luego, cualquier argumento de tipo path pasa por PathSanitizer. Después la política de aprobación. Solo entonces se ejecuta el cuerpo de la operación. Cada puerta emite un registro de auditoría al aceptar y al rechazar — H-8 en la Parte 4.

NOTAS DEL ORADOR — diff. Misma operación, dos mundos distintos. El "antes" está bien para una demo y es un disparo en el pie en producción. El "después" delega todo lo peligroso a un único resolver auditado. Sin Path.GetFullPath crudo sobre input del LLM. Sin verificación por prefijo de cadena que se rompe en C:\openclawnet vs C:\openclawnet-evil. Sin reescritura silenciosa. Este es el patrón que repetiremos para cada herramienta que reciba paths.

NOTAS DEL ORADOR — metadatos sin cambios. Tranquilidad importante. Si escribiste una herramienta personalizada en la Sesión 2, NADA CAMBIA para ti a nivel de API. Mismo ITool, mismos metadatos, misma puerta de aprobación. SÍ obtienes los nuevos sanitizers gratis si optas por usarlos. La compatibilidad hacia atrás fue un requisito firme de esta refactorización.

NOTAS DEL ORADOR — NDJSON. Un nuevo tipo de evento — ToolSanitizationFailed — emitido cuando un sanitizer rechaza una entrada antes de la aprobación. La UI lo muestra como una nota amarilla inline en la conversación para que el usuario vea "el modelo intentó leer C:\Windows\System32 y el sanitizer lo bloqueó". Esa transparencia es oro para depurar intentos de prompt-injection.

NOTAS DEL ORADOR — recompensa. Esta es la diapositiva fundacional. Ni habilidades, ni endurecimiento de almacenamiento, ni memoria podían aterrizar limpiamente sobre las internals de herramientas de la Sesión 2 tal como estaban. Tuvimos que hacer esta refactorización primero. La portabilidad entre proveedores es el gran beneficio externo; el beneficio interno es que las próximas sesiones puedan AGREGAR sin reescribir.

NOTAS DEL ORADOR — divisor de la Parte 3. La parte más grande de la sesión — unas treinta diapositivas. Las habilidades son la característica que la mayoría de los usuarios notarán primero. Archivos Markdown que cambian el comportamiento del agente. Hot-reload. Habilitación por agente. Vamos.

NOTAS DEL ORADOR — qué es una habilidad. Léelo en pantalla. Tres líneas de YAML, un párrafo de Markdown, y el agente ahora se comporta como un arquitecto sénior de .NET. Sin C#. Sin deploy. Sin reinicio. El punto que estamos haciendo toda la sesión: las habilidades son CONTENIDO, no CÓDIGO. Cualquiera del equipo puede escribir una — tu PM, tu QA, tu líder de seguridad.

NOTAS DEL ORADOR — vs herramientas. Mucha gente ve las habilidades y pregunta "¿no es solo un system prompt?" Sí — pero con estructura, ciclo de vida y auditoría. La columna crucial es "autoría". Las herramientas requieren un ingeniero; las habilidades no. Ese único hecho cambia quién en la empresa puede moldear el comportamiento del agente. La superficie de riesgo también es genuinamente distinta — las habilidades no pueden ejecutar código en v1 (S-8) pero pueden hacer prompt-injection al modelo para que haga cosas malas, por eso importan las puertas de aprobación y la habilitación por agente.

NOTAS DEL ORADOR — bug estrella. Doloroso de admitir pero vale la pena mostrarlo. Teníamos dos subsistemas de habilidades que ambos funcionaban, ninguno sabía del otro. Click en disable, el agente sigue usando la habilidad. Click en install, el archivo cae en una carpeta que el agente no escanea. El hot reload recarga el loader que el agente no usa. Esto fue el catalizador de toda la propuesta de habilidades.

NOTAS DEL ORADOR — solución. Eliminamos el cargador paralelo. MAF — Microsoft Agent Framework — ya implementa la especificación completa de agentskills.io, incluyendo divulgación progresiva, parsing de YAML y herramientas de recursos. Nuestro trabajo se vuelve un decorador delgado scoped que agrega tres cosas: atribución de capa, filtrado de habilitación por agente y logging estructurado. La UI llama a nuestro registry; el registry alimenta a MAF; MAF alimenta al agente. Un solo pipeline.

NOTAS DEL ORADOR — 3 capas. Tres capas, una regla de precedencia. System se entrega con la aplicación y es de solo lectura. Installed es compartido por todos los agentes y vive detrás del pipeline de importación. Agents-slash-name son overrides por agente — tanto para habilitación (enabled.json) como para habilidades genuinamente personalizadas. Quarantine es donde caen las importaciones antes de la aprobación. La capa más alta gana en colisiones de nombre, así que un agente puede ocultar una habilidad del sistema con la suya propia.

NOTAS DEL ORADOR — almacenamiento compartido. La decisión de Drummond del review de seguridad. La amenaza es lo que entra al system prompt en runtime, no lo que está en disco. Si copiamos cada habilidad instalada en la carpeta de cada agente, tendríamos N copias para actualizar en cada CVE. Los usuarios saltarían la puerta de aprobación solo para mantener el ritmo. Almacenamiento compartido con habilitación por agente es a la vez más seguro y más sensato.

NOTAS DEL ORADOR — frontmatter. agentskills.io es la spec abierta con la que nos alineamos. Define los campos núcleo — name, description, license — y reserva un namespace metadata para extensiones del proveedor. Nuestros extras (tags, category, examples) se mueven a metadata.openclawnet.* para ser compatibles a futuro con cualquier otro host que hable la spec. MAF parsea YAML correctamente, incluyendo strings multi-línea entrecomillados — nuestro parser hecho a mano se atragantaba con esos.

NOTAS DEL ORADOR — campos antiguos. El gran cambio es eliminar enabled-true del frontmatter mismo. Por qué: la habilitación es por agente, no por habilidad. Ponerlo en el archivo lo hace ver global. Los valores por defecto para "qué built-ins están on por defecto para nuevos agentes" se mueven a un SystemSkillsDefaults.json en el content root del gateway. Separación más limpia entre "qué es la habilidad" y "quién la tiene activada".

NOTAS DEL ORADOR — watcher. FileSystemWatcher es notoriamente ruidoso — VS Code guarda un archivo escribiendo un temp, eliminando el original y renombrando. Tres eventos por un guardado. Coalescemos con un debounce de 500ms: cualquier número de eventos dentro de la ventana colapsa en una sola reconstrucción. Watchers por capa porque las capas pueden vivir en discos o volúmenes distintos y queremos dominios de falla independientes.

NOTAS DEL ORADOR — snapshot. Decisión de diseño importante. Cuando el watcher dispara no metemos la mano en las conversaciones en curso para parchearlas. Reconstruimos un nuevo snapshot inmutable. Los turnos activos terminan en el snapshot viejo; el siguiente turno toma el nuevo. Esta es la respuesta a la pregunta abierta Q2 de Bruno en la propuesta — "¿auto-recarga a mitad de conversación? no — snapshot por turno." Evita lecturas rotas y mantiene una sola conversación determinística.

NOTAS DEL ORADOR — enabled.json. Lógica de tres valores. enabled = encendido explícito. disabled = apagado explícito. use-default = "pregúntale al registry cuál es el default" — útil para habilidades nuevas de las que el agente aún no ha sido informado. El default para habilidades externas recién importadas es disabled. El default para built-ins es enabled. El estado autoritativo vive en SQLite para poder consultar "muéstrame todo agente que tenga la habilidad X habilitada" sin leer cada archivo JSON. El JSON en disco es la proyección amigable para humanos.

NOTAS DEL ORADOR — fail closed. Esto es S-7 de la propuesta — cierre seguro. Es fricción deliberada. No queremos que un usuario acepte un diálogo de importación y que el contenido de la habilidad se cuele en el system prompt de cada agente. Dos gestos: importar y habilitar. El paso de habilitar te hace elegir qué agentes lo reciben. Si importaste por accidente, ningún agente se ve afectado.

NOTAS DEL ORADOR — página de Habilidades. Promovimos las habilidades fuera del submenú de Settings a un ítem de navegación de nivel superior. Dos mitades: Explorar (izquierda) y Actuar (derecha). La columna "Habilitada en" es la clave — de un vistazo ves qué agentes tienen qué habilidades. El modal de asignación por agente es donde cambias la habilitación; lo veremos a continuación.

NOTAS DEL ORADOR — modal de asignación. Diálogo único. Toggle por defecto en la parte superior — qué heredan los nuevos agentes. Dropdowns por agente abajo — encendido explícito, apagado explícito o "usar default". Save persiste a SQLite, la proyección de archivo se actualiza en la próxima reconstrucción de snapshot. El texto "efectivo en el siguiente turno de chat" es importante — establece expectativas sobre la semántica de hot-reload.

NOTAS DEL ORADOR — asistente de importación. Cuatro pasos, deliberadamente. Entrada recolecta la URL. Vista previa es el paso crítico para seguridad: lista completa de archivos, tamaños, SHA-256 por archivo, Markdown renderizado para que veas lo que el modelo verá, y un diff contra cualquier versión instalada previamente. La advertencia "este contenido entra al system prompt" no es descartable — esa única oración es la diferencia entre un usuario informado y un click-through.

NOTAS DEL ORADOR — allowlist. S-12 de la propuesta. v1 se entrega con una fuente confiable. El razonamiento: permitir URLs arbitrarias de GitHub convierte la importación en un primitivo de prompt injection remoto. Anclar a un commit SHA derrota ataques de time-of-check/time-of-use donde el upstream cambia entre vista previa y confirmación. Para agregar una nueva fuente, un admin edita appsettings — fricción deliberadamente alta.

SPEAKER NOTES — autoría manual. Válvula de escape crítica. La tubería de importación es la ruta más segura para compartir habilidades. Pero día a día, tú y tu equipo escribirán habilidades manualmente en su editor, las guardarán bajo agents/{name}/skills/, y el watcher las recogerá automáticamente. Sin reinicio, sin llamadas API. Así es también cómo iteras mientras escribes una habilidad — guardar, probar, guardar, probar.

SPEAKER NOTES — anatomía. Ejemplo concreto que podemos copiar y pegar. Frontmatter en la parte superior — delimitado por triple guion. Cuerpo debajo — Markdown puro. El cuerpo se convierte en parte del prompt del sistema textualmente. El array de ejemplos es solo metadatos — no se inyecta, pero la UI lo usa para mostrar sugerencias "intenta preguntar…". El archivo total suele tener menos de 2KB; el máximo que permitimos es 256 KB (S-11) para que una sola habilidad no explote tu presupuesto de tokens por accidente.

SPEAKER NOTES — limitados. Los presupuestos de tokens son dinero real. Limitamos el tamaño de cada archivo de habilidad y la contribución total al prompt del sistema. Si tres habilidades grandes están habilitadas y exceden el presupuesto, la más antigua por orden de carga se descarta — pero hacemos auditoría WARN para que puedas enterarte. El presupuesto por defecto es 8KB que cabe cómodamente en cualquier ventana de contexto moderna. Configurable por agente.

SPEAKER NOTES — S-1..S-6. Primera mitad de la lista de endurecimiento. S-1 es el manifiesto. S-2 es la lista permitida de tipos de archivo — sin .py, .ps1, .dll, sin bit ejecutable, nada que empiece con MZ o shebang. S-3 delega la resolución de rutas a la capa de almacenamiento. S-4 evita que una habilidad maliciosa reclame ser el shell-exec integrado. S-5 es previsualización/confirmación de dos pasos. S-6 significa que los cambios upstream nunca se aplican silenciosamente — ejecutas nuevamente la puerta.

SPEAKER NOTES — S-7..S-12. Segunda mitad. S-7 ya lo cubrimos. S-8 es "sin contenido ejecutable todavía" — esa es una propuesta futura con su propia caja de arena. S-9 pista de auditoría cubre cada evento del ciclo de vida para que puedas responder "quién instaló qué cuándo" forenseménte. S-10 — deshabilitar toma efecto en el siguiente turno de chat, no en el siguiente reinicio del proceso. S-11 presupuestos de tokens. S-12 lista permitida de fuentes. Doce invariantes, cada PR es revisado contra ellas, sin excepciones para v1.

SPEAKER NOTES — registro. Catorce eventos estructurados que cubren el ciclo de vida completo. Los generadores de código LoggerMessage nos dan registro de asignación cero en las rutas críticas. Ocho campos de correlación significa que una sola consulta SQL puede responder "muéstrame todo lo que pasó durante el último turno de chat de este usuario incluyendo qué habilidades se cargaron, qué funciones se invocaron, qué intentos de importación hubo". Los spans OTel van directamente al panel de Aspire que viste en la Parte 1.

SPEAKER NOTES — qué no registramos. Regla crítica de sensibilidad. Los valores de parámetros pueden contener cualquier cosa — claves API, contraseñas, PII. Valores de retorno también. Los cuerpos de SKILL.md son controlados por el atacante, así que registrarlos amplifica ataques de inyección de log. Registramos ESQUEMA — tipos y formas — más tamaño y un hash parcial. Eso es suficiente para forense, no suficiente para filtrar. Recomendación de Dylan en la propuesta.

SPEAKER NOTES — E2E. Seis pruebas end-to-end son los criterios de aceptación para K-1 (la ola fundacional). Cada una es una solicitud HTTP real contra una gateway en ejecución. La prueba de recarga en caliente suelta un archivo y espera un turno. La prueba por agente afirma aislamiento. La prueba de importación ejercita la tubería completa de previsualización-confirmación-instalación incluyendo verificación SHA y limpieza de cuarentena. Ejecutamos estas en CI en cada PR.

SPEAKER NOTES — olas. Cuatro olas. K-1 es la fundación — eliminar el cargador paralelo, obtener un registro. K-2 es observabilidad. K-3 es la tubería de importación. K-4 es pulido de UX. La flecha de dependencia en la parte inferior es el remate de toda esta sesión: las habilidades dependen del almacenamiento. No podemos escribir contenido proporcionado por el usuario al disco de forma segura hasta que la capa de almacenamiento imponga contención. Que es exactamente la Parte 4.

SPEAKER NOTES — divisor de Parte 4. Veinte diapositivas sobre almacenamiento. Esta es la parte portante de la sesión. Sin H-1..H-8, las habilidades no pueden enviarse de forma segura; sin una raíz predeterminada sensata, los usuarios no pueden encontrar sus archivos. Ambos problemas, un diseño.

SPEAKER NOTES — pregunta de Bruno. Cita directa del issue que empezó esto. Bruno quiere UNA raíz, descubrible, predecible. El predeterminado de hoy está enterrado en bin/Debug/net10.0/ — inútil para usuarios finales. Necesitamos arreglar el predeterminado Y endurecer cada ruta de código que toma una ruta del LLM.

SPEAKER NOTES — qué estaba mal. Seis problemas concretos. Cinco sobre descubribilidad. Uno sobre seguridad. La última viñeta es la peligrosa: incluso después de que arreglamos el predeterminado a C:\openclawnet, el agente TODAVÍA podía escribir en cualquier parte del disco emitiendo una ruta absoluta. Redirección no es restricción. La revisión de endurecimiento hizo eso explícito.

SPEAKER NOTES — predeterminados. Así es como se ve tu C:\openclawnet después de la Sesión 3. Siete subcarpetas bien conocidas, cada una con un propósito claro. Los agentes obtienen su propia carpeta por nombre de agente — base para futura aislación por agente. Los modelos son compartidos. Workspaces son áreas de scratch nombradas por usuario. Uploads y exports separan archivos de usuario entrantes de salientes. Skills viste. Dataprotection-keys lo cubriremos en la diapositiva de ACL.

SPEAKER NOTES — config. Tres fuentes. La variable de entorno gana para que contenedores y CI puedan sobrescribir sin tocar JSON. Appsettings es la respuesta cotidiana. El predeterminado integrado se activa para UX de primera ejecución. AdditionalWritablePaths es la lista permitida explícita para "sí, quiero que el agente también pueda escribir aquí" — usado con cuidado. El log INFO de inicio es la recomendación de endurecimiento: la mala configuración se vuelve visible en el panel, no silenciosa.

SPEAKER NOTES — nombre único. Modelo de amenaza sutil de Drummond. Si respetas tanto OPENCLAWNET_STORAGE_ROOT como OPENCLAW_STORAGE_DIR, un atacante que puede configurar una pero no la otra en un contenedor mal configurado redirige todas tus escrituras. Elige un nombre, documéntalo en voz alta, ignora todo lo demás. El log de inicio incluye la FUENTE del valor — variable de entorno, appsettings, o predeterminado — para que la mala configuración esté a un vistazo del panel de Aspire.

SPEAKER NOTES — resolvedor. El punto único de estrangulamiento. Cada herramienta que toma una ruta delega a este resolvedor. Dentro de él, todos los invariantes de endurecimiento viven en UNA clase comprobable — no cinco copias en cinco herramientas. El parámetro scope es la costura para futura aislación por agente: hoy predetermina a RootPath; mañana podemos pasar agents/{name}/ sin romper la API.

SPEAKER NOTES — H-1. Invariante más importante. Cada escritura — cada una — tiene que aterrizar bajo la raíz de almacenamiento o bajo una lista permitida de rutas adicionales explícitas. Las lecturas pueden ser más amplias porque las lecturas son de menor riesgo y a veces legítimamente necesitas mirar un proyecto hermano. La decisión de diseño crucial: RECHAZAR, no REESCRIBIR. Si el LLM emite C:\Windows\System32, decimos "no" — no decimos "redigiré silenciosamente eso a C:\openclawnet\Windows\System32".

SPEAKER NOTES — H-2. La regla de "detener implementaciones proliferantes". La forma más confiable de asegurar que cada resolución de ruta esté endurecida es tener solo UN lugar que lo haga. Tenemos un elemento de lista de verificación de revisión de código: cualquier herramienta nueva que tome una cadena de ruta debe inyectar ISafePathResolver y delegar. Sin excepciones. Las pruebas unitarias adversarias viven junto a ella.

SPEAKER NOTES — H-3. Sutil. Path.GetFullPath NO sigue puntos de reanálisis — resuelve .. y barras redundantes, pero una unión de directorio dentro de la raíz de almacenamiento apuntando a C:\Windows pasa la verificación de prefijo. Usamos ResolveLinkTarget en la ruta final Y en cada directorio padre. Sí, es costoso en rutas frías; cacheamos. Los enlaces simbólicos creados por el agente están prohibidos de plano — demasiado fácil de usar como escondite.

SPEAKER NOTES — H-4. Bug de prefijo de cadena que muerde a cada biblioteca de manejo de rutas en algún punto. C:\openclawnet vs C:\openclawnet-evil — mismo prefijo, directorio diferente. La corrección es requerir igualdad O startswith de root+separador. Hay una prueba de regresión para este par exacto para que una refactorización futura no pueda reintroducir el bug.

SPEAKER NOTES — H-5. Adiós a la vieja lista de denegación de tres subcadenas. Lista permitida gana a lista de denegación cada vez. Límite de sesenta y cuatro caracteres porque MAX_PATH de Windows se pone feo después de eso. Nombres de dispositivos reservados — CON, PRN, AUX, NUL, COM1-9, LPT1-9 — son especiales en Windows y crearían un directorio que no puedes eliminar. Puntos y espacios finales también: Windows los elimina silenciosamente, así que "foo." y "foo" colisionan de formas sorprendentes.

SPEAKER NOTES — H-6. Con visión de futuro. Hoy el runtime del agente pasa scope=null y el resolvedor usa RootPath. Mañana cuando enviemos aislación multi-agente — agente de Slack vs agente de Telegram vs agente de investigación — el runtime puede pasar agents/SlackAgent/ y esa única invocación de agente solo puede escribir en su propio subárbol. La filtración entre agentes se vuelve imposible. Hoy: solo el parámetro. Mañana: la política.

SPEAKER NOTES — H-7. Endurecimiento de ACL para los directorios que contienen secretos. Por defecto C:\openclawnet hereda de la raíz del volumen, que en la mayoría de las instalaciones de Windows otorga Users(OI)(CI)M — cada usuario local puede leerlo. Ese es el predeterminado incorrecto para un directorio que contiene claves de DataProtection de ASP.NET, tokens OAuth, y futuros bóvedas de claves API. Establecemos una DACL explícita en los subdirectorios de credenciales al inicio: usuario actual + SYSTEM, sin herencia. POSIX obtiene chmod 0700. Si falla la verificación, rechazamos iniciar los servicios que portan credenciales con un mensaje de remediación claro.

SPEAKER NOTES — H-8. Cada escritura al disco deja un rastro. Las escrituras exitosas obtienen la ruta resuelta, conteo de bytes, SHA-256 del contenido, atribución de fuente (¿fue esto sugerido por el LLM o explícito del usuario?), ids de correlación. Las escrituras fallidas — path traversal bloqueado, ACL denegada, fallo de lista permitida de nombres — también son auditadas en WARN con la cadena de entrada original no resuelta para forense. Combinado con auditoría de habilidades (S-9 de la Parte 3) puedes decir exactamente qué pasó durante cualquier turno de chat.

SPEAKER NOTES — resumen. Ocho invariantes en una diapositiva. Memoriza estos — son el contrato que cualquier código que tome rutas debe satisfacer. Igual que la lista de habilidades S-1..S-12, cada PR se revisa contra ellos. Tenemos pruebas unitarias cubriendo cada uno con casos adversarios.

SPEAKER NOTES — conexión. Tres métodos de extensión, en este orden. AddOpenClawStorage vincula StorageOptions, asegura el árbol de directorios y ejecuta el endurecimiento de ACL. AddSafePathResolver registra el resolvedor singleton. AddOpenClawTools conecta cada herramienta incorporada para usar el resolvedor. Las herramientas personalizadas solo inyectan ISafePathResolver y están listas.

SPEAKER NOTES — interfaz de configuración. Hasta ahora tenías que saber sobre Storage:RootPath en appsettings para siquiera verificar dónde iban los archivos. La página Settings ahora tiene una tarjeta Storage mostrando la raíz actual, la FUENTE (para que sepas si vino de env o config), y espacio libre. Mover-raíz requiere un reinicio por diseño — no queremos migrar escrituras en vivo. El estado de ACL expone violaciones de H-7 como puntos rojos.

SPEAKER NOTES — migración. Eliminamos el sufijo /storage entre lanzamientos. Las instalaciones existentes perderían el rastro de sus archivos a menos que manejemos la migración explícitamente. En el primer arranque detectamos el diseño antiguo, preguntamos al usuario, y ya sea movemos atómicamente o mantenemos la raíz antigua fija vía config. El flag de CLI existe para despliegues desatendidos donde no es posible preguntar.

SPEAKER NOTES — divisor de Parte 5. La parte prospectiva. La memoria está mayormente diseñada, parcialmente construida, y la versión de grado de producción es lo que la Sesión 4 recogerá. Ocho diapositivas sobre cuál es el problema, cuál es la estrategia y qué viene después.

SPEAKER NOTES — problema de ventana de contexto. Por qué importa la memoria. Incluso con una ventana de contexto de 128K, cada turno reenvía todo el historial. Después de 100 mensajes tus prompts son enormes, tu latencia es alta, tu GPU está caliente, y el modelo empieza a perder el medio del contexto de todos modos (el problema de atención en forma de U). Truncamiento ingenuo — soltar los N más antiguos — significa que el agente olvida el nombre del usuario. Ambos son malos.

SPEAKER NOTES — estrategia. Estrategia de tres niveles. Los mensajes recientes se mantienen verbatim porque el modelo los necesita palabra por palabra para coherencia. Los mensajes más antiguos colapsan en un resumen de párrafo. Los mensajes muy antiguos están fuera del prompt activo completamente pero almacenados en un índice vectorial — el agente puede recuperarlos por similitud semántica cuando sea relevante. Este es el patrón estándar en todos los frameworks de agentes modernos.

SPEAKER NOTES — entidad. Una entidad pequeña de EF Core. SessionId es la clave foránea, Summary es la prosa real, CoveredMessageCount le dice al compositor de prompt "los primeros N mensajes de esta sesión están resumidos — empieza verbatim desde el mensaje N+1". Cada resumen es inmutable; se agregan nuevos resúmenes en lugar de actualizarse, para que podamos reconstruir cualquier vista histórica de la conversación.

SPEAKER NOTES — interfaz. Tres métodos. Obtener el último resumen para que el compositor pueda inyectarlo. Almacenar un nuevo resumen cuando el umbral se dispara. GetStatsAsync alimenta el panel de memoria de UI. El patrón factory es el correcto para servicios async — servicio singleton, DbContext con alcance por llamada, sin trampas de seguridad de hilos. El umbral de 20 mensajes es configurable por sesión.

SPEAKER NOTES — embeddings. Los embeddings son la primitiva que potencia la búsqueda semántica. Microsoft ofrece APIs de embedding administradas pero para desarrollo local-primero usamos Elbruno.LocalEmbeddings que envuelve ONNX. 384 dimensiones es el tamaño all-MiniLM — diminuto, rápido, suficientemente bueno para recuperación conversacional. El ejemplo muestra la ganancia: "dependency injection" e "IoC container" se embeben a vectores que son 0.82 similar por coseno aunque no comparten palabras superficiales.

SPEAKER NOTES — búsqueda semántica. El eventual tercer nivel. Cada mensaje se embebe una vez y se almacena en caché. Llega una nueva pregunta, la embebemos, ejecutamos un escaneo de similitud de coseno sobre embeddings de mensajes pasados, tomamos los top K, inyectamos esos fragmentos como contexto adicional. SQLite no es una base de datos vectorial pero a escala de historial de conversación (miles de mensajes) es perfectamente adecuado — usamos una columna BLOB serializada y un top-K hecho a mano. Si creces más allá de eso, intercambia DuckDB o un almacén vectorial real sin cambio de API.

SPEAKER NOTES — panel. La transparencia es una característica. Los usuarios entran en pánico cuando una IA afirma "recordar" cosas — quieren saber cómo. El panel muestra mensajes totales, cuántos están resumidos (y en cuántos resúmenes), cuántos están aún verbatim, cuándo se disparó el último resumen, y el impacto en tokens. El número "eran 26 K" es el más poderoso — muestra el ahorro sin resumen en términos concretos.

SPEAKER NOTES — qué viene. Dónde estamos: el diseño está hecho, la entidad existe, las interfaces son estables. Lo que falta: un resumidor robusto que maneje fallas de modelo con gracia, un índice vectorial real, aislamiento de memoria por agente, e importación-exportación. Todo eso llega en Sesión 4 junto con proveedores en la nube y el programador. Para el final de Sesión 4 el agente recordará a través de reinicios y a través de sesiones.

SPEAKER NOTES — divisor de Parte 6. Tres demos, ocho minutos en total. Iniciaremos la pila, golpearemos la API de habilidades, y soltaremos una habilidad autoral manual para probar hot-reload.

SPEAKER NOTES — demo 1. Demo en vivo. aspire run en el repo, observa Aspire Monitor en la bandeja ponerse verde a medida que los recursos suben. Abre el dashboard, desplaza el log para encontrar la línea de almacenamiento que enviamos esta sesión — "Storage root resolved to C:\openclawnet (source: default)". Source es el valor que la revisión de endurecimiento pidió. Si la variable env está configurada, muestra "source: env". Si appsettings, "source: config". Visible de un vistazo.

SPEAKER NOTES — demo 2. Curl al endpoint de habilidades. Dos habilidades, ambas capa de sistema, ambas habilitadas para GptAgent. Nota la forma por agente: enabledFor es un array, no un booleano global. POST disable, luego envía un mensaje de chat — el agente responde sin la habilidad de memoria en su prompt. Verificamos revisando la auditoría de prompt. Esta es la API unificada que la diapositiva de error titular prometió.

SPEAKER NOTES — demo 3. El demo "wow". Crea una habilidad en tiempo real. Guarda el archivo. Observa el log de gateway emitir SkillDiscovered y SkillLoaded — eso es el FileSystemWatcher y la reconstrucción del registro disparándose. Envía un mensaje de chat en la UI y la respuesta es de repente dos oraciones y carece de "aprovechar". Sin reinicio. Sin despliegue. Sin código. Ese es todo el discurso en 60 segundos.

SPEAKER NOTES — divisor de cierre. Dos diapositivas más una diapositiva de pregunta.

SPEAKER NOTES — perspectivas. Cinco conclusiones. Las habilidades son contenido no código — esa es la ganancia más visible para el usuario. El almacenamiento es fail-closed — esa es la ganancia de seguridad. La memoria es transparente — esa es la ganancia de confianza. La portabilidad de llamada de herramientas es la ganancia de ingeniería. Los monitores de bandeja son la ganancia de experiencia de desarrollador. Cada ítem mapea a una de las seis partes de la sesión.

SPEAKER NOTES — lo que construimos. Doce marcas de verificación. La mitad son visibles para el usuario (monitores, página de habilidades, tarjeta de configuración). La mitad son calidad bajo el capó (refactors, sanitizadores, endurecimiento). Las dos mitades van juntas: las características visibles para el usuario solo son seguras PORQUE el trabajo bajo el capó llegó primero. Esa es la lección de la sesión 3.

SPEAKER NOTES — vista previa de Sesión 4. Hacia dónde vamos después. Proveedores en la nube significa que el mismo agente se ejecuta contra Azure OpenAI sin cambios de código — el trabajo de alineación de llamada de herramientas en la Parte 2 es lo que hace eso posible. La programación significa trabajos impulsados por cron que el agente ejecuta autónomamente. La memoria a escala termina el trabajo que esbozamos en la Parte 5. Las pruebas + health checks convierten el demo en un despliegue. La Sesión 4 es el final.

SPEAKER NOTES — cierre. Gracias a todos. El repo es github.com/elbruno/openclawnet, licencia MIT. Todo lo de hoy — diapositivas, script del orador, prompts de copilot, los documentos de propuesta — vive bajo docs/sessions/session-3/. Las dos nuevas herramientas se instalan con un comando dotnet tool install -g y viven en tu bandeja. Si quieres extender algo, el drop-in manual de habilidad es el punto de partida más gratificante: escribe un SKILL.md, guárdalo, ve al agente cambiar. ¿Preguntas?