Repository navigation
Refresh .floppy/run: no. The shim is untouched. scripts/run did change,
and that copy arrives with plugin update.
A worktree of a correctly wired repository had no memory
git worktree add produced a checkout with no memory in it at all. The memory
was reached through a symbolic link at <repo>/<memory_dir>, gitignored, and
git carries neither ignored files nor symbolic links into a worktree — so
lint exited 2 with "this repository does not use this memory layout", check
printed MEMORY LINT COULD NOT RUN, and commit stopped at "memory lint is
red" before it ever reached the file-list gate. The whole corpus was on the
machine, complete, and unreachable from the tree the session was working in.
The address is computed now, not stored. A repository whose
.floppy/config carries public_repo and project_key resolves its memory to
<agents_memory_dir>/<project_key>/shared, and nothing of that name is created
in a working copy at all. project_key is tracked, so every worktree of a
repository computes the same address. memory_dir stays the name you type —
.agent-memory/… is what you write in a file list and what every report calls
it.
What follows from that:
storeruns once per machine, not once per working copy. A worktree
needs no step of its own.linkis no longer a step. The agent application's own pointer is
<claude-config>/projects/<encoded cwd>/memory, one per working directory,
so it cannot be addressed by repository. The config parser creates it on any
verb, announces it once, and skips it for--check. The verb still ships,
for a pointer you want to rewire by hand.- The configuration decides, not the file system. Resolution no longer
falls back to the working copy when nothing stands at the store's address.
That fallback read as caution and was not: this plugin's own skills tell an
agent to write.agent-memory/<file>, so one note from an agent that
followed them recreated the directory, took the address back, and put the
repository into exactly the state above. - A real directory at
memory_diris a fork of the corpus and is now
treated as one.guardrefuses while it stands, andstore --migratemoves
it — printing every file before it moves it, refusing the whole run on a name
that exists on both sides or on a private scope it has no right to publish,
and deleting nothing, not even the directory it empties. - The fence between the two repositories holds. The public scope commits
from the public store and the private one from the workplace store;git add -Ain a code worktree stages no memory path at all.
A symbolic link an earlier store left in your working copy costs you nothing:
it points at exactly the address that is computed now, so the configuration
gives it the answer it already had. It is not followed, though — nothing in a
working copy is an address any more, so a link that was repointed somewhere else
by hand is ignored from this release on, and the notes behind it stop being read.
Nothing deletes such a link; what to do with it is yours to decide.
A repository whose memory_dir was pointed at a store by hand, with no
public_repo and no project_key, has nothing to compose an address from: its
worktrees
fall back to asking the main working copy of the same clone, which needs git
2.31 or newer for rev-parse --path-format=absolute. On an older git that
fallback does not engage and such a worktree behaves as it did before this
release, reporting no memory. The repair is the same in both cases — set
public_repo and project_key, then run store once.
Four defects that work uncovered on the way
ln -snow has its exit status read. Everyok linkedline in this
plugin was printed unconditionally after anlnnobody checked. In a git
worktree whose target directory did not exist, the truth and the lie came out
two lines apart on two streams:ln: failed to create symbolic link …: No such file or directory, thenok linked: .agent-memory/private -> …. It was the
only place in the whole verb matrix where this tool said yes about something
that had not happened.store,workplaceandlinkall report the failure
and stop now;ln's own message is left on stderr, because it names the errno.workplacesays which verb comes first, and refuses before it clones.
Run with no memory for the private scope to sit in, it used to create a real
directory there — after whichstorerefused to touch it, which is how the
order the tool suggested bricked a tree. It now stops withx no memory at … — there is nothing for the private scope to sit in, says to run thestore
verb first, and ends withNothing was cloned, linked or written.checkandstatusjudge the wiring with one piece of code. They had
drifted:statuscarried an arm for a private scope that is not a symbolic
link, and the gate never had it. Measured one second apart in a worktree of
this plugin's own repository —status: repository exists, but .agent-memory/private is not a symlinkagainstcheck: clean and pushed. The
gate was the optimistic one of the pair, which is the wrong way round for a
gate.linkstopped calling a correct worktree the wrong repository. Its
refusal wasx no <path>/.agent-memory — wrong repository, rc 2, for a
worktree wired exactly as intended. It names what is actually missing now, and
the verb that makes it.
init wrote where CDPATH pointed, not where you pointed it
An exported CDPATH with a relative entry makes cd print the directory it
found on stdout, and that print landed inside the command substitution that
resolves --repo. With CDPATH=.:<dir> and a relative --repo target, the
path came out two lines long: init created a directory whose name ends in a
newline beside the target, printed ok at every step, exited 0 — and the
repository it had been pointed at kept nothing but its .git.
The unset CDPATH was already in the file, seven lines below where it had to
be: above the cd that finds the plugin, rather than above the one that
resolves the argument. --repo . and an absolute --repo are both immune,
because cd consults CDPATH for neither, which is why every call the suite
and the init skill make missed this.
The dispatcher stops inventing a plugin root
scripts/run roots itself in its own path and then sourced the config parser
out of that root without asking whether it was there. . on a missing file
prints one raw No such file or directory, and with set -u but no -e the
dispatcher carried on: called through a symbolic link that points at that file
alone rather than at a checkout, env printed a FLOPPY_ROOT naming no plugin
and exited 0 — an invented root wearing a success. It now names the resolved
root, names what is missing in it, and says the two things that can mean: an
incomplete install, or a call that came through a link to the dispatcher alone,
which carries none of the plugin with it.
Three documentation claims that measured false
.floppy/heat.logis excluded, not gitignored. Both language versions of
the install guide said.gitignorecovers it. It does not, deliberately:
memory-heat.shwrites its ignore line into.git/info/exclude, and its
header records why. The first release of the verb appended to.gitignore
itself, so the firstheaton every machine left that file modified — and
wrap's guard refuses to commit it, so a wrap that named the file stopped on
a change the human had not made. The log is machine-local, and so is the rule
that hides it..floppy/does hold code — just never the plugin's. The sectioninit
appends to a consumer'sAGENTS.md, and the install guide with it, said the
directory holdsconfigand nothing else.workstatusruns
.floppy/workstatus-project.shwhen a project defines one, which is an
executable in that directory. Both now say what is true: the plugin puts no
code there, that hook is the project's own, andheat.logsits beside
config.- A
watched_filesentry naming.floppy/runwas typed by hand. No
releasedinitever wrote one: every version from 0.11.0 to 0.26.0 writes
watched_filescommented out and without the runner in it. The 0.26.0
migration notes said nothing about the entry at all, which left a reader who
had followed the migration to the letter with a line they could not date. The
0.26.0section of this file now carries it, with the measurement — the
release page forv0.26.0was published before that and is not rewritten. And
initnames the entry
when it sees one — read with the config parser's own key pattern and matched
between commas, so an indented or spaced copy is found and.floppy/runner.md
is not — splitting its advice by whether.floppy/runis still in the tree,
because dropping the entry while the file is tracked takesguard .floppy/runfrom rc 0 to rc 1. It edits nothing either way:.floppy/config
is yours.