Agents · 2026-05-24 · By RedDB team · 5 min read
Building a "remember this" skill end-to-end
A 30-minute walkthrough — turn a Claude Code skill into a durable note-taker. The skill captures the current conversation slice, embeds it, writes it to RedDB, and surfaces it on the next SessionStart. Every file you need, in order.
What we’re building
Three earlier posts in this pillar set the table:
- RedDB as Claude Code’s memory backend — schema,
SessionStarthook, retrieval query. - Slash commands with memory —
/remember,/recall,/forgetas the human-driven write path. - Skills as data — skill metadata and runs as queryable rows.
A slash command needs a human to type it. A SessionStart hook needs a session boundary. Neither catches the moment in the middle of a turn where a fact deserves to be remembered — a correction, a constraint, a piece of domain language the agent just learned. That is what a skill is for: the harness picks it up by description, the agent invokes it without ceremony, the side effect lands in the database.
By the end of this post you have a skill called remember-this:
- Reader installs three files (
SKILL.md,capture.sh, an embedder). - Mid-turn, agent decides “this is worth keeping” and invokes the skill with a short body.
- Skill embeds the body, writes it to the
memorytable from D1, returns a confirmation. - Next session, the
SessionStarthook surfaces the row in context if it scores against the active task.
Total moving parts: one markdown file, one shell script, one HTTP call. No new infrastructure beyond the RedDB instance from D1.
Prerequisites
- A RedDB instance reachable from your workstation. The examples assume
http://localhost:7878and a bearer token in$REDDB_TOKEN. - The
memorytable from the D1 post. Repeated here so this post stands alone:
CREATE TABLE memory (
id TEXT PRIMARY KEY,
kind TEXT NOT NULL,
scope TEXT NOT NULL,
body TEXT NOT NULL,
embedding VECTOR(1024) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX memory_scope_idx ON memory (scope, created_at DESC) WHERE deleted_at IS NULL;
CREATE INDEX memory_embedding_idx ON memory USING hnsw (embedding vector_cosine_ops) WHERE deleted_at IS NULL; - An embedding endpoint. The examples call
voyage-3via the Voyage HTTP API; swap in any 1024-dim model and the code is the same. Set$VOYAGE_KEY. - Claude Code installed with skills enabled (any recent release).
File 1 — SKILL.md
Drop at ~/.claude/skills/remember-this/SKILL.md:
---
name: remember-this
description: Persist a fact, correction, or constraint into long-term memory mid-turn. Use when the user states a preference, corrects an approach, names a non-obvious constraint, or shares a piece of domain knowledge that should outlive the current session. Do not invoke for ephemeral task state, code changes, or anything already obvious from the repo. Invoke at most once per logical fact.
---
# remember-this
When this skill matches, capture the fact you just learned.
## Steps
1. Decide what to save. One memory equals one self-contained fact. Strip surrounding chat. Rephrase in third person if the user said "I" — future-you will not know who "I" was.
2. Pick a `kind`:
- `user` — durable facts about the user (role, expertise, tools they own)
- `feedback` — corrections to your approach, with the *why*
- `project` — facts about the current project that are not derivable from the code
- `reference` — pointers to external systems (Linear projects, Grafana boards, Slack channels)
3. Pick a `scope`:
- `global` — true regardless of project
- the current working directory — true here only
4. Run the capture:
```bash
~/.claude/skills/remember-this/capture.sh \
--kind=<kind> \
--scope=<scope> \
--body="<the fact, one sentence>" - Report back to the user in one line:
Remembered (<kind>, <scope>): <body>.
Do not
- Save the verbatim user message. Save the fact extracted from it.
- Save anything that would be obvious from reading
git logorCLAUDE.md. - Save more than one row per turn. If two facts appeared, invoke twice with two bodies.
- Invoke when the user is mid-task and just venting. Memory is for things that change future behavior.
Two things the description does, that matter for trigger quality:
- It tells the harness *when not* to fire. Skills with vague descriptions fire on every turn and the user disables them within a day.
- It encodes the unit of work: one fact per invocation. The agent will otherwise dump entire chat windows into a single row and the embedding becomes useless.
## File 2 — `capture.sh`
Drop at `~/.claude/skills/remember-this/capture.sh`. `chmod +x` it.
```bash
#!/usr/bin/env bash
set -euo pipefail
KIND=user
SCOPE=$(pwd)
BODY=
for arg in "$@"; do
case "$arg" in
--kind=*) KIND="${arg#--kind=}" ;;
--scope=*) SCOPE="${arg#--scope=}" ;;
--body=*) BODY="${arg#--body=}" ;;
*) echo "unknown arg: $arg" >&2; exit 2 ;;
esac
done
if [[ -z "$BODY" ]]; then
echo "--body is required" >&2
exit 2
fi
case "$KIND" in
user|feedback|project|reference) ;;
*) echo "invalid --kind: $KIND" >&2; exit 2 ;;
esac
ID=$(python3 -c 'import secrets,time;print(f"{int(time.time()*1000):x}{secrets.token_hex(6)}")')
EMBEDDING=$(
curl -sS https://api.voyageai.com/v1/embeddings \
-H "Authorization: Bearer ${VOYAGE_KEY}" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg input "$BODY" '{model:"voyage-3", input:[$input]}')" \
| jq -c '.data[0].embedding'
)
curl -sS -X POST "${REDDB_URL:-http://localhost:7878}/sql" \
-H "Authorization: Bearer ${REDDB_TOKEN}" \
-H 'Content-Type: application/json' \
-d "$(jq -nc \
--arg id "$ID" \
--arg kind "$KIND" \
--arg scope "$SCOPE" \
--arg body "$BODY" \
--argjson emb "$EMBEDDING" \
'{sql:"INSERT INTO memory (id,kind,scope,body,embedding) VALUES ($1,$2,$3,$4,$5)", params:[$id,$kind,$scope,$body,$emb]}')" \
>/dev/null
echo "ok $ID" The script is intentionally small. The contract with the model is “exit zero on success, non-zero with a message on failure”, so any breakage shows up in the agent’s turn as an error it can recover from (re-invoke, or apologise and continue). Three things to notice:
set -euo pipefailmakes silent failure impossible. A skill that pretends to save and doesn’t is the worst class of bug — the user trusts the memory exists and finds out three weeks later it never did.IDis a millisecond-precision timestamp prefix plus six random bytes. Sortable, unique, no extra ULID dependency.- The
INSERTis parameterised. The body comes from a language model; treat it like user input from the open internet.
File 3 — nothing (read-back already exists)
The SessionStart hook from D1 already reads top-K rows from memory scoped to the project. Once capture.sh writes a row, the next session sees it without any change to the read path.
If the D1 hook is not in place, the minimal version:
#!/usr/bin/env bash
# ~/.claude/hooks/session-start.sh
EMBED=$(printf '%s' "$CLAUDE_PROJECT_DIR" | embed-cli)
curl -sS "${REDDB_URL}/sql" \
-H "Authorization: Bearer ${REDDB_TOKEN}" \
-d "$(jq -nc --arg s "$CLAUDE_PROJECT_DIR" --argjson e "$EMBED" \
'{sql:"SELECT body FROM memory WHERE scope IN ($1,$'global') AND deleted_at IS NULL ORDER BY embedding <=> $2 LIMIT 8", params:[$s,$e]}')" \
| jq -r '.rows[] | "- " + .body' That output is appended to the session’s initial system message. The agent now opens every turn with the most relevant rows from prior conversations.
Testing it
Start a fresh session in any project. Steer the agent into territory worth remembering:
“I’m a Go engineer working on the React side of this repo for the first time. Frame frontend explanations in terms of backend analogues.”
A correctly-tuned remember-this should fire once and produce something like:
Remembered (user, /home/you/work/this-repo):
User is a Go engineer new to React in this repo;
prefers frontend concepts framed as backend analogues. Verify directly against the database:
SELECT id, kind, scope, body
FROM memory
WHERE scope = '/home/you/work/this-repo'
ORDER BY created_at DESC
LIMIT 5; Then start a new session in the same directory. The SessionStart hook should pull the row, and a question like “how should I structure this component?” should get answered in backend-analogue terms without you re-stating the preference.
What goes wrong in week one
Three failure modes, observed across the first dozen installs:
Over-firing. The skill description is the only thing the harness sees. If it reads as “save anything interesting”, the agent saves chat noise. Tighten the description until the skill fires roughly once per session of real work. Re-read the Do not block above — most of it exists because of an earlier over-firing rev.
Embedding cost surprise. Every /remember is one embedding call. At Voyage’s price it is fractions of a cent, but it is non-zero and it lives on the read path of every future session. If memory grows past ~10k rows for a single scope, prune. A /forget --before=30d --kind=feedback cleanup pass once a quarter keeps the index hot.
Scope confusion. scope=global looks tempting and is almost always wrong. A preference that holds for one project rarely holds for the next. Default to the project path; promote to global only after the same fact has been re-saved across three projects.
Why this shape
The skill is twenty-odd lines of markdown plus a thirty-line shell script. It deliberately does not include:
- A daemon. The skill runs synchronously on the agent’s turn. Latency is one HTTP roundtrip to the embedder and one to RedDB, well under a second.
- A queue. If the write fails, the agent sees the non-zero exit and tells the user. No silent retry, no dropped rows.
- An ORM. The SQL is short enough to read; the parameter binding is the only safety bit that matters.
The whole pillar’s argument is that a relational + vector database is a sufficient memory substrate for agent CLIs. This skill is the smallest end-to-end demonstration of that claim: one table, three files, real recall on the next session.
Next
The next post in this pillar takes the same memory table and adds the observability layer — every tool call, token, and cost landing in RedDB via a PostToolUse hook, queryable as “which skill cost me the most last sprint” and “p99 latency by tool”. The remember-this skill becomes one row among thousands in that view; the database stops being a memory store and starts being the agent’s substrate.
RedDB Cloud
Join the private beta.
One-click managed deploy. Free during beta. Founding pricing locked at GA.
Request access →