Repository navigation
emrg: the agent registry is agents.yml, migrated in place from projects.yml #2521
Description
What is observed
~/.emrg/projects.yml is the installation's register of the directories a session runs in. Four modules build its path themselves, each spelling the directory out again:
emrg/server/daemon.py—self._projects_log = runtime_dir / "projects.yml", read by the project-touch path, the picker'slist_projectsandremove_projectemrg/server/scheduler.py—config_dir() / "projects.yml"in_resolve_project_path,_load_project_configand theemrg-entry self-healemrg/tools/submit_rant_tool.py—self._rants_log().parent / "projects.yml"in the candidate-name readerscripts/check-memory-rotation.py—DEFAULT_PROJECTS = "~/.emrg/projects.yml"
emrg/tools/file_policy.py names the same file in its protected-files list, and tests/conftest.py names it as one of the host's real config files, so a test that writes it fails loudly.
Since #2520 the record of a directory a session runs in is an Agent (emrg/agent.py) — a base_dir and the three paths derived from it. The registry file still carries the name of the concept it no longer represents.
What is expected
The registry is ~/.emrg/agents.yml. Every reader and writer asks one producer for that path instead of building it, and the host's existing registry arrives at the new name with nothing lost — no entry dropped, no field dropped, last_active intact.
What the fix must satisfy
- One producer, in
emrg/config.pybesideconfig_dir()andlogs_dir()— the same "one derivation of that directory" treatmentlogs_dir()already has. Every site listed above asks it;config_dir() / "projects.yml"disappears from the package. The producer takes its base directory as an optional argument so a caller holding its own data directory (the rant tool, whose tests point it at a temp dir) still goes through the one function. - One-shot and atomic. When
agents.ymlis absent andprojects.ymlis present, the old name is moved into the new one by a single rename inside the same directory, so the move either happened or it did not: there is no window in which both names hold different halves of the register. - A registry already at the new name is never touched, and one that never existed is not created — a reader keeps its current "no registry ⇒ no entries" behaviour rather than materialising an empty file.
- A failed move degrades, it does not raise. A read-only home or a permission boundary leaves the legacy file exactly where it is, and the caller proceeds as it already does for a missing registry; nothing new escapes into the daemon's startup path.
agents.ymlinherits the protection. Its read and write semantics under every sandbox tier are the onesprojects.ymlhad; the hermeticity guard intests/conftest.pywatches the new name, so a test writing the host's real registry still fails loudly.- Two concurrent callers are safe: the second rename finds the legacy name gone and is treated as the first one's success.
- Guards to leave behind: a migration test (an entry and its
last_activesurvive; the new file exists and the old one is gone), a "never moves what is already renamed" test, and a "a move that cannot happen leaves the legacy name readable" test.
Out of scope
The 9 task records, the 92 rant records, the project → agent vocabulary across the protocol and both clients, and the automatic registration of an arbitrary directory each stay for their own issue and their own change.
Activity
argszero commentedon Oct 23, 2026
Handled by #2522