Skip to content

SynapCores / Recipe

Tell Claude Code to Use SynapCores for Your App's Memory

Paste one prompt into Claude Code and get a chatbot memory that knows when a fact changed: REMEMBER per user, CURRENT for the live value, RECALL for history.

All recipes· claude-code· 10 minutesbeginner

Part of the guide AI agent memory: types, architecture and how to build it, with every agent-memory recipe in one place.

Share

The problem: your chatbot keeps notes about each user. In September Sam said "ship to Boston." Last week Sam moved to Denver. Store both notes in a table, search them by meaning, and the old one can win: "Sam lives in Boston and wants orders shipped there" sounds more like a shipping question than "Sam moved to Denver." The package goes to the wrong city.

What you want is a store that knows a newer fact replaces an older one, keeps the history, and gives you one answer you can act on. In SynapCores that's a MEMORY. This recipe has Claude Code build the module for you.

We build SynapCores. Everything here runs on the free Community Edition.

What you need

  • SynapCores Community Edition 2.4.8 or later, running locally (by default on port 8080). Download it.
  • Claude Code, opened in an empty folder (or in your app's repo).
  • An API key. Log in to get a session token, then create a key with it:
# 1. Log in. Copy access_token from the response.
curl -s -X POST http://localhost:8080/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username": "admin", "password": "<your admin password>"}'

# 2. Create a key. It's in raw_key, and it's shown once.
curl -s -X POST http://localhost:8080/v1/api-keys \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{"name": "claude-code", "permission": "FullAccess"}'
  • Two environment variables in the shell you start Claude Code from:
export SYNAPCORES_URL=http://localhost:8080
export SYNAPCORES_API_KEY=...   # the raw_key from step 2

The prompt tells Claude Code to read the key from the environment and never print it.

Step 1: Paste this prompt into Claude Code

Add long-term memory to this app using SynapCores, an AI-native database with a
built-in MEMORY store. Use the MEMORY statements below; don't design your own
memory table.

Connection: SynapCores is running at $SYNAPCORES_URL. Send each SQL statement as
  POST $SYNAPCORES_URL/v1/query/execute
  Headers: Authorization: Bearer $SYNAPCORES_API_KEY, Content-Type: application/json
  Body: {"database": "app", "sql": "<one statement>"}
Results come back in data.columns (a list of {"name", "data_type"}) and data.rows
(a list of rows, each a list of values in column order). Create the database
first with CREATE DATABASE IF NOT EXISTS app (send that one with "database": "main").

MEMORY syntax:
- Once:      CREATE MEMORY app_memory IDENTITY user_id WITH (llm_extraction = false)
- Store:     REMEMBER app_memory FOR user_id = <id> '<what the user said>'
               WITH (source_type = 'verified_user_statement',
                     attribute = '<snake_case_key>', value = '<value>')
- One value: CURRENT app_memory FOR user_id = <id> ATTRIBUTE <key>
- Question:  RECALL app_memory FOR user_id = <id> ABOUT '<question>'
Each MEMORY statement returns one row with a JSON string in the "envelope" column.
Parse it. In RECALL, data.current_state is what's true now, data.temporal is what
changed, and data.episodic holds the original sentences. A newer REMEMBER with the
same attribute replaces the old value and keeps it as history.

Build:
1. memory.py (Python 3, standard library only) with remember(user_id, text,
   attribute, value), current(user_id, attribute) and recall(user_id, question).
   Create app_memory on first use if it doesn't exist. Escape single quotes in
   text by doubling them. Read the URL and key from the environment and never
   print the key.
2. demo.py that:
   a. remembers for user 42, in this order:
      "Sam lives in Boston and wants orders shipped there." (ship_city = Boston)
      "Sam is allergic to wool, so never suggest wool sweaters." (allergy = wool)
      "Sam moved to Denver last week; ship everything to Denver from now on." (ship_city = Denver)
   b. prints current(42, "ship_city")
   c. prints the current_state and temporal parts of recall(42, "where do we send the package")
   d. prints current(7, "ship_city") for user 7, who has no memories
Run demo.py and show me the output.

The prompt carries the syntax, so Claude Code doesn't have to guess it from older docs.

Step 2: What Claude Code builds

Two files: memory.py and demo.py. In our run on 2.4.8, Claude Code wrote a small _execute() helper that posts one statement to /v1/query/execute, an _envelope() helper that parses the JSON in the envelope column, and then the three functions. Here they are as the agent wrote them (your run may differ in the details):

def remember(user_id, text, attribute, value):
    _ensure_memory()
    sql = (
        "REMEMBER {name} FOR user_id = {uid} '{text}' "
        "WITH (source_type = 'verified_user_statement', "
        "attribute = '{attr}', value = '{val}')"
    ).format(
        name=_MEMORY_NAME,
        uid=user_id,
        text=_escape(text),
        attr=_escape(attribute),
        val=_escape(value),
    )
    return _envelope(_execute(sql))


def current(user_id, attribute):
    _ensure_memory()
    sql = "CURRENT {} FOR user_id = {} ATTRIBUTE {}".format(
        _MEMORY_NAME, user_id, attribute
    )
    return _envelope(_execute(sql))


def recall(user_id, question):
    _ensure_memory()
    sql = "RECALL {} FOR user_id = {} ABOUT '{}'".format(
        _MEMORY_NAME, user_id, _escape(question)
    )
    return _envelope(_execute(sql))

The three functions are thin. Each one sends a single statement. All the logic that matters (which fact is current, what it replaced, which user it belongs to) runs inside the database.

Step 3: The demo output

This is the output from our run on 2.4.8, with request ids and timestamps trimmed and the JSON compacted:

current(42, 'ship_city'):
{
  "status": "ok",
  "operation": "CURRENT",
  "memory": "app_memory",
  "identity": {"field": "user_id", "value": "42"},
  "data": {
    "attribute": "ship_city",
    "value": "Denver",
    "confidence": 0.85,
    "authority": "verified_user_statement",
    "valid_to": null,
    "source_memory_id": "mem_1m70abs7v_ejmbbw",
    "supersedes_memory_id": "mem_1m70abs4e_9rckii"
  },
  "warnings": [],
  "error": null
}

recall(42, 'where do we send the package') current_state:
{
  "allergy":   {"value": "wool",   "confidence": 0.85, "source_memory_id": "mem_1m70abs6a_nm4my9"},
  "ship_city": {"value": "Denver", "confidence": 0.85, "source_memory_id": "mem_1m70abs7v_ejmbbw"}
}

recall(42, 'where do we send the package') temporal:
[
  {"attribute": "ship_city", "previous_value": "Boston", "current_value": "Denver"}
]

current(7, 'ship_city') (user with no memories):
{
  "status": "not_found",
  "operation": "CURRENT",
  "memory": "app_memory",
  "identity": {"field": "user_id", "value": "7"},
  "data": {"attribute": "ship_city", "value": null},
  "warnings": [],
  "error": null
}

Read it from the top:

  • current(42, "ship_city") is one value. Denver, where it came from (verified_user_statement), and the id of the Boston fact it replaced (supersedes_memory_id). No ranking, no threshold to tune.
  • recall answers a question, not a key. You don't need to know the attribute is called ship_city. You get what's true now (current_state) and what changed (temporal), so your bot can say "you moved from Boston, right? Shipping to Denver."
  • User 7 gets nothing. Every fact belongs to one user_id, so one user's allergy never shows up in another user's prompt.

The problem from the top of this page, measured on the same three memories: RECALL's episodic part, which ranks the original sentences by meaning, puts the Boston sentence first (relevance 0.44) and the Denver sentence second (0.36). current_state still says Denver. That's the difference between searching notes and asking a memory.

Step 4: Wire it into your chat loop

Paste a second prompt:

At the start of every chat turn, call recall(user_id, <the user's message>) and add
its current_state to the system prompt as "Known facts about this user". When the
user states a fact about themselves, call remember() with a snake_case attribute.
If there's no chat loop yet, create chat.py with one. Then print the system prompt
it would send for user 42's message "where should my order ship?".
Show me the diff.

In our run the folder had no chat loop, so Claude Code created chat.py. It calls recall(user_id, message) at the start of each turn, puts current_state into the system prompt, and calls remember() when the user states a new fact. The system prompt it printed for user 42 (your run may differ in the wording):

Known facts about this user:
- allergy: wool
- ship_city: Denver

Denver, not Boston, and the wool allergy, ready for the model to use. To send live turns, connect chat.py to the model you use (in one of our runs Claude Code wired in the Anthropic Python SDK, which needs pip install anthropic and ANTHROPIC_API_KEY).

Why this needs SynapCores

REMEMBER, CURRENT and RECALL are statements in the database's own SQL. The memory keeps three things together that you'd otherwise build by hand: the original sentence (episodic), the current value per attribute (state), and the history of what replaced what (temporal). It also embeds each sentence with the built-in model, so RECALL ... ABOUT finds the relevant facts by meaning. Your app sends four kinds of one-line statement and gets back structured JSON.

Next step

Download SynapCores Community Edition, paste the prompt, and give your bot a memory that knows when a fact has changed.

Related: How to make a chatbot remember previous conversations

Tags

claude-codeai-agentsagent-memoryrememberrecall