agent-memory
Skill2 starsChanged 2 months agoMITView on GitHub
Conventions for the durable session memory this toolkit keeps — one fact per file, the moment a note gets written, note frontmatter and metadata.evidence, the three-level index tree, the quota.lock ratchet, and which of project/cross-project/workplace/machine scope a fact belongs to. Load this before writing, moving, or reorganizing a memory note, or whenever start/workstatus/wrap need the ground rules — they assume these conventions rather than restate them.
38 other projects use a version of this.
Adopted by
Most recently taken up by
- isaacbitran/agent-memorytheir own version, first seen Nov 8, 2026
- msking18/agent-memory-mcptheir own version, first seen Nov 8, 2026
- komluk/scaffoldingtheir own version, first seen Nov 7, 2026
- MemTether/MemTethertheir own version, first seen Nov 1, 2026
- tigerless-labs/agent-memorytheir own version, first seen Oct 22, 2026
- brianschroeder/simple-agent-memorytheir own version, first seen Oct 21, 2026
Used with
Projects with it also use observability, data-analysis and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
OpenAISkillobservability
Vercel Observability expert guidance — Drains (logs, traces, speed insights, web analytics), Web Analytics, Speed Insights, runtime logs, custom events, OpenTelemetry integration, and monitoring dashboards. Use when instrumenting, debugging, or optimizing application performance and user experience on Vercel.
Also in 4 projects that use this
shanggqmSkilldata-analysis
数据分析技能,提供结构化的数据查询、统计分析和报告生成工作流。当用户需要查询数据库、进行数据统计分析、生成分析报告时使用。按规范输出原始数据、计算结果、Markdown报告和HTML可视化报告。
Also in 3 projects that use this
Microsoft AzureSkillskill-authoring
Write Agent Skills that comply with the agentskills.io specification. WHEN: "create a skill", "new skill", "write a skill", "skill template", "skill structure", "review skill", "skill PR", "skill compliance", "SKILL.md format", "skill frontmatter", "skill best practices". DO NOT USE FOR: general documentation. INVOKES: vally CLI.
Also in 3 projects that use this
VercelSkilltechnical-writing
Write, edit, review, or audit user-facing documentation for the eve repository. Use for changes under docs/, documentation tied to eve APIs or CLI behavior, docs work based on Slack or support feedback, and requests to make eve docs clearer, more natural, or less AI-patterned while verifying claims against current source, tests, CLI help, public releases, and repository conventions.
Also in 3 projects that use this
JetBrainsSkillwebapp-testing
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
Also in 4 projects that use this
AnthropicSkillpdf
Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.
Also in 5 projects that use this
Other versions38 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 38
- tigerless-labs/agent-memory★ 4.3ksame name
- databricks/app-templates★ 218same name
- OpenHands/extensions★ 167same name
- JKHeadley/instar★ 86same name
- nimadorostkar/Claude-Skills-collection★ 33same name
- VoDaiLocz/kilo-kit-mcp★ 28same name
- jayzeng/agentmemory★ 24same name
- komluk/scaffolding★ 16same name
- xChuCx/agent-memory★ 11same name
- t4sh/skills4sh★ 5same name
28 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository7 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- consolidateSkill
- initSkill
- startSkill
- workstatusSkill
- wrapSkill
The file
We split this skill into the rules it gives an agent
8 rules. 0 are common, 7 are distinctive, and 1 is very specific to this project.
0
Common
Most of the 33 edited versions we read keep these.
7
Distinctive
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 25
If that line is not there — a harness that does not print it — do not guess a cache path.
- Distinctive: line 34
Before writing a new note, check whether one already covers the ground.
- Distinctive: line 48
Every note opens with:
- Distinctive: line 122
no longer true → rewrite it in place.
- Distinctive: line 170
Not moments: finishing a task, making a commit, a green suite.
- Distinctive: line 181
The end of a session is the most expensive place to think: Every turn resends the whole window, the window only grows, and closing is where …
- Distinctive: line 216
When the corpus cap does trip, the linter now prints the per-half breakdown beside it, whether or not any per-half key is set.
- Distinctive: line 229
All of them are facts about this corpus, which is why they live with the memory.
“Common” is measured: kept by at least 6 in 10 of the 33 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Agent memory
This is not a rite like start, workstatus, or wrap. It has no steps of
its own and nothing runs at the end of it. It is the set of conventions the
other three assume: what a note looks like, where it lives, and how a session
knows it can trust one.
The memory lives under one configured directory — memory_dir in
.floppy/config, a repository default such as .agent-memory. Everything
below calls that directory "the memory directory" rather than naming it,
because the name is the consumer's choice, not this plugin's.
The few commands below call floppy's dispatcher, which ships with the plugin
and has no copy in this repository. The harness names this skill's own base
directory when it loads it — Base directory for this skill:
<plugin>/skills/agent-memory — and the dispatcher is scripts/run two
directories above that. Write that absolute path wherever a command says
<plugin>.
If that line is not there — a harness that does not print it — do not guess a
cache path. $CLAUDE_PLUGIN_ROOT and $CURSOR_PLUGIN_ROOT are worth trying
first, and a checkout the human can name is worth asking for; a guess whose
failure mode is running the wrong copy of the plugin is worse than stopping.
One fact per file
A memory note holds exactly one fact. A refinement of an existing fact edits the file that already states it — it does not add a second file next to it. Before writing a new note, check whether one already covers the ground.
That check has a second half: a new note pulls a revision of its
neighbours. Before saving, open the notes the new one [[links]] to, and
where the new fact refines or contradicts one of them, fold it into that note
or rewrite it — do not leave a sibling saying almost the same thing. Now is
the cheap moment for that merge: the session still holds the whole picture,
while a near-duplicate written today is consolidation debt that some later
session pays without it. The corpus that motivated this rule hit its quota
ceiling three times in one week with nothing stale to prune — on a live
corpus, merging at write time is the only pruning there is.
Note frontmatter
Every note opens with:
---
name: <slug, matches the filename without .md>
description: <one line, what the note is about>
metadata:
type: user | feedback | project | reference
evidence: measured | read | decided | sourced
as_of: <YYYY-MM-DD, the date the evidence is from> # optional
---name is the slug [[wikilink]]-style references resolve by, not the file
path — so moving a note between directories does not break links to it, as
long as the slug stays put and stays unique across the whole memory.
metadata.evidence: four values, chosen on purpose
evidence records where a claim came from, not how important it is:
| value | when it applies |
measured | verified against reality — a run, a profile, an incident that happened |
read | taken from code, a spec, or docs — true until something runs and says otherwise |
decided | a choice or an agreement; evidence does not apply, a date and an author do |
sourced | an outside claim checked against its primary source, quoted and dated |
If unsure, write read, and say in the note's body what a measurement
would need to confirm it. The expensive mistake runs the other way: marking
measured something that was in fact only read turns a guess into a fact for
whichever session reads it next, and nothing downstream can tell the
difference once it's written down.
metadata.as_of: when the claim was last true
evidence says what kind of claim a note holds. as_of says when — the
date of the run, the incident, the decision or the source check the note rests
on. The two answer different questions and a note wants both: read is
defined above as "true until something runs and says otherwise", and decided
as the case where a date and an author are what apply instead of evidence.
Date the note by its evidence, not by the clock in front of you. If the
measurement is a CI run, use that run's UTC date. A session ending at 22:00 in
UTC+3 that stamps its local date writes tomorrow, and the check that reads it
runs on UTC — the failure heals itself overnight, which is the signature
everyone misreads as flakiness. lint allows one day of slack for exactly
this and refuses anything further ahead.
Not git log. That records when the file was last touched — a reformat, a
renamed link, a fixed typo — and it moves in the direction that makes an old
claim look freshly checked.
The field is optional and stays optional. lint counts undated notes in
one line rather than naming them: the field arrives into corpora that already
exist, and a check that reddens a memory the day its owner updates the plugin
is a check they switch off — taking the four checks that were already earning
their place with it.
So no migration is owed. Adding the field as you next touch each note is the expected path. A one-time pass over an existing corpus is fine too, on one condition: every date has to come out of the note's own evidence — the run, the incident, the decision it records. A corpus stamped with the date of the pass itself is worse than an undated one, because it now claims eight notes were checked on a day when nothing was checked at all.
Past note_stale_days (180 by default, a key in .floppy/config) lint
names the note and stops. It does not fail the run, because old and wrong
are different things. A note recording a frozen decision is fine at three
years; a note about a dependency's behaviour is suspect at three months, and
no threshold distinguishes them. Three legitimate answers to an aged note:
- re-checked, still true → move
as_ofto today's date, change nothing else; - still true but the surroundings moved → update the note and the date;
- no longer true → rewrite it in place. Do not leave the old claim standing next to its replacement; a superseded note that survives is worse than no note.
Deleting is the fourth answer and it is under-used. A note whose subject no
longer exists should go, and the commit that removes it should say what
replaced it. This is the pruning half of the quota.lock ratchet below: a
ceiling with no habit of deletion only decides which session gets stopped by
it, and that is routinely not the one that filled it.
The index is a tree, three levels deep
<memory_dir>/MEMORY.md root router
<memory_dir>/<half>/INDEX.md half index
<memory_dir>/<half>/<group>/INDEX.md sub-indexThe root file (MEMORY.md) is a router, not a table of contents: it
holds only the notes worth reading in every task, plus one link per half. It
stays small on purpose. The session loader that pulls it into every fresh
session truncates it past a size limit and does not say so — a note past the
cut is not read, and every other check can pass while that happens. Keeping
MEMORY.md thin is the only defense; there is no error to catch the loss
after the fact.
A half opens its own INDEX.md as a second step, only when the task belongs
to that half. A half that outgrows its pointer budget splits into
sub-indexes rather than growing a longer index — that split is the third
level, and it is the last one. Depth stops at three levels not because of a
technical ceiling, but because three is the number of files a session opens
before it reaches a note (router → half → note, or router → half → sub-index
→ note), and a fourth hop has never been measured to still get read. A note
nested deeper than a sub-index can reach is unreachable in practice, whatever
the file tree says.
When a note gets written: at the moment, not at the end
A note is written when the fact appears, not collected at the end of the session. Five moments produce notes:
- an option or a hypothesis is rejected — the reason is exact now and reconstructed later;
- a measurement lands — the number, and what it was measured against;
- a number turns out to mean something other than it looked like;
- a tool or platform trap bites;
- a decision is frozen — a choice that constrains later work.
Not moments: finishing a task, making a commit, a green suite. Those are in the git log, and a list of what got done is the least valuable thing a memory can hold.
Two reasons, different in kind.
The reasoning is only intact now. A rejected option carries its argument for as long as the conversation holds it. What survives a summary is the decision, not the argument — so the next session proposes the same option again and pays for the same refusal twice.
The end of a session is the most expensive place to think. Every turn
resends the whole window, the window only grows, and closing is where it is
largest, so the same judgement costs more there than anywhere else. That is
arithmetic over the turn measurement in wrap, not a measurement of its own:
what nobody has counted is how many of a wrap's turns go to selecting facts
rather than to checking and committing.
What is safe to write mid-session, and what is not. A note file collides with nothing — its name is unique, so two sessions writing two notes write two files. Its pointer is a single anchored line, and an insert applies against whatever the index holds at that moment, so two sessions inserting different pointers both land. Neither needs the lock.
The whole-file rewrite is the opposite, and it stays in wrap under the lock:
rewriting an index, or either status file, is where a second writer silently
drops the first one's work.
The pointer is written with the note, never after it. A note no index
points at is a hard lint error, not a warning — nobody will find it. Write
both or neither.
The quota.lock ratchet
The memory directory can hold a quota.lock — plain key=value numbers
(chars_max for the whole corpus, note_chars_max for one note,
pointers_max for one index, pointer_line_max for one pointer line) that
bound how large the memory is allowed to get before something has to give.
A fifth is optional and per-half: half_chars_max.<half>=N bounds one half of
the tree on its own (half_chars_max.root covers the notes sitting directly in
the memory directory). A half with no key is not bounded, so setting none keeps
the old behaviour exactly. Reach for these when the corpus cap keeps tripping on
sessions that did not fill it: on a repository worked from two machines, the
half that grew and the session that hits the ceiling routinely belong to
different people, and one number for the whole corpus cannot say which is which.
When the corpus cap does trip, the linter now prints the per-half breakdown
beside it, whether or not any per-half key is set.
Every ceiling warns at 96% before it refuses at 100%. The run still passes;
lint prints a ! line naming the ceiling and, for the corpus one, the
breakdown by half. This is what you act on: at a warning the fix is still small
and still yours — drop what went stale, split the note you are writing, plan
the sub-index. Wait for the refusal and it lands on whichever session crosses
the line, which on a memory written from two machines is routinely not the one
that filled it, and that session may not raise the ceiling (the ratchet below)
nor prune a neighbour's notes (the wrap rite). pointer_line_max has no band:
it bounds one line, and a line near the limit is a line that fits.
All of them are facts about this corpus, which is why they live with the
memory. The one size limit that is not — the ceiling on the index the harness
loads into every session, which it truncates in silence — is a fact about the
harness and lives in .floppy/config as index_chars_max, with a default
shipped by the plugin. Asking a project to measure that one would be asking it
to measure somebody else's tool.
This file is not shipped by the plugin and not copied from one project to another. Its numbers come from measuring the consumer's own corpus — a ceiling copied from a different project is that project's ceiling, which is the same as having none at all: it bounds nothing about the memory actually in front of you.
Raising a number is allowed, but only in the same commit as the notes that needed the extra room, with the reason written into the commit message. A ratchet edited on its own, "to make the check pass," defeats the reason the file exists.
The heat log: what pruning decisions stand on
Opening a note is worth one line: bash <plugin>/scripts/run heat <slug>
appends a
dated entry to .floppy/heat.log — machine-local, kept out of git via
.git/info/exclude by the verb itself, outside every
quota. lint reads it back and names the notes no session has ever reported
opening.
The log exists because pruning otherwise runs on proxies. A date says when a note was last true; only usage says whether anyone reads it, and the one time that was counted by hand, fourteen of twenty notes had never been opened. Two honesty rules keep the signal worth having: log notes actually read, not the index skim past them; and treat the cold list as candidates for the merge-or-prune question, never as a deletion order — the log is self-reported and measured to under-count, and a note kept for a rare failure is cold right up to the day it pays for itself.
Scope: where the fact is true, not whether it is secret
A fact belongs to one of three places, and the test is where it is true:
- the repository's own memory (
memory_dir) — true about this project, travels withgit clone, read by anyone with the repo; - a workplace-wide private store — true across the machines of one workplace but not committed to this repository: another project's memory, or a fact private to this project that still needs to reach a second machine (someone's checkout path, a branch, an access note);
- machine-bound — true on one machine only (a local port, a disk name, a path that differs elsewhere) and never committed anywhere.
Confusing "private" with "machine-bound" is the mistake this scope exists to
prevent: a fact can be private to a workplace and still need to sync between
that workplace's machines, which a plain .gitignore'd local directory
cannot do.
A second question, independent of that one: is the fact about THIS project at
all? An evaluation of an outside tool, a shell trap, what this machine has
installed — those are true in every project, and filing them under one project
buries them for the others. They belong in the cross-project scope,
<memory_dir>/common/, which holds one link per audience: common/shared for
what the team may read and common/private for what stays off their
repository. Both are wired by store and workplace respectively, so a
machine that ran only one of them has only that half.
Two rules follow from where those links point, and both are checked:
- Nothing in the committed index links into
common/— nor into the private scope. Both are wired per machine, so a pointer into either is dead for anyone who has not wired it. A session reaches the scope becausestartnames it, not because a pointer led there;lintfails on such a link. - A note in
common/obeys the same frontmatter rules as any other. It is read by sessions on projects that know nothing about where it came from, sometadata.evidencematters more there, not less.
The quota does not reach it, and that is deliberate: quota.lock holds
measurements of ONE project's corpus, and common/ is written by several.
The memory may not live in this repository at all
Where memory_dir points is a layout decision, not something to assume. Some
repositories cannot hold agent notes — a checkout the team does not own, or a
policy separating notes from code — so memory_dir is a symlink into a
separate git repository and is gitignored here. The first scope above is then
hosted elsewhere; it is still this project's memory, not the workplace store.
Wiring that up is bash <plugin>/scripts/run store, once per machine and per
worktree;
init --memory-repo … --memory-key … does it at setup time. The state to know
about is the half-done one — the ignore line added, the symlink never created —
because it is comfortable: notes are written and read normally while nothing
will ever commit them. guard fails on that combination by name.
Nothing about writing a note changes: same paths, same index, same frontmatter.
Two things about closing a session do, and the verbs handle both — check
prints a memory store section, because this repository's own diff cannot see
the notes, and commit commits and pushes that store from the same file list.
What you must not do is conclude from a clean git status here that the
session left nothing behind: in this layout that is exactly what a clean status
looks like while every note is still uncommitted.
Three status genres — and why they stay three files
A repository following this convention keeps three documents about its current state, deliberately not merged into one:
- a project current-state file (
statuses_now) that a session start reads whole — rewritten in place, so it always answers "what's true right now" without a reader needing to reconstruct it from history. What is true about the project: frozen decisions, what is red, what waits on the human, metrics with their regression marks; - a personal current-state file (
statuses_personal) — the same shape, one scope narrower: the working state of one person on one machine, what they were mid-way through and where to resume. It defaults into the private scope undermachines/<name>/, so no other machine writes that path; - an append-only dated journal — grows one section at a time, holds the why and the numbers behind a change, and is opened only for the section a session was pointed at.
The first two split for a reason the memory learned earlier. A current-state file is the one artefact rewritten whole on every wrap, so two sessions overlapping in it conflict, while a note collides with nothing by construction and an index merges. Most of what forced those rewrites was personal — a thread of work that changes every session and is shared with nobody. Separated, it never needs merging; what stays in the shared file changes rarely enough that two sessions rarely meet in it. The split is structural, not a matter of tidiness: a private working note written back into the shared file re-creates the collision, on the document a stranger reads first.
The journal stays separate from both because it wants the opposite thing from a writer: a current-state file wants to be overwritten so a stale claim cannot survive in it, while the journal wants every entry preserved so a past number or a rejected hypothesis is never lost to a later edit. A single file trying to satisfy both ends up serving neither — the cost of starting a session would then depend on how much got written into yesterday's entry, and a big day would tax every session after it.
None of the three covers two people wrapping at the same moment on two machines. The wrap lock serialises one machine, and across machines git hands back a merge conflict rather than arbitrating silently. Splitting the personal half out shrinks how often that happens — it does not close it.
Language
Everything this plugin ships — scripts, their output, these skills — is
English, because it is meant to travel between projects unmodified. The
memory notes it manages are not: their language is the consumer's own
choice, set once as memory_language in .floppy/config. Neither of those
settings decides the language a session answers its human in — that follows
the human, in every session, and is not configured anywhere. State this
plainly so a future session does not "fix" it by wiring a language setting
to the reply language; there is nothing to wire.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

