Repository navigation
feat(agents): keep agent state in files that a program outside the app can read - #974
Conversation
8bb79e2 to
9d87233
Compare
9d87233 to
05c88ea
Compare
05c88ea to
445272e
Compare
445272e to
4ede2f9
Compare
632c3bf to
b9fb90d
Compare
b9fb90d to
47c4892
Compare
47c4892 to
6014443
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 thanks for turning the first round around so fast. the record split, the Claude sessions split and the agents2 rename all keep their source now when a write fails, provider changes roll back correctly, and a direct save() retry reaches disk. four persistence issues are still open on 99500019a, details inline:
- blocking: a failed first copy out of
localStoragecan still lose teams and settings (state.ts) - blocking: a failed settings Save leaves the form's Save button disabled, so the user can't retry (
service.tswrite) - blocking: agent attention tools (watches,
interest_set) report success when the write failed (service.tsrunner adapter) - blocking: valid Codex keys on very long hosts still go over the file name limit (
agent_state.rs)
nonblocking, still open from the first round: main.tsx shows a blank window if loading agent state never finishes.
these come from reading the source and testing with injected write failures. nobody has run them in a real window or with two programs.
This comment has been minimized.
This comment has been minimized.
4df4d09 to
c3f5fe1
Compare
c3f5fe1 to
fac7739
Compare
fac7739 to
25cf56c
Compare
25cf56c to
fb880dd
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source follow-up via Wes’s account: two P2 findings, detailed inline. Timer progress can overwrite a pending settings edit, and synchronous storage refusal can admit the same overdue timer repeatedly. Source assessment: 8/10 until these persistence paths are repaired. Both existed at the prior reviewed head and were missed in my earlier review; neither is a new rebase regression.
The prior migration, per-agent checkpoint and provider-default repairs remain intact. The rebase preserves the feature patches while retaining the model-loading and save guards. Rocket, Mantis and Groot completed independent systems, correctness/security and product/accessibility lanes; I reconciled their evidence and verified both findings against the source. This is a repair/integration follow-up, not a new exhaustive audit of unchanged feature code.
Head 100d6534a5c32350f343bca674e0c0f1b7a96d63; base dcd3960c9f6613ded457232903d250caa6e7e1fd. Public description (no attachments), seven commit messages and changed publication surface inspected. Shared Select/Button and host Dialog ownership remains intact. Source-only: no tests, builds or app execution, and current CI was not assessed. Live migration, concurrent-host operation, native keyboard/error-retry focus, themes/sizes, performance and human acceptance remain unverified. COMMENT only, not approval or merge authorization.
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source follow-up via Wes’s account: one P2 finding, detailed inline. Source assessment: 8/10 because timer progress can still overwrite a pending timer rearm’s saved usage, granting an unintended fresh occurrence budget.
The synchronous storage-refusal fix is confirmed in source, and pending non-timer settings now survive progress writes. The remaining issue is the per-timer state within that merge. Rocket, Mantis and Groot completed independent lanes; I checked the finding against the supported attention-write path and reconciled their results.
Reviewed head d914c830ebfaa007b042a52449c89e0871014d55, base dcd3960c9f6613ded457232903d250caa6e7e1fd; repair delta from 100d6534a5c32350f343bca674e0c0f1b7a96d63 is three files. Public description (no image attachments), changed publication surface and eight commit messages inspected. Source-only: no tests, builds or app execution; CI was not assessed. Live persistence/concurrency, native focus and error/retry interactions, themes/sizes and human acceptance remain unverified. This is a non-blocking COMMENT review, not approval.
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source follow-up via Wes’s account: one P2 finding, detailed inline. Source assessment: 8/10 because a timer can consume an occurrence while its own rearm is saving, then lose that consumed count and exceed its configured budget.
The prior different-timer overwrite is repaired: only changed slugs are merged, and unrelated pending timer state is retained. Rocket, Mantis and Groot completed independent systems, correctness/security and product lanes; I reconciled their evidence and checked the runner lifecycle. Later invalidation cannot undo an occurrence that already completed before the save settled.
Head e71ab26c907a93eae0e7da1e2d1ceb843a0baef5; base dcd3960c9f6613ded457232903d250caa6e7e1fd. Scope: three-file repair delta from d914c830ebfaa007b042a52449c89e0871014d55, prior finding and affected callers, not a new exhaustive audit of unchanged feature code. Public description (no attachments), commit messages and repair publication surface inspected. Source-only: no tests, builds or app execution; current CI was not assessed. Live persistence/concurrency, native focus/error-retry, performance and human acceptance remain unverified. This is a non-blocking COMMENT review, not approval.
Plan: restructure agent storage instead of another patch roundThe last several review rounds on this PR found the same kind of bug in a new place each time. The latest finding (a timer that uses an occurrence while its own rearm saves) is one more. I fixed each case where it was found. Those fixes are correct, but they do not remove the cause, so I expect more. I will change the structure in this PR. Why the bugs keep coming1. File storage looks like 2. No single owner changes an agent's record. Owner settings, the attention tools, timer progress, retype and the migrations each copy the whole record from a snapshot and write all of it back. The snapshot can be the shown record, the one still saving, or storage. Each pair of writers that overlap is a new bug: timer vs settings save, timer vs another timer's rearm, and now timer vs its own rearm. The The old What I will change
Scope and limits
I will reply to the open thread when the change is pushed. |
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source review via Wes's account, integrating independent Rocket (systems/lifecycle), Mantis (correctness/security/concurrency), and Groot (product/user-flow) lanes.
Follow-up review of head 8aa080b086c1ecbf5ff1365f08c32c09c7dadea9 against base dcd3960; repair delta from previously reviewed e71ab26c907a93eae0e7da1e2d1ceb843a0baef5. The prior pending-rearm occurrence-budget defect is repaired in source. One new P2 is recorded inline. Assessment: 8/10 pending that lifecycle repair.
Inspected the three-file repair, relevant runner/storage/caller context, public description (no attachments), and commit messages. Source-only: no tests, builds, app execution, live persistence/concurrency reproduction, native focus/retry testing, current CI assessment, or human acceptance. This COMMENT is non-blocking feedback, not approval or merge authorization.
1c35a19 to
f3ef785
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source follow-up via Wes’s account: one P2 finding, detailed inline. The AgentStore conversion leaves the documented Codex live acceptance caller on the old storage interface, so that workflow fails when saving its first session. Source assessment: 8/10 until that caller is adapted. The production plugin supplies the new store correctly.
The prior timer-wake finding is addressed in source: pending settings changes wake their runner on settlement, and timer progress no longer blocks a free Interest lane. Rocket, Mantis and Groot completed independent systems/lifecycle, correctness/security/concurrency and product/accessibility lanes; I reconciled their evidence and checked the supported callers. This is a follow-up of the storage refactor, timer repair and rebase integration, not a new exhaustive audit of unchanged feature code.
Head c1479a63f9575b9964fd2c9cf0698015347fed56; base a5b3391ecd2f7cfc141ba6ecd0886226daaaee9a. Public description (no attached images), twelve commit messages and changed publication surface inspected. Shared Select/Button and host Dialog ownership is preserved. Source-only: no PR code, tests, builds or app execution; CI was not assessed. Live migration/concurrent-host persistence, native keyboard/error-retry focus, themes/sizes, performance and human acceptance remain unverified. Non-blocking COMMENT only, not approval or merge authorization.
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source follow-up via Wes’s account: no additional changes requested. The prior Codex live-acceptance finding is addressed: the fixture now provides AgentStore get/keys and promise-returning set/add, including deletion and create-if-absent behavior. The thread-name assertion matches the existing title plus conversation-tag format. Source assessment: 9/10 for this repair scope.
This uses the small-follow-up solo exception: comparison with previously team-reviewed c1479a6 shows only src/bundled/codex/live.mjs changed (17 additions, 4 deletions), with the same base and unchanged feature source. I checked the repair against AgentStore, CodexRuntime save/load and sessionName/sessionTag, then reread the delta for integration mistakes. No new independent team lanes were run.
Head 9eb07892a1425aee31e5c848ec76a64d562460b6; base a5b3391ecd2f7cfc141ba6ecd0886226daaaee9a. Public description (no attachments), commit messages and changed publication surface inspected. Source-only: no acceptance command, tests, builds or app execution; current CI was not assessed. Live saved-session recovery, native focus/error-retry behavior and human acceptance remain unverified. This is a bounded repair follow-up, not a new exhaustive audit of unchanged feature code. Non-blocking COMMENT only, not approval or merge authorization.
9eb0789 to
d3786b6
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source review via Wes’s account, integrating independent Rocket (systems/lifecycle), Mantis (correctness/security/concurrency), and Groot (product/accessibility) reviews.
No new actionable findings in this revision-focused review. Source-review assessment: 9/10.
Reviewed head: d3786b6da4758704b695d43d1e829a0efe650b3a
Reviewed base: dc305c9
Previous reviewed head: 9eb0789
The 13-commit range-diff retains 11 identical patches; the remaining two differ only in service import context for the Me→Solo rename. We checked the rebased service/runner and Claude/Codex routing integration, startup and fallback handling, ordered persistence and session-storage consumers. The prior Codex acceptance-fixture repair remains present. Provider-default save failures retain drafts and expose retryable errors; the shared button preserves focus during saving. The public description and commit messages were inspected; the description has no attached images.
Scope and limits: this is a follow-up integration review, not a fresh exhaustive review of unchanged feature patches. Source only: no tests, typecheck, builds, app launches, or PR code execution. Live migration/restart and cross-process behavior, rendered accessibility/focus, and performance remain unverified. A single hosted-check snapshot had jobs still running; Windows native validation was skipped. This COMMENT is not an approval or merge authorization.
d3786b6 to
55c7419
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source review via Wes’s account, integrating independent Rocket (systems/lifecycle), Mantis (correctness/security/concurrency), and Groot (product/accessibility) reviews.
No new actionable findings in this revision-focused review. Source-review assessment: 9/10.
Reviewed head: 55c7419e40bfe4c8c293398d8a3c99bdc89ca443
Reviewed base: 13e878e
Previously reviewed head: d3786b6da4758704b695d43d1e829a0efe650b3a
The 13-commit range-diff retains eight identical patches; five adapt to the newer base, principally the inline provider model/effort controls. We checked save completion and failure/retry handling, ordered rapid selections, the retained Codex dialog draft, Claude session lifecycle integration, startup and native webview boundaries. Prior timer/rearm and Codex live-fixture repairs remain present. The public description has no attached images; description and commit messages were inspected with no new disclosure finding.
Scope and limits: bounded follow-up and integration review, not a fresh audit of unchanged feature patches or all intervening base changes. Source only: no PR code, tests, builds or apps executed. Live migration/restart, concurrent-host persistence, rendered accessibility/focus and performance remain unverified. One hosted-check snapshot showed 8 successful checks, 26 running and Windows native validation skipped. This COMMENT is not an approval or merge authorization.
The app kept each agent's settings, the owner's teams, unfinished setups, the Claude and Codex sessions and their default settings in the WebView's localStorage, which no program outside the app can read. Keep them in agents/state/ beside identities.json instead, one file per key, through two native commands. The app reads the folder once before it starts, keeps a copy in memory, and saves each write to its key's file in order. On first start it moves the agent keys it had in localStorage into the folder. Each agent's record and its Claude sessions now have their own key, so two programs that run different agents never write over each other. Agent types get the storage from the agents service (agents.storage). Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
A failed file write during the one-time moves (the shared record value, the shared Claude sessions value, the agents2 rename) still removed the source, so the next start lost that agent's settings or sessions. Each move now removes its source only after every new copy is saved. A failed write also no longer passes as saved: agent storage puts back the key's saved value, saves of settings, providers and teams reject, and the same value can be saved again. A failed timer-progress save still keeps the run's memory, so a timer does not run again at once. Agent state keys are limited by their file name (255 bytes), not by a 200-byte key, so Codex sessions on long community hosts are saved. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
A settings, team or attention change was shown before its save finished and stayed shown when the save failed. The settings form then saw no change and turned off Save, and an agent's watch or interest_set edit reported success. Each now goes back to what storage still has when its save fails, and an attention write passes the error to the tool. Only a timer's progress stays, so the timer does not run again at once. The first move out of localStorage deleted a key whose copy failed from the storage the app reads, so the app started with no teams and default settings, and a later save replaced them. Now every key is copied before any leaves localStorage, and if a copy fails, or loading takes longer than 15 seconds, the app uses localStorage until the next start. The agent keys still in localStorage then replace the copies in the folder, since the app wrote them last. A key whose encoded file name is over 255 bytes, such as a Codex session key on a long community host, could not be saved. Its file is now named by the key's SHA-256 and holds the key with the value. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
…saved Each start let agent keys in localStorage replace the agent storage files. After the first move, localStorage has no agent state, so a start that could not use agent storage began with none, and what it saved then replaced the full files at the next start: for example, a session list with one conversation replaced one with three. Agent storage now keeps a marker once every key was moved. After that, the files win, and keys from such a start only fill in keys the files do not have. A settings save was shown before it was saved. The Codex settings form resets its draft when the shown settings change, so when the save failed, the owner's edits were replaced with the saved settings and Save was turned off. Settings are now shown only once they are saved. Later changes still build on a write that is being saved. Timer progress is still shown at once. The Claude Code and Codex provider defaults dialogs closed before the defaults were saved, and a failed save was lost at the next start. They now wait for the save, close only when it succeeds, and otherwise keep the draft and show the error, so the owner can try again. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
- A full store no longer stops startup. If the move of the old shared agent records fails, the app logs it and reads the old value, which stays for the next start to move. - Unfinished hosted-agent changes now wait for their save. When it fails, Delete and setup report the error and the app shows the saved list again, so a restart shows the same state. - The default-model and thinking choices stay fixed while defaults save, so the dialog never closes over a choice it did not save. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
Catch-up progress was one shared value. With agent state in shared files, two programs that run different agents each rewrote the whole value from their own copy, so one could undo the other's saved progress and an agent could get already handled events again after a restart. Each agent's progress now has its own key, and a save writes only the agents whose progress changed. The old shared value is split into the per-agent keys at start, before any progress is read, and removed once they are saved. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
Two programs can start from the old shared value before either moves it. If one moves it and runs its agent on, the other's late move wrote its older copy over the newer file, so a restart could replay handled events or bring back an older record. A move now adds each agent's key only where no file holds it. The native store does that in one step and returns the file's value, which the copy then takes. Catch-up progress is also saved only for the agents this program moved since its last save, so an older copy of another program's agent is never written. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
A timer that came due while a settings or attention change was still saving wrote the shown record with its progress, after the change. The change was lost on disk and in the app, though its Save succeeded. Timer progress now goes into the record being saved, and the app still shows the saved settings until that save finishes. A save refused at once by storage (localStorage quota) now throws at once again, so the runner does not start a timer whose progress it could not save. Before, the refusal became a logged rejection, and the timer ran again after each run. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
…ress Timer progress replaced the whole timers map of a pending change with the shown one. So when an agent or owner rearmed timer A while timer B came due, B's progress put back A's old armed time, and A then got a new budget of runs. The runner now reports only the timers whose state changed. The service copies each into the pending change only when that change left the timer's state as shown; a timer it rearmed, restarted or removed keeps that. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
Timers ran from the shown record while a change to it was still saving, and their progress was then merged into that change. Each kind of change needed its own merge rule, and a timer rearmed during the save could run from its old schedule and lose that run from its count. Now a timer does not start while a save of its agent's record is pending; the next tick runs it from the record that save left. So every write to the record starts from the latest one, and the progress merge is gone. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
…wner Review rounds on this PR kept finding the same two kinds of bug in new places. This fixes their cause instead of the next case. Agent storage is now an explicit async store (AgentStore). Its set and add return a promise that settles once the value is saved and rejects when it was not. The fake Storage over the agent state files, and the persisted() and afterSaved() helpers callers had to remember, are gone. The compiler now shows every caller that writes agent state. Doing so found one more: an imported team's save was not awaited, so a failed save was not reported as a team error. Each agent's record now changes only through one ordered queue in the agents service. Owner settings, the agent's attention tools, provider changes, removal and timer progress all send a change. Each applies to the newest saved record after the change before it settles. Timer progress is a change to run state only: when a timer was rearmed or restarted since the pass read it, the new deadline stays and only the occurrences the pass used are added. The shown/keep/saving rules are gone. Timers still wait while another change to their record is queued, so a pending rearm keeps its budget. A timer whose progress cannot be saved keeps it in memory, as a failed save did before. It now runs only when it is due, not never: an async store cannot refuse at once. Cross-process writes are still a documented limit. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
… saved Timers wait while a change other than timer progress is waiting or saving, since it may rearm, restart or remove one. When the last such change settled, nothing woke the runner, so a timer due meanwhile waited for the next five-second tick and could expire first. The change owner now wakes the agent's runner once its last change is saved or fails. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
The live acceptance check (`bin/node src/bundled/codex/live.mjs`) still
passed a `{ getItem, setItem }` fixture to CodexRuntime. Its first session
save threw, since agent storage now has `get`, `set`, `add` and `keys`, and
JavaScript callers are not type-checked. Its thread name check also predates
the conversation tag in thread names (#846).
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
55c7419 to
c63382b
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord automated source review via Wes’s account, integrating independent Rocket (native storage/startup/lifecycle), Mantis (persistence/correctness/security/concurrency), and Groot (product/accessibility) reviews.
One P2 finding: the Codex defaults dialog can be dismissed while its asynchronous save is pending, losing the draft and failure feedback if that write fails. Details and the smallest repair are inline. Source-review assessment: 8/10 until this pending-save contract is preserved.
Reviewed head: c63382b
Reviewed base: ac42eb6
Previously reviewed head: 55c7419e40bfe4c8c293398d8a3c99bdc89ca443
The 13-commit range-diff retains 10 identical patches; the other three adapt test context and the existing Codex thread-name assertion to the newer base. Feature production patches are unchanged. The dismissal finding is newly established evidence in code already present at the previous reviewed head; it is distinct from the previously fixed loss of edits made during a save. We checked the full dismissal owner and the explicit pending-operation requirement in DESIGN.md before reopening this path. No other actionable integration findings. The public description and commit messages were inspected; no attached images or new disclosure finding.
Scope and limits: bounded follow-up and integration review, not a fresh audit of unchanged feature patches or all intervening base changes. Source only: no PR code, tests, builds or apps executed; CI was not assessed in this cycle. Live migration/restart, concurrent-host persistence, performance and rendered accessibility/focus remain unverified. This COMMENT is non-blocking feedback, not an approval or merge authorization.
| return; | ||
| } | ||
| onClose(); | ||
| setSavingDefaults(true); |
There was a problem hiding this comment.
🤖 [P2] Keep every dismissal path closed until the defaults save settles
After Save starts, savingDefaults disables the thinking select and Save button, but it does not reach the dialog’s dismissal guard. ProviderDialog.tsx:79 uses state.blocking, which CodexProvider sets only from setup activity; Cancel also calls onClose() directly at lines 584–588. Therefore Cancel, X, Escape or outside-click can unmount the dialog while storage.set is pending (including behind another queued file write). If the write then fails, this rejection handler targets the unmounted body: the draft and its error/retry UI are lost. A successful write also completes after the user selected Cancel.
This conflicts with src/shared/design-system/DESIGN.md:173–175 and its dialog pending-operation rule: preserve entered values/recovery and use preventClose during pending work. Include pending defaults saves in the existing provider blocking state, combine that with setup activity so one cannot clear the other, and disable/guard Cancel until settlement. Cover dismissal attempts followed by a rejected save, then successful retry. This is separate from the earlier fix that disabled edits during saving; that fix remains present.
🤖
Summary
localStorage). Only the app can read that. So a program outside the app could run the agent's code, but not with the agent's settings, its team instructions, or its saved conversations.agentsfolder where it already keeps the list of agents. The standalone program in feat(agent-host): run a saved agent without the desktop app #967 reads and writes the same files. An agent then behaves the same in both places.Why
Buzz agents are AI assistants, such as Claude or Codex, that a person sets up in the Buzz desktop app. They answer when someone mentions them in a channel, and they can also watch for events and run on a timer. Today an agent runs only while its owner's desktop app is open, because the code that runs agents lives inside the app.
The goal of this stack is a small command-line program that runs an agent with the app closed. It must use the same code and the same saved state as the app, so the agent behaves the same wherever it runs.
Stack
Review in order. Each PR keeps the app working.
buzz-agent-host, the program that runs an agent with the app closed.What moves
localStoragevalue for all agentslocalStoragelocalStoragelocalStoragevalue for all agentslocalStoragevalue per agent and communityThe files are in
agents/state/in the app's data folder, besideidentities.json.Details
src-tauri/src/agent_state.rs): two commands, load all and set one key. Each key is one file. A write goes to a temporary file first and is then renamed, so a crash leaves the last complete copy. Characters that are not safe in a file name are written as%XX. If that name is longer than 255 bytes (the longest file name macOS, Linux and Windows all allow), as a Codex session key on a long community host can be, the file is named~plus the key's SHA-256 instead, and it holds the key with the value so a load gets the key back. The standalone host (feat(agent-host): run a saved agent without the desktop app #967) names files the same way.src/features/agents/state.ts): web page storage reads must be synchronous, so the app reads all the files once before it starts (main.tsx) and keeps a copy in memory. Each write changes the copy at once and is then saved to its file, in order. If the folder cannot be read, or reading and moving take longer than 15 seconds, the app useslocalStoragefor that start, as before, and the window still opens.localStorageis copied into the folder. Agent keys are inlocalStorageonly before the first move, or after a start that could not use the folder, so they are the newer copy and replace what the folder has. They are removed fromlocalStorageonly once every copy is saved. If any copy fails, the app useslocalStoragefor that start, so nothing it shows is missing, and the next start tries again.localStoragedirectly. They now useagents.storage, which the app provides.Limits
interest_setchange) reports the failure. A failed save of timer progress only logs: the app keeps the progress in memory so the timer does not run again at once, but after a restart the timer can run once more.localStoragebecause a copy failed, and the owner deletes an agent's settings or a team during that start, a copy saved to the folder before the failure can bring it back on the next start.