Agent memory as normative source code: a lintable, queryable memory DSL for LLM agents
Project description
memdsl
English
Agent memory as normative source code.
Most agent memory systems store what you said. Almost none of them
distinguish what an agent must obey from what it may consider. memdsl
treats long-term memory as a small, typed, lintable source language —
.mem files you can read, review, diff, version, and test — where a
boundary is a rule to enforce, not a fact to recall.
preference schedule.deep_work_mornings {
subject: User
claim: "Prefers deep work in the morning; meetings after 2pm."
force: strong
scope: scheduling
evidence { source: chat quote: "Stop booking me into morning meetings." }
}
boundary privacy.no_family_in_public {
subject: User
rule: "Never include family member names or details in public-facing content."
force: hard
scope: global
exceptions: [user_explicit_override]
evidence { source: chat quote: "Anything about my family stays out of blog posts. Always." }
}
The difference matters: a preference shapes suggestions; a boundary binds. Flat embeddings of "user said X" erase exactly this distinction, which is why agents violate rules their memory technically "contains".
30 seconds: same question, one declaration apart
Ask an agent to "draft a public blog post about building Aurora as a working parent." A similarity-ranked memory gives it the old chats where the kids' names appear — and nothing that ranks a rule above a reminiscence. The retrieved snippets are all just CONTEXT, so the names end up in the post:
retrieved: "...shipped the sync fix after bedtime, Theo finally sleeping..."
retrieved: "aurora beta feedback thread..."
With the boundary declared, the evidence pack puts the rule where it cannot be missed — in MUST, above every piece of context, whether or not the query lexically matched it:
$ memdsl query examples/alex/ -q "draft a public blog post about aurora"
MUST
- [boundary:privacy.no_family_in_public] Never include family member names
or details in public-facing content. (exceptions: ['user_explicit_override'])
CONTEXT
- [state:aurora.beta_progress] Private beta with 40 testers; ...
Feed the pack to the LLM as context and the compliant behavior is to write
the post without family details — and to be able to cite
boundary:privacy.no_family_in_public as the reason. The pack is the
prompt; the MUST layer is what changes the answer.
Lint it, query it
Memory that can be wrong deserves a linter:
$ memdsl lint examples/lint-demo/
broken.mem:7: error[unresolved_symbol] subject 'User.Barista' is not a declared entity
broken.mem:20: error[missing_evidence] active fact 'home.timezone' has no evidence block
broken.mem:28: warning[type_force_mismatch] preference uses force: hard; promote it to a boundary
broken.mem:41: warning[boundary_without_exception] confirm it is truly unconditional
broken.mem:53: warning[stale_state] as_of 2025-11-02 is older than 180 days
6 declarations, 2 error(s), 3 warning(s)
Queries return a layered evidence pack, not a hit list. Hard boundaries surface even when the query doesn't lexically match them; declared conflicts are shown, not averaged away; open issues surface as gaps instead of being hallucinated over:
$ memdsl query examples/alex/ -q "should aurora keep the free tier"
# resolved subjects: Project.Aurora
MUST
- [boundary:privacy.no_family_in_public] Never include family member names ...
SHOULD
- (none)
CONTEXT
- [decision:aurora.pricing_free_tier] Keep a permanent free tier.
- [state:aurora.beta_progress] Private beta with 40 testers; sync is the top complaint. (as_of 2026-06-20)
- [goal:aurora.revenue_target_2026] Reach $2k MRR from Aurora by end of 2026.
CONFLICT
- [decision:aurora.pricing_free_tier] conflicts_with [aurora.revenue_target_2026]
MISSING
- open issue [open_issue:aurora.pricing_undecided]: Paid tier pricing is undecided: $5 flat vs usage-based.
Feed that pack — MUST/SHOULD/CONTEXT/CONFLICT/MISSING — to your LLM as context. Every line carries a declaration id, so answers are citable and auditable back to source and evidence.
Install
pip install memdsl # or: pip install -e . from a checkout
memdsl lint examples/alex/
memdsl query examples/alex/ -q "plan tomorrow morning"
memdsl explain examples/alex/ decision:aurora.db_postgres_migration
Zero runtime dependencies. Python 3.9+.
Note: memdsl lint examples/alex/ reports one intentional warning —
Alex's schedule.no_meetings_before_10 boundary declares no exceptions,
and the linter asks you to confirm it is truly unconditional. That nudge
is the feature; a clean run is examples/mira/.
What's in the box
- A tiny declarative language (
.mem): typed declarations (entity,fact,preference,boundary,principle,decision,state,open_issue) with force, scope, evidence, relations (supersedes,conflicts_with,refines, ...), and lifecycle status. Full grammar and semantics in docs/SPEC.md. - A linter with ten diagnostics: dangling symbols, missing evidence, ambiguous aliases, stale states, boundaries without exceptions, preferences masquerading as laws, unmarked supersede chains.
- A query executor implementing the EvidencePack contract, plus
explainfor tracing one declaration's relations and provenance. - Synthetic example personas (
examples/) — fictional users "Alex" and "Mira" — and a deliberately broken file for the linter demo.
What memdsl is not
- Not a replacement for Mem0,
Zep, or
LangMem. Those are
retrieval/extraction platforms. memdsl is a source format and
contract for the layer above: what memory means, how strongly it
binds, and how it is maintained. You could compile
.memfiles into any of them. - Not a retrieval engine. The reference executor is deliberately naive lexical matching — enough to demonstrate the contract. Production use should plug BM25/embeddings behind the same EvidencePack interface. Do not benchmark toy retrieval and conclude the format failed.
- Not an auto-writer. v0.1 has no write pipeline. The spec (§10) describes a gated, human-reviewed write path; automation should be earned with audited metrics, not assumed.
Does the approach work?
Early evidence from the private system this was extracted from (DigitalSelf, single-user): on a 100-question eval over the author's real long-term memory, DSL-structured retrieval nearly doubled top-1 precision against the same system's tuned RAG baseline (0.57 vs 0.30; hit rate 0.67 vs 0.53). On public conversational benchmarks (LongMemEval, LoCoMo) it performs at parity with baselines under a retrieval-only harness whose target mapping is still being audited — we explicitly do not claim public-benchmark wins. Current honest costs: seconds-level query latency at scale in the private implementation (being moved to write-time compilation), and n=1 personalization. A reproducible benchmark report will be published separately.
The interesting unmeasured dimension — and the reason this exists — is compliance, not recall: existing memory benchmarks test whether an agent can find a fact, none test whether it respects a boundary. A boundary-compliance benchmark is the roadmap's centerpiece.
Related work
Typed/structured agent memory is converging fast:
MemIR (typed memory IR, provenance-role
separation), Zep/Graphiti (temporal knowledge
graphs with fact validity windows), A-Mem
(Zettelkasten-style linked notes), MemOS
(memory scheduling), and the CLAUDE.md/AGENTS.md culture of local,
reviewable context files. memdsl's position in that landscape: local-first
plain-text source, an explicit normative layer (force + boundaries +
exceptions + MUST/SHOULD rendering), and code-style diagnostics as a
first-class surface.
Roadmap
- Target-mapping audit + reproducible benchmark report
- Boundary-compliance benchmark (does the agent respect MUST items?)
- Pluggable retrieval backends (BM25, embeddings) behind the EvidencePack contract
- Module directory compilation for query planning
- Gated write pipeline with review queue
Today, memdsl defines memory as typed, auditable source code. Future runtimes can navigate these declarations the way developers navigate code — following relations, inspecting evidence, tracing history, and asking for missing information instead of guessing.
License
Code: [MIT](LICEN
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file memdsl-0.1.0.tar.gz.
File metadata
- Download URL: memdsl-0.1.0.tar.gz
- Upload date:
- Size: 24.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72e07634ae1d8dd3f153711dcc2004c42bd7ac0a07218a79e676c208fa879c99
|
|
| MD5 |
ca4d4acdafc01c822120ac0e03e3d4b7
|
|
| BLAKE2b-256 |
6f2f32f95c88592a70c86997ace14c697579539a619296c621a9aa2fab8bf601
|
File details
Details for the file memdsl-0.1.0-py3-none-any.whl.
File metadata
- Download URL: memdsl-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d0e27257a37aad0fb5ebb983f8a4b00a91492400fba46dde0bb373a7edd71896
|
|
| MD5 |
0be800c672dd4cd452845df6010516eb
|
|
| BLAKE2b-256 |
641e65ff1821063b9885fd45f24087a784367bf81089d33d7c8ce844f624128c
|