Skip to content

Repository files navigation

Memreda

Memreda is a private handover inbox for one family sharing care:

voice note or forwarded email → editable handover → private family timeline → daily brief

It keeps family updates from email, voice notes, and written notes. Each handover links to its source. It also shows who will do the next action.

Memreda is not medical advice, an emergency service, a medical record, medication management, surveillance, a general family dashboard, or a chatbot.

 voice note ─┐
 typed note ─┼─▶ stored source     ─▶  editable handover     ─▶  private timeline  ─▶  daily brief
forwarded    │   (audio/text kept        (edit before it            (source-linked         (Resend,
email        ┘    verbatim, never          ever counts as             continuity:            every send
                   overwritten)             saved)                    what changed,          logged)
                                                                       carry-forward,
                                                                       tags, replies)

Each step above is an API boundary in server.js. The app stores each source without changes. AI drafts the three editable handover fields. It does not diagnose or add details. A person must confirm a handover before it appears in the timeline.

Start a private family circle The Today view for a circle Writing a source note Reviewing the editable handover The private timeline

Start a circle → share an update → review the editable handover → the private timeline. Live screenshots from a fresh instance, not mockups.

Customer-zero scope

This Phase 1 app supports one private family circle per signed-in owner in the UI:

  • Passwordless owner sign-in using expiring email magic links.
  • Every first-time user enters a name. This includes owners and invited contributors. The app uses the saved name in member lists and private tags.
  • A private circle and email/link invitation for contributors.
  • Browser-recorded or uploaded audio voice notes.
  • Immutable stored source metadata and transcript, separately from AI output and later edits.
  • OpenAI transcription and schema-validated editable fields: What happened, Worth remembering, and Tomorrow.
  • Private membership-checked timeline, edit, soft-delete, and JSON export.
  • Basic-auth inbound email endpoint for Postmark-style forwarding. It accepts email from known circle members only. It removes duplicate MessageID values. It creates an owner-reviewable draft before an email handover enters the timeline.
  • Daily owner digest through Resend. Each delivery attempt is recorded. The owner can turn the digest on or off for the circle.

The incoming-email and digest transport patterns were informed by /opt/verasettle-repos/verasettle-email-agent; no payout, settlement, finance, or product logic was copied.

Phase 2 continuity layer

Phase 2 adds private continuity features to saved handovers:

  • What changed? shows family-reported New, Continuing, Resolved, and Needs follow-up items since a member last opened the view. Each item opens its source handover. It does not diagnose or make clinical conclusions.
  • Related handovers form topic threads. Each thread shows its first mention, latest update, source count, and evidence.
  • The Tomorrow field can create a carry-forward commitment. Members can claim and complete it. They can also add it to the shared action list.
  • The action list has manual and commitment-derived items. Members can claim and complete items. It has no recurring tasks, calendar, notifications, projects, or priorities.
  • Private-history questions search only the circle's handovers and source text. The answer includes linked evidence. This feature is not a general chat assistant.
  • Voice notes, written notes, and forwarded emails have circle-only replies and seen acknowledgements. There are no direct messages, public reactions, or nested threads.
  • Members can add or remove private tags on sources, replies, and tasks. Tags do not change the original source. They can appear in the next digest without push notifications.
  • When a saved source names a circle member, the app adds a private source tag for that member. It matches the first word of a saved name. A member can remove the tag. The app does not match members who have no saved name.
  • The Today view greets the signed-in member by name. Settings lets a member edit their name and view the circle roster.

Continuity extraction is deterministic and source-linked. It remains available when external AI is unavailable. It stores generated records separately from sources and editable handovers.

Local setup

Requirements: Node 20+ and npm.

cp .env.example .env
npm install
npm run migrate
npm run dev

Open http://localhost:3000. Without email credentials, the UI shows one-time development links. Without an OpenAI key, uploads use a labeled local-development transcript. Use this mode for flow testing only.

Environment variables

Variable Purpose
PORT HTTP port, default 3000.
APP_URL Public canonical HTTPS URL used in sign-in and invitation links. It must be a new dedicated hostname, not the existing Tailnet /memory-lane path.
DATABASE_PATH SQLite file path, default ./data/memory-lane.db.
UPLOAD_DIR Private filesystem directory for voice-note files, default ./uploads. Do not expose it as static content.
SESSION_SECRET Reserved for deployment secret management; use a long random value.
OPENAI_API_KEY Enables real OpenAI transcription and handover generation.
OPENAI_TRANSCRIPTION_MODEL Defaults to gpt-4o-mini-transcribe.
OPENAI_HANDOVER_MODEL Defaults to gpt-4.1-mini.
TRANSCRIPTION_API_URL, TRANSCRIPTION_API_KEY, TRANSCRIPTION_MODEL Optional OpenAI-compatible transcription override (for example Groq's /openai/v1/audio/transcriptions with whisper-large-v3). Falls back to the OPENAI_* settings when unset.
HANDOVER_API_URL, HANDOVER_API_KEY, HANDOVER_MODEL Optional OpenAI-compatible chat-completions override for handover drafting. Falls back to the OPENAI_* settings when unset.
RESEND_API_KEY, EMAIL_FROM Enable real magic-link, invitation, and digest delivery.
INBOUND_BASIC_USER, INBOUND_BASIC_PASS Required together to enable the inbound email webhook. Use the credentials in the provider webhook URL/auth header.

Database and migrations

npm run migrate creates the SQLite database and all Phase 1 and Phase 2 tables. SQLite WAL mode is enabled. Do not serve the database or the upload directory as public content. Back them up together, and take the database snapshot with VACUUM INTO rather than copying the file: in WAL mode a plain copy opens cleanly, passes PRAGMA integrity_check, and silently omits everything committed since the last checkpoint.

Authentication and privacy

Magic links expire after one hour. Invitation links expire after seven days. Every first-time user enters their name after they use a magic link. This applies to owners and invited contributors. An owner then enters the circle name. Session cookies are HTTP-only and same-site. They are secure when APP_URL uses HTTPS. Timeline, upload, update, delete, and export operations check circle membership. Only an owner can invite members. Inbound email uses constant-time basic-auth comparison. It rejects unknown senders and removes duplicate MessageID values.

Use HTTPS and a strong operational secret. Use private encrypted storage and backups. Validate sender domains before you handle sensitive family material in production.

Deletion and erasure

There are two separate operations, and only one of them is recoverable.

  • Remove a handover from the timeline. DELETE /api/handovers/:id sets deleted_at. The handover leaves every view. The source, its transcript, and its audio remain stored, so an owner can still find what was said.
  • Erase a source permanently. DELETE /api/sources/:id is owner-only and irreversible. It removes the source and everything derived from it — transcript, note text, handover, pending draft, replies, derived tasks, and tags — inside one transaction, then unlinks the audio file from disk. Topic threads left without evidence are pruned and the remaining source counts are recomputed. If the audio file cannot be unlinked, the row is still gone and the orphaned path is logged for an operator to clear.

Erasure is the operation to use when a family asks for something to be gone. Because it deletes the recording, it removes material that the daily digest and any earlier export may already have carried elsewhere; those copies are outside the app's reach. A backup archive taken before an erasure still contains the erased source, so the backup retention window is the effective ceiling on how long erased material can survive.

OpenAI behavior

Audio is sent to OpenAI only when OPENAI_API_KEY is configured. The transcription is stored separately. A second OpenAI request drafts only the three editable handover strings from that transcript. Server-side length/type validation is applied before persistence. The prompt instructs the model not to diagnose, give medical advice, invent details, or turn family-reported wording into a conclusion. Users see that output is editable before saving.

This is the function in server.js. The prompt applies these limits to every call. If a provider fails, the app uses a labeled fallback.

async function generateHandover(transcript) {
  if (!HANDOVER_KEY) return { ...fieldsFromText(transcript), provider: 'local-development-fallback', model: null };
  const prompt = `Turn this private family voice-note or email transcript into a cautious editable handover.
Do not diagnose, give medical advice, infer facts, or add details. Preserve family-reported wording.
Return JSON only with strings: happened, remembering, tomorrow. ...`;
  try {
    const response = await fetch(HANDOVER_URL, {
      method: 'POST',
      headers: { authorization: `Bearer ${HANDOVER_KEY}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        model: HANDOVER_MODEL,
        messages: [
          { role: 'system', content: 'You write concise, non-medical, source-faithful family handovers.' },
          { role: 'user', content: prompt },
        ],
        response_format: { type: 'json_object' },
        temperature: 0.2,
      }),
      signal: AbortSignal.timeout(45_000),
    });
    if (!response.ok) return { ...fieldsFromText(transcript), provider: `${providerName(HANDOVER_URL)}-unavailable-fallback`, model: HANDOVER_MODEL };
    const body = await response.json();
    return { ...validateFields(JSON.parse(body.choices?.[0]?.message?.content || '{}')), provider: providerName(HANDOVER_URL), model: HANDOVER_MODEL };
  } catch {
    return { ...fieldsFromText(transcript), provider: `${providerName(HANDOVER_URL)}-unavailable-fallback`, model: HANDOVER_MODEL };
  }
}

The model receives only the selected transcript. validateFields limits every returned string on the server. Timeouts, non-200 responses, and malformed JSON use the same deterministic, labeled fallback. The app does not show a raw provider error to a family member.

Email and daily digest

Configure a Postmark-style provider to POST JSON to POST /api/inbound/email with HTTP basic authentication. The endpoint accepts FromFull.Email, MessageID, Subject, and TextBody. It can also use stripped HtmlBody. It preserves the source and transcript, then creates an owner-reviewable email draft. Only the owner can publish that draft to the timeline.

Run the digest once with:

npm run digest

Schedule this command once each day with a host scheduler, such as cron. When enabled by the owner, it sends a Resend email with handovers from the last 24 hours. It also includes open commitments, pending sources, and new tags. The app records each result in digest_deliveries.

Testing

npm test

The tests cover sign-in, name setup for owners and contributors, circle creation, invitations, source access, automatic tags, notes, continuity, commitments, tasks, replies, tags, history search, export, inbound-email deduplication, and outsider denial. Run them where a temporary localhost listener is permitted.

Deployment

This deployment uses one Node process and a private persistent volume for SQLite and uploads. Deployment files are in deploy/. The service listens only on loopback port 9020. Caddy provides HTTPS. SQLite and audio are stored under /var/lib/memory-lane. Do not place them in a static Caddy root.

A production host needs a public HTTPS hostname, APP_URL, encrypted persistent storage, environment secrets, Resend, OpenAI, and a verified inbound-email provider. Use npm start to start the app, npm run migrate to migrate the database, and npm run digest to send the daily digest.

Memreda has a public landing page at https://memreda.xyz. The private app is at https://app.memreda.xyz. The Node process listens on 127.0.0.1:9020 behind Caddy HTTPS. The former Tailnet /memory-lane preview is not part of this deployment. Verify the live app with curl -fsS https://app.memreda.xyz/api/session. Then complete the two-person flow below before you treat Phase 1 as complete.

Live verification steps

  1. Owner requests a magic link at https://app.memreda.xyz and receives it at the configured real inbox.
  2. Owner enters their name, creates a circle, and sends an invitation to the contributor's real inbox.
  3. Contributor accepts the invite, enters their name, records a short browser voice note, reviews the OpenAI-generated editable fields, and saves it.
  4. Owner confirms the handover and preserved transcript in the private timeline, edits it, exports it, and receives the scheduled daily brief.
  5. Contributor forwards an email through the configured Postmark inbound address; the owner reviews/edits the resulting email draft before publishing it to the timeline.

Safety boundaries and known limitations

  • Not for emergencies: call local emergency services when someone may be in immediate danger.
  • Not medical advice or a clinical record; verify health decisions with appropriate professionals.
  • One-circle customer-zero UI; no multi-family administration or complex RBAC.
  • Inbound sender validation is secure transport plus membership matching, but production should also enforce provider SPF/DKIM/domain verification.
  • Audio files are local private disk storage in this deployment shape; move to encrypted object storage for multi-instance hosting.
  • Phase 2 continuity records are deterministic summaries, not clinical conclusions. Review the source handover. The product does not diagnose or certify that a family action occurred.
  • Voice notes remain private, replayable, and retryable when a transcription provider is unavailable. Use the TRANSCRIPTION_* settings to configure an OpenAI-compatible fallback provider.

How Codex and GPT-5.6 Were Used

Memreda was built in Codex with GPT-5.6.

  • Product and architecture. GPT-5.6 reviewed the static prototype and designed a single Node process, SQLite in WAL mode, and a private upload volume.
  • Implementation. Codex sessions implemented authentication, source capture, handover drafting, continuity features, the daily digest, and deployment files.
  • Testing. Codex sessions added the customer-zero and hardening tests.
  • Primary Codex session (2026-07-18): 019f76c6-3420-7032-be7b-b1c9df2824d4. An earlier session covered product ideation: 019f7695-93cf-70f2-a369-09934d9ee004.

The deployed app makes two types of model call: audio transcription and handover drafting. The models do not diagnose, give advice, or add details. The default models are OpenAI gpt-4o-mini-transcribe and gpt-4.1-mini. Use TRANSCRIPTION_* and HANDOVER_* settings to configure compatible fallback providers.

About

Private continuity layer for families sharing care

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages