Memory & state
Persistent agent state is declared, not implicit. One line at the top of the program names the memory and unlocks the whole family — memory, owner rules, and progress:
require memory("support-agent")
The declared name is the identity: state persists in <program-dir>/.synsema/state/<name>.db (gitignore .synsema/), keyed by that name — not by the filename. remember() in one run is found by recall() in the next; renaming the .syn file changes nothing; two entry points that declare the same name share one memory.
-- 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)
The declaration (know this before anything else)
- Without it, nothing persists —
remember,recall,add_rule,create_progress, and the rest of the family fail withCapability not granted: memory(which names the exact line to add), and no file or directory is created.memoryis never auto-granted, not even underrun: it writes files to disk. - One name per program. Two
require memorywith different names is a startup error. Names match[a-zA-Z0-9_-]+— no paths, no empty string; invalid names fail at the declaration. Barerequire memory(no name) is a parse error. - Every execution context shares it:
run,test,servehandlers, cron ticks,parallel_mapworkers, and spawned agents all see the program's one declared memory, saved on every write (a crash loses nothing already written). - Capability semantics like any other: denied inside
sandbox;call_toolintersects it (a tool must declarerequire memory("<name>")itself); the host ceiling gates it (--cap-setwithoutmemorydenies the family and creates no file;--cap-set "memory=shop-*"allows names under a prefix). - Env vars:
SYNSEMA_STATE_DIRrelocates the state directory (handy in tests).SYNSEMA_STATE_NAMEis deprecated and ignored with a warning — the declaration replaced it. - Upgrading: if an older engine left a
<file-stem>.dbbehind, running without a declaration prints a warning with the exactrequire memory("<stem>")line to keep it; the schema is unchanged (renaming the.dbis the whole migration). In the REPL, typingrequire memory("x")enables a session-only in-memory store.
Persistent memory
require memory("assistant")
remember("learning", "API slow on Mondays", ["api", "performance"])
let notes be recall("learning", ["api"]) -- newest first ([0] = most recent)
let hits be recall(nothing, nothing, "Monday") -- free-text search (skip args with nothing)
forget_memory(entry_id)
A recall entry is a map with id, category, content, source, tags.
recall takes 6 args — recall(category, tags, search, mode, limit, from) — all optional, all usable as named args (recall("learning", limit = 10)):
| Arg | Meaning |
|---|---|
category | one of the fixed categories (below) |
tags | list; OR by default |
search | free-text substring match |
mode | "all" = every tag must match (AND) |
limit | max entries, default 200 |
from | source namespace to read (see next section) |
Categories are a fixed English set:preference,rule,learning,decision,context. Any other string (e.g."preferencia") raises an error.
Per-agent namespaces (source / from)
Each entry records its writer in source: inside agent X a remember writes source = "X"; top-level code (and serve handlers) write "main". Reads are namespaced by default, so agents sharing one memory don't confuse each other's notes:
require memory("newsroom")
agent Writer
remember("context", "draft done") -- source = "Writer"
agent Analyzer
let mine be recall() -- only Analyzer's own entries (default)
let theirs be recall(from = "Writer") -- explicit cross-namespace read
let all be recall(from = "*") -- everything
spawn Writer
spawn Analyzer
let everything be recall() -- top-level sees ALL by default
Rules and progress are not namespaced — one rulebook, one plan board per program.
Progress (crash-resumable)
require memory("import-agent")
create_progress("import", ["ingest", "validate", "load"])
start_step("import", "ingest")
complete_step("import", "ingest", "100 rows") -- or fail_step(...)
let where be resume_point("import") -- "validate" — where to resume after a restart
Owner rules
require memory("pricing-agent")
add_rule("max_discount", "must", "discount <= 0.20", "pricing")
let violations be check_rules("pricing", {"discount": 0.25}) -- non-empty → violated
Levels: must (hard block), should (warning), avoid / prefer (preferences). Numeric conditions are evaluated against the context map. get_rules(category?) lists them; memory_summary() prints an overview of everything stored.
Serve state (per-process, in-memory)
Under serve, state_* builtins (e.g. state_incr("visits")) share in-memory state across requests — see HTTP server. (They're a serve feature, not available in plain run, and they need no memory declaration — they never touch disk.) For durable state, use the declared memory/progress builtins above or a database.