sense-memory
Give your AI agent a memory. Encrypted key-value storage and journal entries on Nostr relays, the agent writes to itself using its own keypair. Nobody else can read them.
Quickstart
import asyncio, os
from nostrkey import Identity
from sense_memory import MemoryStore
identity = Identity.from_nsec(os.environ["NOSTR_NSEC"])
store = MemoryStore(identity, "wss://relay.nostrkeep.com")
async def main():
# Key-value: store and recall
await store.remember("user_timezone", "America/Vancouver")
mem = await store.recall("user_timezone")
print(mem.value) # "America/Vancouver"
# Journal: append-only log
await store.journal("User prefers concise responses")
entries = await store.recent(limit=5)
asyncio.run(main())
How It Works
Two modes of persistence, both encrypted with NIP-44 using the agent's own keypair.
| Mode | Nostr Kind | Behavior | Use Case |
|---|---|---|---|
| Key-value | 30078 (NIP-78) | Replaceable by key | Preferences, state, facts |
| Journal | 4 (NIP-04 DM to self) | Append-only | Conversation logs, observations |
API
Key-Value Memories
# Store (overwrites if key exists)
await store.remember("user_name", "Vergel")
# Recall one
mem = await store.recall("user_name") # Memory | None
# Recall all
all_memories = await store.recall_all() # list[Memory]
# Delete
await store.forget("user_name") # NIP-09 deletion
Journal Entries
# Write (append-only)
await store.journal("Had a great conversation about scheduling")
# Read recent (newest first)
entries = await store.recent(limit=10) # list[JournalEntry]
Return Types
| Function | Returns | Description |
|---|---|---|
remember(key, value) | str | Event ID of stored memory |
recall(key) | Memory | None | Memory if found |
recall_all() | list[Memory] | All stored memories |
forget(key) | str | Event ID of deletion |
journal(content) | str | Event ID of journal entry |
recent(limit=20) | list[JournalEntry] | Recent entries, newest first |
Security
- All content is NIP-44 encrypted, only the agent's own keypair can decrypt
- Memory keys validated: no slashes, backslashes, null bytes, or path traversal
- Content length capped at 65,000 characters
- Relay queries capped at 1,000 events (memory exhaustion prevention)
- Decrypted key verified against queried key (tamper detection)
- No telemetry. No network calls except to the relay you configure
- Never hardcode an nsec, load from environment variable or encrypted file
Configuration
| Variable | Required | Description |
|---|---|---|
NOSTR_NSEC | Yes | Agent's nsec private key (bech32 or hex) |
NOSTR_RELAY | No | Relay URL (default: wss://relay.nostrkeep.com) |
| Constraint | Limit |
|---|---|
| Key length | 256 characters max |
| Value / content length | 65,000 characters max |
| Forbidden key chars | / \ null byte .. |
| Journal recent limit | 1 to 100 |
Nostr NIPs Used
| NIP | Purpose |
|---|---|
| NIP-01 | Basic event structure and relay protocol |
| NIP-04 | DM to self (journal entries) |
| NIP-09 | Event deletion (forget) |
| NIP-44 | Encryption for all stored content |
| NIP-78 | App-specific replaceable data (key-value memories) |
Part of the NSE Ecosystem
sense-memory uses NostrKey for identity and encryption. It sits alongside nostr-profile, NostrCalendar, NostrSocial, and social-alignment in the NSE identity stack.
The agent's keypair is the thread that runs through everything. One identity, many capabilities, and now, persistent memory.