---
slug: 61-memory
title: Memoria y estado
description: Memoria persistente declarada del agente (require memory + remember/recall), namespaces por agente, progreso reanudable tras crash, reglas del owner y estado de serve por proceso.
example_ids: [memory]
---

# Memoria y estado

El estado persistente del agente se **declara, no es implícito**. Una línea al tope del programa nombra la memoria y habilita toda la familia — memoria, reglas del owner y progreso:

```synsema
require memory("support-agent")
```

El nombre declarado **es** la identidad: el estado persiste en `<dir-del-programa>/.synsema/state/<name>.db` (gitignoreá `.synsema/`), keyed por ese nombre — no por el nombre del archivo. Un `remember()` en una corrida lo encuentra `recall()` en la siguiente; renombrar el `.syn` no cambia nada; dos entry points que declaran el **mismo** nombre comparten una memoria.

```synsema
-- Doc example: DECLARED agent memory, progress, and owner rules (persist to SQLite).
-- The declared name IS the identity: state lives in .synsema/state/doc-memory.db,
-- keyed by the name below — not by this file's name. Without the declaration the
-- whole family fails with `Capability not granted: memory` and creates no files.
intent: "doc example: declared memory, namespaces, progress, rules"
require memory("doc-memory")

remember("learning", "demo note from run", ["demo"])
print("recalled → " + text((recall("learning", ["demo"])[0])["content"]))

test "remember then recall — newest first ([0] is the most recent)"
    remember("learning", "API is slow on Mondays", ["api"])
    let notes be recall("learning", ["api"])
    assert_eq((notes[0])["content"], "API is slow on Mondays")

test "recall named args: limit caps results; from filters by writer namespace"
    remember("context", "note-a", ["ns"])
    remember("context", "note-b", ["ns"])
    assert_eq(length(recall("context", ["ns"], limit = 1)), 1)
    -- top-level writes source = "main"; agents write under their own name
    assert(length(recall("context", ["ns"], from = "main")) >= 2)
    assert_eq(length(recall("context", ["ns"], from = "nobody")), 0)

test "progress: resume_point returns the step to resume from"
    create_progress("import", ["ingest", "validate", "load"])
    start_step("import", "ingest")
    complete_step("import", "ingest", "100 rows")
    assert_eq(resume_point("import"), "validate")

test "owner rules: a numeric must-rule flags a violation"
    add_rule("max_discount", "must", "discount <= 0.20", "pricing")
    assert(length(check_rules("pricing", {"discount": 0.25})) > 0)
```

## La declaración (sabelo antes que nada)

- **Sin ella, nada persiste** — `remember`, `recall`, `add_rule`, `create_progress` y el resto de la familia fallan con `Capability not granted: memory` (que nombra la línea exacta a agregar), y **no se crea ningún archivo ni directorio**. `memory` nunca se auto-otorga, ni siquiera bajo `run`: escribe archivos a disco.
- **Un nombre por programa.** Dos `require memory` con nombres distintos es un error de arranque. Los nombres siguen `[a-zA-Z0-9_-]+` — sin rutas, sin string vacío; un nombre inválido falla en la declaración. `require memory` pelado (sin nombre) es error de parseo.
- **Todos los contextos de ejecución la comparten**: `run`, `test`, los handlers de `serve`, los ticks de cron, los workers de `parallel_map` y los agentes spawneados ven la única memoria declarada del programa, guardada en cada escritura (un crash no pierde nada ya escrito).
- **Semántica de capability como cualquier otra**: denegada dentro de `sandbox`; `call_tool` la intersecta (una tool debe declarar su propio `require memory("<name>")`); el techo del host la gatea (`--cap-set` sin `memory` deniega la familia y no crea archivo; `--cap-set "memory=shop-*"` permite nombres bajo un prefijo).
- **Variables de entorno:** `SYNSEMA_STATE_DIR` relocaliza el directorio de estado (útil en tests). `SYNSEMA_STATE_NAME` quedó deprecada y se ignora con warning — la declaración la reemplazó.
- **Al actualizar:** si un engine anterior dejó un `<stem-del-archivo>.db`, correr sin declaración imprime un warning con la línea exacta `require memory("<stem>")` para conservarlo; el esquema no cambió (renombrar el `.db` es toda la migración). En el REPL, tipear `require memory("x")` habilita un store en memoria solo para la sesión.

## Memoria persistente

```synsema
require memory("assistant")
remember("learning", "API lenta los lunes", ["api", "performance"])
let notes be recall("learning", ["api"])        -- más nuevo primero ([0] = más reciente)
let hits  be recall(nothing, nothing, "lunes")  -- búsqueda libre (saltá args con nothing)
forget_memory(entry_id)
```

Una entrada de recall es un map con `id, category, content, source, tags`.

**`recall` toma 6 args** — `recall(category, tags, search, mode, limit, from)` — todos opcionales, todos usables como args nombrados (`recall("learning", limit = 10)`):

| Arg | Significado |
|---|---|
| `category` | una de las categorías fijas (abajo) |
| `tags` | lista; **OR** por defecto |
| `search` | búsqueda por substring |
| `mode` | `"all"` = todos los tags deben matchear (AND) |
| `limit` | máximo de entradas, default 200 |
| `from` | namespace `source` a leer (sección siguiente) |

> **Las categorías son un set fijo en inglés:** `preference`, `rule`, `learning`, `decision`, `context`. Cualquier otro string (p. ej. `"preferencia"`) da error.

## Namespaces por agente (`source` / `from`)

Cada entrada registra quién la escribió en `source`: dentro de `agent X` un `remember` escribe `source = "X"`; el código top-level (y los handlers de serve) escriben `"main"`. Las lecturas están namespaceadas por defecto, así los agentes que comparten una memoria no confunden sus notas:

```synsema
require memory("newsroom")

agent Writer
    remember("context", "borrador listo")    -- source = "Writer"

agent Analyzer
    let mine   be recall()                   -- solo las entradas propias de Analyzer (default)
    let theirs be recall(from = "Writer")    -- cruce de namespace explícito
    let all    be recall(from = "*")         -- todo

spawn Writer
spawn Analyzer
let everything be recall()                   -- el top-level ve TODO por defecto
```

Las reglas y el progreso **no** están namespaceados — un solo reglamento, un solo tablero de planes por programa.

## Progreso (reanudable tras crash)

```synsema
require memory("import-agent")
create_progress("import", ["ingest", "validate", "load"])
start_step("import", "ingest")
complete_step("import", "ingest", "100 filas")    -- o fail_step(...)
let where be resume_point("import")               -- "validate" — dónde reanudar tras un restart
```

## Reglas del owner

```synsema
require memory("pricing-agent")
add_rule("max_discount", "must", "discount <= 0.20", "pricing")
let violations be check_rules("pricing", {"discount": 0.25})   -- no vacío → violada
```

Niveles: `must` (bloqueo duro), `should` (warning), `avoid` / `prefer` (preferencias). Las condiciones numéricas se evalúan contra el map de contexto. `get_rules(category?)` las lista; `memory_summary()` imprime un resumen de todo lo guardado.

## Estado de serve (por proceso, en memoria)

Bajo `serve`, los builtins `state_*` (p. ej. `state_incr("visits")`) comparten estado **en memoria** entre requests — ver **[Servidor HTTP](/es/0.6.x/40-serve)**. (Son una feature de serve, no disponibles en `run` plano, y no necesitan declaración de `memory` — nunca tocan disco.) Para estado durable, usá los builtins de memoria/progreso declarados de arriba o una base de datos.
