Skip to main content
OpenHome Abilities can read and write files that persist across sessions. This is how you build voice journals, running logs, long-term preferences, alarms, grocery lists, and any Ability that needs to remember something beyond the current conversation.
If you specifically need an Ability to influence the Agent’s prompt (inject context the Agent can see), see Agent Memory & Context Injection — that page covers the .md-file → Agent-prompt pipeline. This page is about general-purpose file storage.

The API

Four methods, all from self.capability_worker:
# Write or append
await self.capability_worker.write_file(name, content, temp=False)

# Read (always check existence first)
if await self.capability_worker.check_if_file_exists(name, temp=False):
    content = await self.capability_worker.read_file(name, temp=False)

# Delete
await self.capability_worker.delete_file(name, temp=False)
ParameterMeaning
nameFilename, including extension
contentString payload (serialize JSON/CSV yourself)
temp=FalsePersistent across sessions
temp=TrueCleared when the session ends
Full method details: SDK Reference.

Storage patterns

Four patterns cover 90% of use cases. Pick the one that matches your data’s shape.

1. Journal / append log

For things that grow forever and don’t need to be read in structured form — voice journals, event logs, debug traces.
async def append_journal(self, entry: str):
    timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
    line = f"{timestamp}\t{entry}\n"
    await self.capability_worker.write_file("voice_journal.log", line)
Key property: write_file appends by default for text-like files — no read-modify-write needed. Read later:
if await self.capability_worker.check_if_file_exists("voice_journal.log"):
    full_log = await self.capability_worker.read_file("voice_journal.log")
Never use this pattern for JSON. Appending to a JSON file produces invalid JSON. Use the Key-value JSON pattern instead.

2. Latest state / replaceable file

For a single current value that gets overwritten — user preferences, the latest mood reading, the current “focus mode” flag.
async def save_state(self, filename: str, content: str):
    if await self.capability_worker.check_if_file_exists(filename):
        await self.capability_worker.delete_file(filename)
    await self.capability_worker.write_file(filename, content)
Always delete + write, never append. Then read gives you the current state:
current = await self.capability_worker.read_file("focus_mode.md")
This is also the pattern required for .md context files that inject into the Agent prompt.

3. Key-value JSON

For structured data with multiple fields — alarms, reminders, a grocery list, user settings.
import json

async def read_alarms(self):
    if not await self.capability_worker.check_if_file_exists("alarms.json"):
        return []
    raw = await self.capability_worker.read_file("alarms.json")
    try:
        return json.loads(raw)
    except json.JSONDecodeError:
        return []

async def write_alarms(self, alarms: list):
    # Always delete + write — append corrupts JSON
    if await self.capability_worker.check_if_file_exists("alarms.json"):
        await self.capability_worker.delete_file("alarms.json")
    await self.capability_worker.write_file(
        "alarms.json",
        json.dumps(alarms, indent=2),
    )

# Usage
alarms = await self.read_alarms()
alarms.append({"id": "alarm_1", "target_iso": "2026-04-18T07:00:00"})
await self.write_alarms(alarms)
Always delete + write for JSON. write_file() appends by default, which produces invalid JSON and breaks the next read.

4. Rolling window

For recent-N data — last 100 sensor readings, last 24 hours of transcripts, recent commands.
MAX_ENTRIES = 100

async def push_reading(self, reading: dict):
    entries = []
    if await self.capability_worker.check_if_file_exists("readings.json"):
        try:
            entries = json.loads(await self.capability_worker.read_file("readings.json"))
        except json.JSONDecodeError:
            entries = []

    entries.append(reading)
    entries = entries[-MAX_ENTRIES:]  # Trim old entries

    if await self.capability_worker.check_if_file_exists("readings.json"):
        await self.capability_worker.delete_file("readings.json")
    await self.capability_worker.write_file(
        "readings.json",
        json.dumps(entries),
    )
Always trim at write time, not at read time — the file size stays bounded, reads stay fast.

Persistent vs temp

temp=False (default)temp=True
Survives session endDeleted when session ends
User preferences, alarms, journalsConversation scratch space, session flags
Read on next sessionGone after the current call
Default to temp=False. Only use temp=True when you need to remember something within a session but want it cleared after.

Best practices

Namespace your filenames

Avoid generic names. They collide across Abilities.
  • data.json, state.md, list.txt
  • smarthub_prefs.json, alarm_active.md, grocery_list.txt
Use the Ability name (or a short prefix) at the start of every filename.

Keep each file focused

One logical object per file. Split a shared concern across files when the single file gets past ~1 MB or starts holding multiple unrelated concepts.

Always check existence before read

read_file() on a missing file throws. Always:
if await self.capability_worker.check_if_file_exists(name):
    content = await self.capability_worker.read_file(name)
else:
    content = ""  # or default

Serialize JSON yourself

write_file() takes a string. Use json.dumps() when writing, json.loads() when reading. Wrap json.loads in a try/except json.JSONDecodeError to recover from corrupt files.

Bound your data

Rolling windows, journal rotation, log truncation — pick a cap and enforce it at write time. Unbounded files fill disk and slow down every future read.

Handle missing files as “first run”

On the first call, every file is missing. Design for that:
prefs = {}
if await self.capability_worker.check_if_file_exists("prefs.json"):
    prefs = json.loads(await self.capability_worker.read_file("prefs.json"))
prefs.setdefault("theme", "dark")  # defaults for new users

When to use main.py + background.py together

If you need to write from main.py and react to that write from a background daemon — like alarms or reminders — see the Coordination Pattern in Background Abilities. Both files read and write to the same shared file storage.

See also