The family.md file IS the database. Every piece of care information—schedules, medications, member roles, recent updates—lives in a structured markdown file. When you ask CareSupport to update the schedule, it surgically edits family.md, creates a timestamped backup, validates the result, and writes only if validation passes.
This isn’t a cache or a view—it’s the canonical state. If family.md says Solan is driving Monday at 8am, that’s the truth. If the agent thinks otherwise, it’s wrong.
Human-readable state beats query logs every time. Here’s why:
Transparency
Auditability
Portability
You can open family.md in any text editor and see exactly what CareSupport knows:
## Schedule### This Week (Mon Mar 3 - Sun Mar 9)- Mon 7:45am: Solan drives Degitu to work- Mon 4:30pm: Liban picks up Degitu from work- Tue 2:00pm: Roman drives Degitu to doctor appointment
No SQL queries. No API endpoints. Just plain text you can read, edit, and version control.
last_updated: Set by family_editor.py on every successful write
status: active | paused | archived
Current Section (Always Loaded)
# Current<!-- Agent reads this section every message --><!-- Keep under 2000 tokens -->## Care Recipient- Name: Degitu Tefera- Age: ~66- Primary conditions: Post-toe surgery recovery- Mobility: Can walk, cannot drive or take public transit- Emergency contact: Liban (+16517037981)## Care Team| Role | Name | Phone | Access Level | Active ||---|---|---|---|---|| Primary caregiver | Liban | +16517037981 | full | ✓ || Family caregiver | Roman Tefera | +16516214824 | schedule+meds | ✓ |## Urgent Notes- (none)## Recent Updates- 2026-02-28: Solan confirmed Monday 8am ride- 2026-02-27: Added Yada Kano to care team
Design principle: If the agent needs it often, it lives in Current. If it’s referenced rarely (full medication history, insurance details), move it to Reference.
Reference Section (Loaded on Demand)
# Reference<!-- Loaded when conversation requires historical context -->## Full Medication History## Past Appointments## Provider Contacts## Insurance & Coverage## Emergency Protocols## Decision History
Not yet implemented: The agent currently loads all sections. Future optimization will load Reference sections only when conversation context suggests they’re needed.
Some sections grow large enough to justify their own files:
schedule.md
medications.md
## Schedule### This Week (Mon Mar 3 - Sun Mar 9)- Mon 7:45am: Solan drives Degitu to work (Downtown Minneapolis)- Mon 4:30pm: Liban picks up Degitu from work- Tue 2:00pm: Roman drives Degitu to doctor appointment- Wed: Day off (Degitu working from home)- Thu 7:45am: Amanti drives Degitu to work- Thu 4:30pm: Liban picks up- Fri 7:45am: Solan drives- Fri 4:30pm: Roman picks up### Availability (Who Can Drive When)- Liban: Evenings (after 5pm), weekends- Roman: Flexible (retired)- Solan: Mornings before 9am, some afternoons- Amanti: Thursdays only
When to use: Weekly schedules for care recipients who work or attend regular programs. Prevents the Current section from growing beyond 2000 tokens.
When to use: Families with 3+ active medications. Keeps medication details accessible without cluttering family.md.
How split files are loaded:
# From sms_handler.py:185-201def load_family_context(family_dir: str) -> str: parts = [] family_file = Path(family_dir) / "family.md" if family_file.exists(): parts.append(family_file.read_text()) for extra in ("schedule.md", "medications.md"): extra_path = Path(family_dir) / extra if extra_path.exists(): parts.append(extra_path.read_text()) return "\n\n".join(parts)
The agent sees all three files as one continuous context.
Hard rule: CareSupport NEVER rewrites family.md from scratch. It parses the file into sections, modifies only the target section, and reassembles with everything else unchanged.
Why Edit-Not-Write?
Supported Operations
Implementation
If the agent rewrites the file:
It might hallucinate details (“I think there was a medication…”)
It might drop sections it wasn’t instructed to change
It might reformat tables or change phrasing unintentionally
With surgical edits:
Only the target section changes
If the edit fails validation, nothing is written (backup stays, file unchanged)
The Last Updated timestamp is auto-updated only on successful writes
Before writing any changes, the editor validates the result:
# From family_editor.py:163-198def validate_family_file(content: str) -> tuple[bool, list[str]]: issues = [] # Check 1: File must not be empty if not content.strip(): issues.append("File is empty") # Check 2: Must have a top-level # header lines = content.strip().split("\n") if not lines[0].startswith("# "): issues.append("Missing top-level # header") # Check 3: Must have at least one ## section header, sections = parse_family_sections(content) if len(sections) == 0: issues.append("No ## sections found") # Check 4: Sections must have content beyond just the header for section in sections: content_without_header = section.content.replace(section.header, "", 1).strip() if not content_without_header: issues.append(f"Section '{section.key}' is empty") return len(issues) == 0, issues
If validation fails: The system logs the issues, skips the write, and returns an error. The backup remains intact.
Problem: Two messages arrive for the same family within 1 second. Without serialization, both handlers read the file, make edits, and race to write—last write wins, first edit is lost.Solution: Per-family message locks (file-based).
How Locks Work
# From message_lock.py:170-207@contextmanagerdef family_lock(family_id: str, phone: str = ""): lock_file = families_dir / family_id / ".lock" # Acquire lock (blocks until available or timeout) lock_info = acquire_lock(family_id, phone=phone, timeout=30) try: yield lock_info # Process message under lock finally: release_lock(family_id) # Always release, even if error
Usage in sms_handler.py:
# Line 958with family_lock(family_id, phone=from_phone): return await _process_message(...) # All reads/writes serialized
Stale lock recovery: If a process crashes and leaves a lock behind, any lock older than 120 seconds is considered stale and force-acquired.
Each care team member has their own profile in families/{id}/members/:
# Liban — Member Profile## Identity- Name: Liban- Phone: +16517037981- Role: family_caregiver- Relationship to care recipient: nephew- Access level: full## Communication Preferences- Preferred channel: iMessage- Language: English## Care Responsibilities- Primary coordinator- Evening and weekend rides- Medication reminders (backup)## Personal Context- Works downtown (flexible schedule)- Usually available after 5pm## Interaction History- 2026-02-25: Set up CareSupport for Kano family- 2026-02-27: Requested Yada be added to care team
When profiles are updated:
Agent uses member_updates (same format as family_file_updates)
Example: “Remember I prefer texts over calls” → appends to Communication Preferences
Profile is loaded into the agent’s context on every message from that member
Want to see how an edit flows? Read family_editor.py:203-312 (apply_updates function). Follow the inline comments—it walks through backup → parse → edit → validate → write.