Справочник конфигурации
Файл содержит по одной строке key=value на каждую настройку. Диспетчер (<plugin>/scripts/run) читает его и экспортирует каждое значение как переменную FLOPPY_* для скриптов.
Все ключи необязательны. В таблице показано значение, которое ключ принимает, если файл его не содержит.
| ключ | по умолчанию | чем управляет |
|---|---|---|
memory_dir | .agent-memory | каталог памяти этого репозитория |
memory_private_dir | private | имя приватной области в памяти: факты об этом проекте, которые репозиторий с кодом нести не должен, — например чужой чекаут или заметка о доступе. Их держит репозиторий рабочего места, поэтому другие машины их читают. Факты об одной машине идут вместо этого в machines/<name>/ того же репозитория. Настройкой является только имя, а не правило: закоммиченная память не должна ссылаться внутрь этой области, и проверка пользуется этим ключом. То же правило распространяется на common/, чьё имя не настраивается: оно вписано в сами пути хранилищ, а имя, задаваемое в одном из двух мест, — это имя, которое разъедется |
public_repo | (не задан) | git-URL репозитория, который держит публичную память этого проекта, когда репозиторий с кодом её держать не может. Задайте также project_key. Затем выполните команду store по одному разу на каждой машине. Не на каждую рабочую копию: память адресуется по репозиторию, и worktree отдельного шага не требует |
private_repo | (не задан) | git-URL репозитория, который держит приватную память этого проекта: факты, которые команда получить не должна. Проводку делает команда workplace |
machine_key | (не задан) | имя этой машины в репозиториях памяти, выбранное вами. hostname не используется: на одной из машин автора он равен WIN-GVR0V5UPOD7. Нужен только для заметки, верной на одной машине |
workplace_key | (не задан) | имя этого рабочего места, когда один приватный репозиторий обслуживает несколько. Нужен только для заметки, верной на одном рабочем месте |
project_key | (не задан) | имя этого проекта в каждом репозитории памяти, который он использует, и имя его каталога в agents_memory_dir. Области — это public/projects/<key> (в public_repo) и private/projects/<key> (в private_repo) |
memory_project_key | (значение project_key) | использовать другой ключ только в public_repo. Нужен, когда один и тот же проект называется по-разному в двух репозиториях |
workplace_project_key | (значение project_key) | то же самое, для private_repo |
agents_memory_dir | $HOME/agents_memory | держит по каталогу на каждый проект и клоны в .clones/. На каждый URL репозитория приходится один клон. Имя клона выводится из URL. floppy выводит его сам, вы его не задаёте. Поэтому два разных репозитория не могут пользоваться одним каталогом клона. Клон от прежней раскладки — прямо под родительским каталогом или в нём самом — используется как есть, но только если его origin совпадает с настроенным URL. См. пример выше |
memory_repo_dir | (выводится) | заменяет выведенный путь чекаута public_repo на этой машине. Задавайте, только если этот чекаут не может лежать под родительским каталогом |
workplace_memory_dir | (выводится) | та же замена, для private_repo. Он же — отказ от общего клона: несколько проектов с одним private_repo по умолчанию делят одно рабочее дерево, а этот ключ даёт одному проекту собственный клон. См. «Когда двум проектам не стоит делить одно рабочее дерево» ниже |
memory_language | en | язык заметок памяти. Ни один скрипт этот ключ не использует. Сессия читает его из этого файла. Языком ответов человеку он не управляет |
index_chars_max | 24500 | максимальное число символов в индексе памяти. Значение взято из загрузчика сессии агентского приложения. Этот загрузчик отрезает текст выше предела и не сообщает об отрезанном. Это факт о приложении, а не о вашем проекте. Ограничения для корпуса лежат в quota.lock |
note_stale_days | 180 | сколько metadata.as_of заметки может простоять, прежде чем lint её назовёт. Он предупреждает и никогда не падает: «старое» и «неверное» — разные вещи, и различить их может только человек, знающий предметную область. Заметки без as_of считаются, но не перечисляются, чтобы поле могло приехать в уже существующий корпус. Уменьшите значение, если ваша память в основном про быстро меняющуюся зависимость. База знаний в этом репозитории считает заметку старой через 90 дней, но другим механизмом: scripts/knowledge-rot-check.py --days, который этот файл не читает вовсе |
statuses_now | docs/statuses/NOW.md | файл состояния. start читает его целиком. wrap поддерживает его корректность |
statuses_now_chars_max | 12000 | максимальное число символов в файле состояния. wrap-guard отказывает в коммите выше этого предела |
statuses_personal | <memory_dir>/<memory_private_dir>/machines/<machine>/NOW.md | второй файл состояния: нить работы одного человека на одной машине — что не доделано, откуда продолжать. start читает его, когда он есть, wrap его пишет. Он лежит внутри памяти, поэтому commit отправляет его в приватное хранилище и никогда в этот репозиторий. Часть с машиной берётся из machine_key, а если он не задан — из hostname. Оставьте ключ незаданным, если только выведенный путь не оказался неверным: записав его здесь, вы кладёте путь одной машины в файл, который читают все машины. Ограничения на число символов у него нет, в отличие от statuses_now, потому что читает его только та сессия, которая его написала |
statuses_regress_marks | (пусто) | слова, отмечающие регресс в колонке направления таблицы трендов, на вашем языке. Разделяйте их запятой. Тогда wrap-guard откажется удалять только те строки, что несут одно из этих слов. Пока ключ пуст, ни одна строка тренда не может быть удалена вообще — безопасно, но переписываемый файл при этом растёт как append-only, потому что разовая строка «сделано» уже никогда не уйдёт |
watched_dirs | docs | каталоги, дополнительно к memory_dir, которые wrap вправе коммитить. Разделяйте имена запятой |
watched_files | AGENTS.md | отдельные файлы, которые wrap вправе коммитить. Шаблоны разрешены. Разделяйте имена запятой |
commit_push | auto | действие после каждого коммита. auto выполняет git pull --rebase, затем пушит. never не делает ни того, ни другого. Используйте never, если у репозитория нет удалённого, потому что auto там падает. Чтобы пропустить пуш ровно один раз, используйте --no-push |
У private_repo и workplace_project_key значений по умолчанию нет. Так сделано намеренно. С умолчанием репозиторий мог бы записать в приватную память другого человека.
Где лежат чекауты
agents_memory_dir содержит две вещи: по каталогу на каждый проект и скрытый .clones/ с одним клоном на каждый репозиторий памяти.
Каталоги проектов открываете вы. Клоны создаёт floppy.
Пример. Конфигурация одного проекта — четыре строки:
project_key=acme
public_repo=git@example.com:team/notes-store.git
private_repo=git@example.com:workplace/agents-memory.git
agents_memory_dir=~/agents_memory
Значение, начинающееся с ~/ или $HOME/, означает ваш домашний каталог — ровно эти два написания. Конфиг — не шелл: ~user, другие переменные и что угодно в середине значения остаются буквальными. Так с 0.24.1; до этого каждый путь приходилось писать абсолютным.
Результат на диске:
~/agents_memory/
acme/ <- проект, названный по project_key
shared -> ../.clones/notes-store/public/projects/acme
private -> ../.clones/agents-memory/private/projects/acme
.clones/
notes-store/ <- клон public_repo
agents-memory/ <- клон private_repo
shared и private — симлинки. floppy создаёт их на каждой машине, и ни один репозиторий их не содержит. Они относительные, поэтому agents_memory_dir можно перемещать целиком.
<memory_dir> в вашем репозитории разрешается в ~/agents_memory/acme/shared, а <memory_dir>/private — в ~/agents_memory/acme/private. Именно разрешается, а не указывает: с 0.27.0 в репозитории нет файла, который бы указывал. См. «Памяти нет в вашей рабочей копии» ниже. Эти два адреса остаются прежними, если URL репозитория изменится.
Второй проект использует те же два репозитория тем же способом. Он получает свой каталог ~/agents_memory/<other key>/ и свои области public/projects/<other key> и private/projects/<other key> внутри тех же двух клонов. Клон по умолчанию один на репозиторий, а не один на проект.
Когда двум проектам не стоит делить одно рабочее дерево
Проекты, разделяющие private_repo, делят его клон — а значит и одно рабочее git-дерево. Их области никогда не пересекаются, но состояние дерева одно: живая сессия одного проекта держит там свой недописанный файл состояния грязным ровно в тот момент, когда wrap другого проекта синхронизируется. С 0.24.0 commit прячет чужую грязь в stash и возвращает её на место, а check считает файлы этого проекта отдельно от чужих — общий клон по умолчанию работает. Отказывайтесь от него, когда вместо stash нужна стена: направьте workplace_memory_dir на путь, принадлежащий только этому проекту.
workplace_memory_dir=~/agents_memory/.clones/agents-memory--acme
На уже подключённой машине витрина и кросс-проектная ссылка всё ещё ведут в общий клон, а глаголы отказываются перенаправлять проводку, которая ведёт куда-то в живое место, — сначала уберите эти две ссылки, потом подключите заново:
git -C ~/agents_memory/.clones/agents-memory push # сначала вытолкнуть остатки этого проекта
rm ~/agents_memory/acme/private
rm <memory_dir>/common/private
bash <plugin>/scripts/run workplace
Цена — ещё один клон на диске. Удалённый репозиторий остаётся одним, области не переезжают, а другие проекты и другая машина не видят никаких изменений.
Если в public_repo и private_repo лежит один и тот же URL, клон будет один, и обе области окажутся в нём, рядом друг с другом.
Где лежат области
Области — два каталога рядом:
public/projects/<key> в public_repo
private/projects/<key> в private_repo
public/common в public_repo — ни про один проект в отдельности
private/common в private_repo — ни про один проект в отдельности
Две области common — это сосед projects/<key>, для фактов, которые не про один конкретный проект: оценённый сторонний инструмент, ловушка шелла, что установлено на одной машине. store и workplace подключают их рядом с собственной областью проекта, как <memory_dir>/common/shared и <memory_dir>/common/private; машина, где запускали лишь один из двух глаголов, получит лишь одну половину. Ничто в закоммиченном индексе не должно ссылаться внутрь них — по той же причине, по которой нельзя ссылаться в приватную область: ссылка ставится на каждой машине отдельно, поэтому для того, кто область не подключил, она мёртвая. Вместо этого область называет start, а lint падает на такой ссылке.
Под любым из них заметка, верная не везде, уходит уровнем глубже: workplaces/<workplace_key>/ или machines/<machine_key>/. Заметка, верная везде, лежит прямо в области, и это самый частый случай.
Приватная область приватна для проекта, и её читает каждая машина рабочего места, — см. docs/memory-model.ru.md. Факты про ОДНУ машину идут в machines/<name>/ репозитория рабочего места.
Эти имена — нынешние, но не первые. Чего стоили переименования до них, написано в уроках.
Если ваш репозиторий всё ещё использует старые имена, глагол останавливается и печатает команды git mv. Сам он заметки не перемещает. Причины две: эти заметки могут быть единственными копиями, и перемещение, сделанное на одной машине, пока другая ещё пишет по старому пути, форкает память без единого сообщения. Сначала обновите каждую машину, потом переместите области один раз.
Два репозитория памяти на одной машине
Проект может использовать store и workplace вместе. store переносит всю память в другой репозиторий. workplace присоединяет общую область в <memory_dir>/private. Это могут быть два разных репозитория.
Держат эти два репозитория порознь два изменения:
- Каталог чекаута выводится из URL. Поэтому два URL не могут дать один каталог.
- Если чекаут уже там, глагол сравнивает его
originс настроенным URL. Если они различаются, глагол останавливается и показывает оба.
Второе изменение находит и другую проблему: посторонний репозиторий по этому пути. До 0.4.2 не было ни одной из двух проверок, и два глагола молча делили один каталог — измерение лежит в уроках.
Перемещать ничего не нужно. Если чекаут уже лежит в родительском каталоге, floppy продолжает им пользоваться и глагол вам об этом сообщает.
Память в другом репозитории
Некоторые репозитории не могут держать заметки агента вместе с кодом. Примеры — чекаут заказчика, который вам не принадлежит, и политика, разделяющая эти две вещи.
В таком случае память уходит в репозиторий-хранилище. Ваш репозиторий с кодом хранит один файл: .floppy/config. Это короткий список настроек. Ревью его занимает минуту.
Чтобы настроить это при init, используйте флаги:
--memory-repo git@example.com:workplace/agents-memory.git --memory-key acme
Чтобы настроить позже, положите public_repo и project_key в .floppy/config. Затем выполните:
bash <plugin>/scripts/run store # клонировать или подтянуть, разложить кеш и проверить запись
store выполняется по одному разу на каждой машине. Не на каждую рабочую копию. Он идемпотентен. Чтобы увидеть результат без изменений, выполните store --check.
Второго шага нет. До 0.27.0 им был link; каталог памяти агентского приложения теперь создаётся за вас — для того каталога, в котором вы работаете, — при первом же запуске любого глагола оттуда. Когда это происходит, об этом сообщается.
Памяти нет в вашей рабочей копии
С 0.27.0 память проекта в хранилище адресуется по репозиторию, а не по рабочей копии. memory_dir остаётся именем, которое вы набираете: .agent-memory/… — это то, что вы пишете в списке файлов и как её называет любой отчёт. Но ни файла, ни символической ссылки с таким именем в репозитории с кодом больше не создаётся. Заметки лежат в agents_memory_dir/<project_key>/shared — это вид в хранилище.
Причина — git worktree. Символическая ссылка в рабочей копии игнорируется git’ом по построению, поэтому git никогда не переносит её в worktree. Worktree правильно настроенного репозитория из-за этого начинался без памяти, и каждый глагол в нём сообщал, что памяти у проекта нет. Ключ, по которому память находится, — project_key из .floppy/config, а его git переносит, поэтому любой worktree репозитория вычисляет один и тот же.
Символическая ссылка от прежней версии продолжает работать. Всё идёт по ней. О ней сообщается, и её никогда не удаляют: это ваше решение.
У репозитория, которому memory_dir нацелили на хранилище руками, без public_repo и без project_key, не из чего сложить путь к кешу. Его worktree откатываются к вопросу к главной рабочей копии того же клона — туда, где эту символическую ссылку и сделали. Этому откату нужен git 2.31 или новее, ради rev-parse --path-format=absolute. На более старом git он не срабатывает, и worktree такого репозитория ведёт себя как до 0.27.0: сообщает, что памяти нет. Починка в обоих случаях одна — задать public_repo и project_key и один раз выполнить store.
Если на этом месте лежит настоящий каталог с заметками, store останавливается и ничего не переносит. Эти заметки могут быть единственными копиями. Чтобы перенести их в хранилище, выполните:
bash <plugin>/scripts/run store --migrate
Он печатает каждый файл перед тем, как его перенести. Если имя есть с обеих сторон, он останавливается и не переносит вообще ничего: какую копию вы хотели оставить — не тот вопрос, на который вправе ответить скрипт. Он ничего не удаляет, включая опустошённый каталог. Больше этот флаг не вызывает ничто.
Последний шаг store — самый важный. Он записывает файл через вид и подтверждает, что файл оказался в хранилище. Все остальные шаги могут выглядеть правильно, пока запись уходит туда, откуда её никто не публикует.
В конфигурации нет ключа «внешняя» или «внутренняя». Раскладка следует из public_repo и project_key: оба заданы — значит, память этого проекта лежит в хранилище, и адрес вычисляется из них. Решает конфигурация, а не файловая система. Что бы ни лежало на месте memory_dir в рабочей копии, адресом это не является и им не становится.
Так сделано намеренно, и это измерено. Раньше разрешение откатывалось к рабочей копии всякий раз, когда по адресу в хранилище ничего не стояло, — выглядит это осторожностью, но ею не является: навыки этого же плагина велят агенту писать .agent-memory/<файл>, и одна заметка агента, который им последовал, создавала там каталог, забирала адрес себе и приводила репозиторий ровно в то состояние, которое эта версия убирает: lint отказывается работать, check печатает MEMORY LINT COULD NOT RUN, а весь корпус в хранилище становится невидимым — и при этом нигде ничего не красное.
Настоящий каталог на этом месте теперь — развилка корпуса, а не память. guard отказывает, пока он стоит, и называет store --migrate; см. выше.
С хранилищем процедура wrap закрывает два репозитория:
guardспрашивает у хранилища его изменения и сообщает о них теми путями, которыми пользуетесь вы.checkпоказывает заметки, которые уходят наружу. Дифф репозитория с кодом их показать не может.commitкоммитит и пушит оба репозитория по одному списку файлов. Если хранилище отказывает в пуше,commitпадает. Он не сообщает «сессия закрыта» поверх неопубликованных заметок.- Если
memory_dirнаходится вне git,statusсообщает об этом состоянии. Память тогда работает на чтение и запись, но её ничто не публикует.
Прежде чем выбирать эту раскладку, знайте один её недостаток. Память никто не ревьюит вместе с кодом. При раскладке внутри репозитория такое ревью бесплатно.
Осторожно: недоделанное состояние комфортно и потому опасно. Конфигурация называет хранилище, а машина его так и не склонировала. Все пути разрешаются; lint и check молчат, потому что молчать там не о чем; а сессия читает пустую память и считает, что памяти у проекта нет. guard на этом падает и называет глагол, который это чинит, а status докладывает о хранилище отдельной секцией и тем самым показывает машину, на которой настройку пропустили. store --check отвечает на тот же вопрос одной строкой — и отвечает про эту машину, а не про эту рабочую копию.
quota.lock
Этот файл лежит в каталоге памяти. Он содержит четыре ограничения:
chars_max— общее число символов.note_chars_max— число символов в одной заметке.pointers_max— число указателей в одном индексе.pointer_line_max— число символов в одной строке указателя. По умолчанию 170.
Пятое необязательно и пишется по одному на половину: half_chars_max.<half> ограничивает одну половину дерева отдельно, а half_chars_max.root покрывает заметки, лежащие прямо в каталоге памяти. Половина без собственного ключа не ограничена, поэтому корпус, не задавший ни одного, ведёт себя ровно так же, как до появления этих ключей.
Все они — факты об этом корпусе. Поэтому они лежат вместе с памятью, а не в .floppy/config. Одно ограничение размера является фактом об агентском приложении: index_chars_max, в таблице выше.
Каждый из этих потолков предупреждает, прежде чем отказать. На 96% потолка lint печатает строку с !, называя его, — прогон при этом проходит, — а потолок корпуса приносит с собой разбивку по половинам, так что строка говорит, какая именно половина выросла. Исключение — pointer_line_max: он ограничивает одну строку, а строка в 165 символов из 170 ни к чему не приближается, она просто помещается.
Полоса существует из-за того, на кого приходится жёсткая остановка. Потолок, который только отказывает, останавливает того, кто его перешёл, а в памяти, которую пишут с нескольких машин, это регулярно не тот, кто её наполнил. Храповик ниже гласит, что число можно повысить только тем же коммитом, что и заметки, которым понадобилось место, — значит сессия, встретившая голый отказ, вынуждена либо повышать потолок, который наполняла не она, либо подрезать половину, которую писала не она, а подрезать чужие заметки — единственное, что обряд wrap запрещает прямо. Предупреждение же достигает той сессии, которая наполняет, пока работа по подрезке ещё её собственная.
96% выводятся из каждого потолка, а не настраиваются. Два числа, которые приходится держать в фиксированном отношении, — это два шанса задать их неверно, а хотеть предупреждение на какой-то другой доле причин ни у кого нет.
Плагин файла quota.lock не поставляет, и файл никогда не копируется из одного проекта в другой. Его числа обязаны происходить из измерения корпуса этого проекта. Ограничение из другого проекта описывает тот проект и здесь ничем не управляет.
Поэтому init создаёт его ровно в одном случае: если в репозитории уже были заметки, когда пришёл floppy. Тогда есть корпус, который можно измерить, и числа принадлежат этому проекту — chars_max по измеренному итогу плюс десятая часть, pointers_max по самому длинному найденному индексу, а любая заметка, уже превышающая note_chars_max, перечисляется в grandfathered, а не роняет первый же прогон. На пустой памяти init не создаёт ничего: измерять нечего, а потолок, выдуманный для пустого каталога, ничего не ограничивает.
Засев при усыновлении — это и есть назначение храповика. Он говорит не о том, какого размера этой памяти следует быть, а о том, какого размера она была в день прихода floppy, чтобы каждое последующее увеличение было осознанным действием, видимым в диффе. Проект, приехавший уже сверх некоего импортированного умолчания, покраснел бы на первом же прогоне, а линтер, красный в первый день, — это линтер, который выключат.
init также печатает, что lint думает об унаследованном корпусе, сгруппировав по виду и поставив счётчик перед каждым: девяносто четыре одинаковых строки — это сырьё для отчёта, а не отчёт. Ни одной заметки он не переписывает. Чистый вердикт приходит вместе с напечатанными под ним предупреждениями линтера, потому что «ничего не сломано» и «делать нечего» — разные отчёты, и одно предупреждение создаётся самим усыновлением: pointers_max засевается по самому длинному найденному индексу, отчего этот индекс с первого же прогона стоит на 100% своего собственного потолка.
Пока файла нет, команда lint выдаёт предупреждение. Она не падает.