Skip to content

Hold a stand-in's work for agents whose memory never came over - #223

Merged
danielehrhardt merged 4 commits into
mainfrom
fix/standin-unreplicated-agents
Oct 22, 2026
Merged

danielehrhardt merged 4 commits into
mainfrom
fix/standin-unreplicated-agents

Conversation

@danielehrhardt

@danielehrhardt danielehrhardt commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Problem

When the primary of a device group went away, the secondary standing in ran the 03:00 dream batch and the scheduled automations of every agent. That included agents whose folder had never been synced to that computer, so their MEMORY.md was still the template. Those dreams wrote "first dream" memories rebuilt from activity logs, and on the next first-time merge with the primary they would have been combined with the primary's real MEMORY.md. Automation prompts also named paths under the primary's home folder, which don't exist on the stand-in.

Changes

  • remote/standIn.ts standInHold(agentId, prompt?): while standing in, the agent waits for the primary when its memory never came over from the primary (no runner_memory row for it) or when its prompt names folders that aren't on this computer
  • The hold applies to scheduled and caught-up automations (recorded as skipped with the reason), scheduled condition checks, event automations (their events are marked skipped) and scheduled and early dreams. Manual runs and "Dream now" are not affected
  • remote/localPaths.ts: in automation prompts, a /Users/<other>/ or /home/<other>/ that doesn't exist here maps to this computer's home. On the primary this changes nothing
  • mergeMemory takes an owner: on the first sync (no base), the owner's version of a file both sides have wins, and files only one side has are kept. exchangeMemory makes the primary the owner when a secondary syncs with it, and the local side otherwise (the primary, or a controller with its runner)
  • A device that receives memory from a peer over the link now stores the agreed state in runner_memory as well, so that agent counts as synced there and later merges have a base
  • remote/devices.ts stopWaitingWork: when the primary is back, the secondary stops work that would continue by itself later. This covers runs waiting for a usage limit or budget reset in chats it took over from the primary, runs of automations, heartbeats, checks, dreams and follow-ups it started while standing in, and follow-ups in taken chats. Without this, a stand-in whose Claude account hit its weekly limit kept those runs paused and would have resumed them days later, repeating outreach the primary had already done. Pauses that wait for the human (a question, a pause they chose) stay. Stopped runs show the reason in the chat

Tests

  • pnpm -r typecheck
  • bun test test/local-paths.test.ts test/scheduler.test.ts test/remote-snapshot.test.ts test/dreaming.test.ts test/automations.test.ts test/remote-link.test.ts test/devices-e2e.test.ts: all pass. This adds tests for the stand-in hold, path mapping and the owner-wins first merge
  • bun test test/stand-in-waiting.test.ts test/pause.test.ts: limit-paused taken chats, stand-in automation runs and follow-ups stop; human pauses and older runs stay
  • The path test no longer assumes /Users/Shared exists, which made CI fail on Linux

A secondary standing in for its primary ran the nightly dreams and the
automations of every agent, including agents whose folder on that computer
was never synced (MEMORY.md still the template), and with prompts naming
folders under the primary's home that don't exist there.

- Scheduled and caught-up automations, condition checks, event automations
  and scheduled or early dreams wait for the primary when the agent's memory
  never came over from it or the prompt names folders missing here
- Paths under another computer's home in automation prompts map to this
  computer's home
- The first memory merge between two computers keeps the owner's version of a
  file both have (the primary's, or the controller's) instead of combining it
  with a stand-in's template or first dream
- A device that receives memory from its peer records the agreed state too, so
  it counts as synced and has a base for the next merge
…is back

Runs that wait for a usage limit or budget reset and follow-ups in those chats would continue by themselves days
later and repeat what the primary already did. What waits for the human stays. The path test no longer assumes
/Users/Shared exists (it doesn't on Linux CI).
@danielehrhardt
danielehrhardt merged commit 67b85be into main Oct 22, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant