[eric] apps: the scaffold ships a durable store and the skill forbids memory-only state, since the runtime dies on a timer

This commit is contained in:
ciregenz
2026-08-07 20:01:06 -07:00
parent dec16d78db
commit dfed363db7
3 changed files with 68 additions and 0 deletions
+16
View File
@@ -274,6 +274,22 @@ and flips `BACKEND_PORT` in both `.env` and `.env.example`. Then run
- Install your own venv or `pip install` manually.
- Edit `backend/run.sh` or the SubApp framework.
**Persist anything the user comes back to. Your process is disposable.**
OpenSwarm freezes this app's process when its card closes and fully kills
it after ~15 minutes idle, on quit, and on crash. A module-level list or
dict is therefore data loss on a timer. The scaffold ships a durable
store; use it (or your own files under `backend/data/`):
```python
from backend.apps.store.store import load_store, save_store
data = load_store() # {} on first run, never raises
data["items"] = [*data.get("items", []), new_item]
save_store(data) # atomic write; a kill mid-write keeps the old data
```
Holding state only in memory is a bug, not a style choice.
Adding a new endpoint is just adding a new SubApp:
```python
@@ -0,0 +1,52 @@
"""Disk-backed app state. Use this instead of module-level variables for anything worth keeping.
The app's process is DISPOSABLE: OpenSwarm freezes it when its card closes, kills it after ~15
minutes idle, on quit, and on crash. A module-level list or dict therefore silently loses the
user's data on a schedule you don't control. This store survives all of that: one JSON file under
backend/data/, written atomically so a kill mid-write can never corrupt it.
from backend.apps.store.store import load_store, save_store
items = load_store().get("items", [])
items.append(new_item)
save_store({**load_store(), "items": items})
"""
import json
import os
import tempfile
from typing import Any, Dict
from typeguard import typechecked
P_BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DATA_DIR = os.path.join(P_BACKEND_DIR, "data")
STORE_PATH = os.path.join(DATA_DIR, "store.json")
@typechecked
def load_store() -> Dict[str, Any]:
"""The whole store as a dict; empty on first run or an unreadable file, never an exception."""
try:
with open(STORE_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
return data if isinstance(data, dict) else {}
except (FileNotFoundError, json.JSONDecodeError, OSError):
return {}
@typechecked
def save_store(data: Dict[str, Any]) -> None:
"""Replace the store atomically: temp file then rename, so a kill mid-write leaves the old data."""
os.makedirs(DATA_DIR, exist_ok=True)
fd, tmp = tempfile.mkstemp(dir=DATA_DIR, suffix=".tmp")
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=1)
os.replace(tmp, STORE_PATH)
except OSError:
try:
os.unlink(tmp)
except OSError:
pass
raise