Project Info
Inspiration
I have always wanted to document my life, but I could never find a method that truly suited me. I don’t enjoy posting Stories on social media, and I don’t want every piece of my life to be shared publicly. But when I choose not to post, my photos, thoughts, voice notes, and life moments end up scattered across albums, chat histories, and notes. Even if I once saved them, after some time it becomes difficult to recall what actually happened. The real problem is not a lack of things worth recording, but the high cost of organizing memories. To properly document an experience, I would need to write it out, add context, tag people, categorize topics, and store it in the right place. This process takes time. For someone with a full-time job and a fast-paced life, this kind of effort is often postponed, and many meaningful moments are never preserved. Most existing tools help us store information, but very few truly reduce the cost of organizing our lives. That is why we built echooo: to allow people who don’t want to share publicly, and don’t have time to organize, to still capture their lives in a natural and low-effort way.
What it does
echooo is a private, voice-first space where users can naturally capture life stories, thoughts, and things they want to do in the future. Its goal is to reduce the cost from “wanting to record something” to “being able to find it again later.” Users don’t need to think about titles, categories, or formats in advance, nor do they need to sit down and write a formal journal entry. At any moment, if they recall an experience, a feeling, or something they want to do, they can simply speak it out and optionally attach a photo. Recording does not require immediate organization. The content is first saved as a pending record, and later transformed into an editable draft. Users can review and confirm people, events, topics, stories, emotions, and follow-up actions, and then decide what becomes part of their memory. The process is: Speak a story, optionally with a photo. Save the raw content as a pending record. Convert it into an editable draft. Review and modify people, topics, events, emotions, and follow-ups. Confirm and save it as a personal memory. Retrieve it later through natural voice queries or filtering. When users want to find something again, they don’t need to remember exact titles, dates, or keywords. They only need to recall fragments that humans naturally remember—who they were with, how they felt, roughly when it happened, or what it was about. echooo doesn’t just store content. It gradually turns scattered life fragments into a personal resource that can be revisited, searched, and used over time.
How we built it
Before implementation, we spent significant time on product and technical decisions. We clearly defined the problem, the full user flow, and the data and privacy constraints the system must follow. We then used AI to help analyze requirements and compare possible technical approaches. However, we did not rely entirely on AI for decisions. We prioritized frameworks we understood and trusted, ensuring we could validate system behavior and implementation quality even while using AI to accelerate development. The frontend is built with React and Next.js, with a mobile-first experience. Recording is handled directly in the browser using the standard MediaRecorder API, so no extensions or native apps are required. The backend uses Rust and Axum, handling authentication, data ownership isolation, media validation, external service requests, and deterministic search. PostgreSQL stores application state and limited-size media, managed through SeaORM and versioned migrations. In the content processing pipeline: Deepgram Nova-3 converts audio into text. GPT-5.6 transforms transcripts into structured drafts for user review. GPT-5.6 also converts natural language queries into strict, typed search filters. Models never access the database directly or generate SQL. All structured outputs are validated by the Rust backend before executing user-scoped PostgreSQL queries. Photos are not processed by external AI systems. Codex was involved in most of the development process, but not for generating isolated code snippets. Instead, we defined each task as a complete, long-running unit that delivers a usable product slice. Each task includes: The user problem it solves Current system and code state Scope of the task Constraints that must not be broken A clear definition of completion Required tests and validations Documentation and follow-ups Through this approach, Codex contributed not only to code generation but also to full-stack implementation across frontend, backend, database, testing, and deployment. Docker and Docker Compose provide a reproducible self-hosted environment, and the judging version is deployed on an HTTPS server.
Challenges we ran into
Our biggest constraint was time. Since I have a full-time job, I could only work on echooo during limited hours after work. Turning an idea with many unclear parts into a usable prototype under these conditions was the main challenge. We adopted a strategy of using AI wherever it could accelerate progress. APIs, tools, and subscriptions were introduced whenever they improved productivity, and work was distributed across parallel threads covering product design, frontend, backend, database, testing, and deployment. However, AI being able to implement does not mean implementation problems disappear. The real challenge was enabling AI to complete long-running tasks that span multiple files and system boundaries without drifting away from the original requirements. As tasks grow longer, AI may lose earlier constraints, misunderstand existing code, produce outputs that seem correct but don’t actually work, or break other parts of the system while fixing one issue. This shifted our focus from simply “building features” to: Translating product requirements into tasks AI can reliably execute Defining clear starting and completion conditions Keeping parallel development threads consistent Detecting issues through testing and real usage Using AI to review and fix outputs generated by other AI processes Ensuring long tasks can resume after interruptions We gradually built systems such as project state tracking, task lists, session handoffs, validation checkpoints, and browser-based flow testing, so that each piece of work produces verifiable results rather than just code that appears complete. Beyond development time, the product itself presented key challenges. First, how to help organize personal memories without letting the system decide what is true. echooo separates raw records, structured drafts, and user-confirmed memories, ensuring only the user determines what becomes part of their history. Second, how to safely handle natural language search. Since user queries can be vague, the model is restricted to producing strictly defined filters. The backend rejects invalid outputs and executes only deterministic, user-scoped queries. Third, how to provide a reliable recording experience in mobile browsers, including handling permission denial, interruptions, size and time limits, retries, draft editing, and recovery from failures. The biggest engineering challenge was not just using AI to build quickly, but creating a development process where AI-generated work can be continuously validated, corrected, and extended.
Accomplishments we're proud of
What we are most proud of is that, within a very limited amount of time, we turned a problem we had repeatedly experienced into a product that actually works. echooo did not begin with a perfectly defined specification. It started from a personal frustration: wanting to preserve meaningful parts of life, but rarely having the time or motivation to organize them. Many parts of the solution were still unclear when development began. As we built the product, the problem gradually became more concrete. What initially seemed like several separate issues—capturing thoughts, organizing memories, tracking things we want to do, and finding old records—came together into one connected experience. The result is a complete working loop: capture → organize → review → confirm → retrieve Users can record a story, turn it into a structured and editable draft, decide what should become part of their confirmed history, and later retrieve it using the incomplete clues people naturally remember. Some parts of the current implementation are still prototype-stage workarounds, but the core experience genuinely functions. It is not only a concept, mockup, or prerecorded demonstration. For us, the most meaningful accomplishment was proving that this personal pain point could become a usable product—and completing that product within the time constraints of the hackathon.
What we learned
Building echooo changed how we think about both the product problem and the software development process. From a product perspective, we learned that the problem is larger than simply transcribing voice notes or searching old records. People already have many ways to capture information. The real difficulty is the effort required to turn scattered fragments into something meaningful, organized, and retrievable. When capture, organization, confirmation, and retrieval are connected in one flow, those fragments can gradually become a personal information system rather than another collection of forgotten files. We also learned that organization should not require users to fully understand the importance of a moment when they first record it. People should be able to capture something quickly and decide how it fits into their lives later. From an engineering perspective, this was the first time in a long while that we had completed such an intensive product sprint within a short period. The capabilities of current AI development tools are significantly stronger than what we had previously experienced. They can contribute not only to isolated pieces of code, but also to longer tasks involving multiple files, system boundaries, tests, migrations, and browser behavior. This changed the skills we needed to focus on. The challenge was no longer only writing every line manually. It became equally important to describe the problem clearly, choose appropriate tools, define task boundaries, establish completion criteria, validate results, and preserve enough project context for work to continue reliably. For a time-constrained sprint like this, failing to make full use of AI-assisted development would mean giving up a substantial productivity advantage. However, increased implementation speed also makes human judgment more important. Product direction, architecture, privacy boundaries, quality standards, and final verification still need clear human ownership. Most importantly, we learned that personal technology should reduce the effort required to understand and preserve our lives—not create another system that demands constant maintenance.
What's next
The current direction of echooo is still largely based on our own experiences and assumptions. The most important next step is to understand whether the product fits naturally into other people’s lives. We have already deployed a working version and invited a small group of friends to try it. Based on their experience, we plan to complete one or two rounds of iteration before deciding on the shape of a broader release. The first round of testing will focus on several fundamental questions: Will people naturally use voice to capture personal moments over time? Is the review and confirmation process lightweight enough? Which parts of a memory are useful to structure, and which should remain as an open narrative? What clues do people actually use when trying to retrieve something later? Does echooo feel like a helpful private space, or another tool that requires maintenance? Data ownership and storage will also be an important part of the next stage. Personal memories are highly sensitive, so we want to explore stronger encryption, clearer export and deletion controls, local or user-controlled storage, and self-hosted options. The long-term goal is for users to have meaningful control over where their memories live and how they are processed. Beyond individual memories, echooo could gradually build longer-term personal context. Future versions may help users follow unfinished intentions, recurring topics, changing relationships, and ideas that develop across multiple entries. We also want to make recaps more meaningful. The current prototype can summarize relatively simple sets of records, but future versions could connect memories across a week, month, or longer period—identifying recurring people, emotions, themes, and changes over time. The goal is not to generate more summaries for users to read. It is to help them notice patterns they might otherwise miss, resurface unfinished things at useful moments, and turn scattered records into a clearer understanding of their own lives.
echooo
Seek what you speak — privately.
echooo is an English-first, voice-first personal memory prototype for OpenAI Build Week. A capture remains pending until the user asks AI to organize it, reviews the proposed structure, and explicitly confirms what becomes memory.
The runnable foundation, account access, authenticated pending text/voice/photo capture, organization boundary, recoverable candidate review, explicit memory commitment, persistent confirmed memory detail, protected title correction, explicit memory/media deletion, zero-typing retrieval, and bounded spoken retrieval are implemented. Every ordinary signed-in screen uses one centered, phone-width Ink & Paper shell with Home, New, Pending, and Search bookmark navigation, a fixed echooo footer, and account controls in the avatar popover. New uses one composer for recording/upload, text, and a photo that unlocks only after voice or text is staged. Administrators see only one admission-only Manage page. Deepgram Nova-3 and GPT-5.6 adapters remain disabled by default; bounded fictional live fixtures validated each provider independently.
Accepted stack
- Next.js 16 with React 19, plain JavaScript/JSX, App Router, and static export
- Rust 2024 with Axum on Tokio
- SeaORM 1.1.20 and SeaORM Migration
- PostgreSQL 18; no SQLite fallback
- One same-origin runtime: Axum serves
/apiand the built frontend - PostgreSQL
media_assetsmetadata plus separatemedia_blobs(content bytea)storage - Docker Compose as the reproducible local/self-host path
The first milestone uses Deepgram Nova-3 Monolingual for bounded English transcription and exact model gpt-5.6-luna for structured memory extraction and spoken-search interpretation. Saving a pending capture does not call either service, and all live-provider paths remain disabled by default.
Repository layout
api/ Rust application, SeaORM entities, release-baseline migration, and data commands
web/ Next.js static frontend and focused component tests
contracts/ Versioned JSON schemas and shared fictional organization/search fixtures
scripts/ Local application and fictional-data smoke helpers
docs/ Durable product, architecture, decision, task, and handoff context
compose.yaml Axum + PostgreSQL local/self-host runtime
Dockerfile Reproducible multi-stage application image
.dockerignore Excludes local dependencies, build output, secrets, and private data
Prerequisites
- Docker Engine or Docker Desktop with Compose for the clean-checkout path; no host Node.js, Rust, or PostgreSQL installation is needed.
- For host-based development only: Node.js 24/npm 11, Rust 1.97 or another compatible stable toolchain with
rustfmtandclippy, and PostgreSQL 18.
After installing Rust with rustup, ensure Cargo is on the shell path, for example with source "$HOME/.cargo/env".
Environment setup
Copy the template and replace the PostgreSQL placeholder locally:
cp .env.example .env
Never commit .env. The application validates these variables at startup:
| Variable | Required | Purpose |
|---|---|---|
APP_ENV | No | development by default; production enforces secure session cookies. |
APP_HOST | No | Listen address; defaults to 127.0.0.1. |
APP_PORT | No | Listen port; defaults to 3000. |
APP_PUBLISHED_PORT | Compose | Localhost port published by Compose; defaults to 3000. |
FRONTEND_DIR | No | Static export directory; defaults to web/out. |
DATABASE_URL | Yes | PostgreSQL connection URL. |
DATABASE_STARTUP_MODE | No | required normally; lazy is reserved for the no-database smoke check. |
RUN_MIGRATIONS | No | Applies SeaORM migrations on startup when true; defaults to true. |
RUST_LOG | No | Rust log filter. Logs must not contain personal content. |
BOOTSTRAP_ADMIN_LOGIN | Seed | Normalized login for the fictional bootstrap Admin. |
BOOTSTRAP_ADMIN_PASSWORD | Seed | Admin password supplied only through local/environment-managed configuration; 12–128 bytes. |
DEMO_USER_LOGIN | Seed | Normalized login for the fictional pre-created user. |
DEMO_USER_PASSWORD | Seed | Demo password supplied only through local/environment-managed configuration; 12–128 bytes. |
DEMO_TIMEZONE_OFFSET_MINUTES | Demo data | Local-day offset for the 14-day River fixture; -840..840, defaults to 480 (+08:00). |
SESSION_TTL_SECONDS | No | Hard lifetime for each opaque session; defaults to 43,200 seconds. |
SESSION_ROTATION_SECONDS | No | Authenticated-use age that triggers token replacement; defaults to 3,600 seconds. |
SESSION_COOKIE_SECURE | No | false for local HTTP; defaults to and is mandatory as true in production. |
POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD | Compose | PostgreSQL container configuration. |
ORGANIZATION_LIVE_PROVIDERS_ENABLED | No | Master live-provider switch; defaults to false. Credentials alone never enable calls. |
ORGANIZATION_EXTERNAL_PROCESSING_CONFIG_ACK | Live only | Must be deepgram_audio_openai_text_images_excluded.v1 after reviewing that boundary. |
DEEPGRAM_API_KEY, DEEPGRAM_MODEL | Live only | Server-only key and accepted nova-3 model; never exposed to the browser. |
DEEPGRAM_MIP_OPT_OUT | Live only | Explicit true/false Deepgram model-improvement choice because it can affect processing and price. |
OPENAI_API_KEY, OPENAI_MODEL | Live only | Server-only key and exact accepted gpt-5.6-luna model. |
OPENAI_REASONING_EFFORT, OPENAI_SERVICE_TIER | Live only | Explicit reasoning and service-tier choices; there is no live default. |
OPENAI_MAX_OUTPUT_TOKENS | Live only | Hard per-extraction output ceiling between 512 and 32,768 tokens. |
ORGANIZATION_REQUEST_TIMEOUT_MS | Live only | Whole-request provider timeout between 1,000 and 120,000 ms. |
ORGANIZATION_CONNECT_TIMEOUT_MS | Live only | Provider connect timeout between 100 ms and the whole-request timeout. |
ORGANIZATION_MAX_RETRIES | Live only | Bounded transient retry count from 0 to 3. |
ORGANIZATION_RETRY_BASE_DELAY_MS | Live only | Exponential-backoff base delay from 50 to 5,000 ms. |
ORGANIZATION_RATE_LIMIT_PER_MINUTE | Live only | Per-owner live organization rate limit from 1 to 60. |
ORGANIZATION_MAX_RUNS_PER_DAY | Live only | Application-wide daily organization ceiling from 1 to 1,000. |
ORGANIZATION_MAX_AUDIO_MS_PER_DAY | Live only | Application-wide daily Deepgram audio ceiling from 1,000 to 86,400,000 ms. |
Run with Docker Compose
Follow the clean Docker runbook for exact prerequisites, required versus optional variables, readiness, deterministic reset/seed/verification, fictional account preparation, provider-disabled operation, teardown, recovery, and failure diagnosis.
For the ordinary path, set POSTGRES_PASSWORD, the matching placeholder in DATABASE_URL, and the four bootstrap/demo account values in .env. Keep ORGANIZATION_LIVE_PROVIDERS_ENABLED=false and leave provider-only values blank. Then run:
docker compose up --build --detach --wait
The one-shot setup service waits for PostgreSQL, migrates, and idempotently seeds the deterministic fictional accounts, review-ready capture, and 68 confirmed River memories across 14 local days. The app repeats migration validation before listening. Open http://127.0.0.1:3000 and sign in with the local demo values. Verify both database-backed runtime endpoints:
curl --fail --silent --show-error http://127.0.0.1:3000/api/health/live
curl --fail --silent --show-error http://127.0.0.1:3000/api/health/ready
The Compose fallback binds the application to localhost only. Its application container runs as a dedicated unprivileged user with a read-only root filesystem, all Linux capabilities dropped, no-new-privileges, and a bounded non-executable temporary filesystem.
The safest current release ledger is m20260720_000001_release_baseline followed by additive m20260720_000002_memory_mood_and_presets. Do not fold the additive migration into the already-applied baseline or rewrite an applied ledger. Exact complete pre-release ledgers are converted only after structural validation; partial, unknown, or ledgerless existing schemas fail closed without discarding application rows.
Fictional seed/reset and provisioning commands remain available inside the Compose network:
docker compose exec -T app reset
docker compose exec -T app seed
docker compose exec -T app seed
docker compose exec -T app verify_demo_state
docker compose exec -T app admin <login> <password>
docker compose exec -T app demo <login> <password>
docker compose exec -T app river_fixture apply <login> <tz_offset_minutes>
docker compose exec -T app river_fixture remove <login>
Do not skip --build for a release or demo reset. A missing verifier or old seed output identifies a stale image: rebuild, run migrate, then repeat reset/seed/verify. Preserve the volume when migration compatibility fails; volume deletion is not an upgrade strategy.
docker compose down stops the runtime while preserving the PostgreSQL volume. This repository's Compose path has been built and exercised with Docker 29.6.1 and Compose 5.3.0 through July 19, 2026.
Run on the host
Start PostgreSQL, create the database named by DATABASE_URL, and then run:
make install
make migrate
make seed
npm --prefix web run build
cargo run --locked --manifest-path api/Cargo.toml --bin personal-memory-api
Open http://127.0.0.1:3000. The seed command is idempotent and creates active fictional accounts for Demo Administrator and Riley Chen, plus one stable fictional pending capture with a review-ready draft for Riley. Every ordinary user, including Riley, also receives exactly one canonical Me person with note This is you and exactly two canonical starter topics, Daily log and Ideas; Admin admission accounts receive none. Repeating seed preserves review edits or confirmation and existing password hashes; changing a supplied password rotates its hash and revokes that account's sessions. make reset removes only those deterministic fictional IDs and their cascading sessions, captures, review drafts, and confirmed relational records.
Provision accounts on demand
Create or refresh an active Admin without changing .env bootstrap values:
cargo run --locked --manifest-path api/Cargo.toml --bin admin -- <login> <password>
docker compose exec -T app admin <login> <password>
Create an active user pre-loaded with seven fictional university stories, each already available as a pending capture with a review-ready draft, plus the 68-memory/14-day confirmed River fixture:
cargo run --locked --manifest-path api/Cargo.toml --bin demo -- <login> <password>
docker compose exec -T app demo <login> <password>
Use a 3–64-character login and a 12–128-byte password supplied only at invocation time; the commands never print the password. Repeating either command for the same role is idempotent. A changed password rotates its Argon2 hash and revokes active sessions, while repeating demo never duplicates or restores storyline/River content. A login already assigned to the other role is rejected. DEMO_TIMEZONE_OFFSET_MINUTES selects the River's local-day basis and defaults to +08:00. The on-demand commands do not modify the deterministic seed, reset, or verify_demo_state accounts.
Account-access, pending-capture, and media API
All request and response bodies are JSON. Personal and Admin routes use the same-origin pm_session cookie.
POST /api/auth/register create a pending user account
POST /api/auth/login sign in an active account
POST /api/auth/logout revoke the current session and clear the cookie
GET /api/auth/me return the active signed-in user
GET /api/admin/accounts list accounts (Admin only)
POST /api/admin/accounts create an active user account (Admin only)
POST /api/admin/accounts/{id}/approve approve a pending user account (Admin only)
GET /api/captures list the signed-in owner's pending captures
POST /api/captures save a pending text capture for the signed-in owner
POST /api/captures/media save multipart text/voice/photo as one pending capture
GET /api/captures/{id} open one owned pending capture
DELETE /api/captures/{id} discard it and erase its source text
POST /api/captures/{id}/organization-runs explicitly start one owned pending organization run
GET /api/organization/availability return provider availability and external-processing disclosure
GET /api/captures/{id}/review open the owned recoverable review draft
PUT /api/reviews/{id} save people/tag edits against an expected revision
POST /api/reviews/{id}/cancel pause a review without creating confirmed records
POST /api/reviews/{id}/reopen reopen a cancelled review
POST /api/reviews/{id}/confirm atomically confirm accepted relational records
POST /api/reviews/people/name-availability check an owner-scoped canonical person name
GET /api/memory-options list the owner's canonical people/topics and fixed moods
POST /api/search/memories execute strict owner-scoped confirmed-memory retrieval
POST /api/search/spoken transcribe and strictly interpret one bounded spoken query
GET /api/memories/{id} open one owned confirmed memory with metadata-only media availability
PUT /api/memories/{id}/title correct one confirmed title with provenance and expected revision
PUT /api/memories/{id}/mood correct or clear one human-selected mood with expected revision
POST /api/memories/{id}/deletion explicitly delete one confirmed memory and its owned source graph
POST /api/memories/{id}/media/{media_id}/deletion independently remove retained source bytes
GET /api/media/{id} open owned available media with private/no-sniff headers
DELETE /api/media/{id} delete media from an owned pending capture
Login identifiers are normalized lowercase usernames containing 3–64 letters, numbers, dots, underscores, or hyphens. Password recovery/change, OAuth, social login, email delivery, and additional Admin creation are deliberately outside this slice. An Admin manages admission only and does not inherit another user's content access.
Capture text is trimmed, must contain 1–4,000 readable characters, and is stored with server capture time and English source-language metadata. A multipart capture requires text or audio and may include one image. The browser can stage either one in-app recording or one uploaded WebM/Ogg/MP4 audio file (including .m4a files declared as audio/mp4), reads uploaded duration from media metadata, and applies the same 60-second/8-MiB limits before submission. MP3 and WAV remain rejected. Images accept decoded JPEG/PNG/WebP up to 5 MiB, 4096 × 4096, and 16 megapixels. The server checks declared type against file signatures, fully decodes images, rejects active/document formats, and ignores original filenames. The entire request is capped at 14 MiB.
Capture/media metadata and blob rows are inserted in one PostgreSQL transaction. Query responses select only media_assets; only the opaque owner-scoped media route reads media_blobs.content. Pending-media deletion removes both rows through the foreign-key cascade. Capture discard atomically deletes its media and erases source text. Capture creation never calls Deepgram, OpenAI, or GPT-5.6 and never creates candidates, people, tags, events, follow-ups, or confirmed memories.
Organization contracts and safe states
contracts/organization/v1 publishes strict JSON schemas plus one shared fictional transcription fixture and one extraction-candidate fixture. Rust/Serde is the canonical runtime trust boundary: it rejects unknown fields, unsupported versions/enums, malformed UUID/date/time values, unsafe identifiers, invalid references, unreadable or oversized content, and over-limit collections. The extraction contract covers people, topics/tags, events, stories/details, and follow-ups. Every output is explicitly unconfirmed and points to its owned source capture as either transcript-derived or text-fallback-derived evidence.
POST /api/captures/{id}/organization-runs preserves the organization_start.v1 request. Disabled and deterministic-test modes accept {"contract_version":"organization_start.v1"}. Live mode additionally requires "external_processing_disclosure":"external_processing_disclosure.v1" after the user has been shown that audio goes to Deepgram, transcript/text goes to OpenAI GPT-5.6, and images are excluded. A run can move only through started → source_validated → candidates_validated or from an unfinished state to failed. On validation success, transcript/candidate content is persisted separately in an unconfirmed review_draft; organization_runs still stores only safe lifecycle/provider metadata.
The runtime defaults to disabled provider adapters. Live construction succeeds only when the master flag and every exact model, disclosure, timeout, retry, rate, and budget value pass startup validation. Deterministic fictional adapters can be injected by tests but cannot be selected through runtime environment configuration. Deepgram receives only bounded owned audio through its fixed HTTPS pre-recorded endpoint with model=nova-3 and language=en. OpenAI receives only the validated transcript or text fallback through a stateless store:false Responses request with strict structured output, an explicit GPT-5.6 model/reasoning/service tier, a privacy-preserving owner identifier, and a hard output-token ceiling. Provider responses are size-bounded; an OpenAI response must report the configured exact model or its valid dated snapshot before content-free metadata is accepted, and canonical Rust validation still gates the unchanged M1-02/search contracts. Image metadata, bytes, filenames, URLs, and image-derived content do not cross either provider interface.
Candidate review and memory commitment
SeaORM Migration m20260719_000006_review_and_memories keeps review_drafts separate from memories and adds the minimum confirmed relational model for people, topics, events, stories, follow-ups, and retained source media. Draft updates and transitions are owner-scoped and revision-checked. Cancel/reopen never creates confirmed records. Final confirmation row-locks the draft and performs memory creation, accepted relational inserts, retained-media links, draft transition, and capture transition in one transaction. Replaying confirmation returns the existing memory; a database failure rolls back every partial write.
People remain canonical per owner under the existing unique normalized-name constraint. person_name_availability.v1 checks only the signed-in owner's canonical people. When a candidate matches one, review requires Link to existing <name> or Create new person — rename first; the latter remains blocked until the display name is unique. Confirmation validates any explicit candidate-to-person link inside the same transaction and rejects an unresolved or stale collision instead of silently merging it.
Ordinary account creation, bootstrap/seed, and on-demand demo provisioning idempotently ensure the canonical Me person and Daily log/Ideas topics. They are ordinary owner-scoped rows under the existing uniqueness rules: review offers them as quick-add choices, Search lists them even before use, and confirmed-memory deletion never treats them as disposable. Me is added only when the user chooses it for a solo/about-owner memory; extraction never inserts it automatically.
review_confirmation.v2 adds one optional human-selected mood: happy, calm, excited, tired, sad, or stressed. Review defaults to none, exposes six labeled chips plus None, and stores the choice atomically with the existing confirmation transaction. Mood is not part of pending captures, extraction candidates, or provider structured output, so AI never chooses or suggests it.
GET /api/organization/availability supplies the UI with provider availability and the fixed disclosure: audio is sent to Deepgram, transcript or text is sent to OpenAI GPT-5.6, and images are excluded. With providers disabled, the UI explains that organization is unavailable while the stable fictional seeded review remains fully testable without credentials.
Confirmed History, detail, and typed retrieval
contracts/search/v2 publishes the closed search_memories.v2 schema plus valid and invalid fictional fixtures. The request contains only bounded people IDs, topic IDs, one optional fixed mood, inclusive YYYY-MM-DD bounds, readable text, and a 1–50 result limit. Rust rejects unknown fields, duplicate/excessive IDs, invalid moods, invalid or excessive date ranges, oversized/control text, unsupported versions, and any raw-SQL field.
Supplied filter dimensions, including mood, combine with AND; IDs within one people/topic list match any selected ID. Date bounds match confirmation dates or related event dates. Escaped PostgreSQL ILIKE covers confirmed memory title/summary and linked person, topic, event, story, and follow-up text. Results use search_memories_results.v2, stable confirmation-descending ordering, and connected confirmed records only. Pending/review candidate content, cross-owner rows, media metadata, and media_blobs.content are never loaded.
History calls that same route with an empty filter for recent confirmed results and sends selected people/topic UUIDs or one selected mood back through the same contract. It renders loading, recent, filtered, empty, failure/retry, and clear-filter states and survives reload through persistence rather than client-only state. People, topic, and mood facet counts narrow client-side from the current connected result set; clear restores recent results and the complete canonical option sets. Final confirmation exposes Open confirmed memory. GET /api/memories/{id} returns the closed memory_detail.v3 contract from contracts/history/v3, including optional mood, source kind/language, all connected confirmed categories, retained-media availability metadata, title provenance, revision, and correction/update timestamps. The detail query selects media_assets metadata but never media_blobs.content; available bytes remain behind the separate owner-scoped media route.
The same versioned contract directory defines memory_title_correction.v1, memory_mood_correction.v1, memory_delete.v1, and retained_media_delete.v1. Title correction is restricted to a readable 1–120-character confirmed title and records user_corrected provenance. Mood correction accepts exactly one fixed mood or null to clear it. Both lock the owned memory, require the expected revision, and update atomically. Confirmed-memory deletion is one revision-checked transaction over the owned capture/review/memory graph while preserving canonical people/topics, including Me and the starter topics. Retained-media deletion is independent and idempotent: it removes the blob while retaining the memory, media link, and metadata-level deleted/source-unavailable state. Cross-owner, Admin, and absent identifiers return the same not-found shape. None of these routes is an external CLI/MCP service.
Spoken retrieval
History includes an explicit spoken-search recorder using the same standard browser boundary as capture: the user starts and stops recording, recording stops automatically at 60 seconds, and the UI supports playback, removal, retry, denied/unsupported/empty/oversized states, and the fixed external-processing disclosure. With providers disabled, the recording action is unavailable and the UI explains why.
POST /api/search/spoken accepts one signature-validated WebM/Ogg/MP4 recording up to 60 seconds and 8 MiB. Deepgram Nova-3 Monolingual receives only the bounded audio. Only its validated English transcript reaches the stateless OpenAI Responses request using exact model gpt-5.6-luna, no tools, a strict search_memories.v2 output schema, and a hard response-size ceiling. The interpretation may include mood only when the transcript explicitly contains one of the six exact mood words; Rust verifies that condition before search. The spoken audio, transcript, and interpretation are not persisted.
Rust remains canonical after both providers: it rejects unknown/raw-SQL fields, malformed or duplicate IDs, invalid dates/combinations, unreadable or oversized text, invalid limits, and oversized output before invoking the unchanged owner-scoped SeaORM retrieval function. Results preserve stable ordering, confirmed-only behavior, Admin exclusion, escaped ILIKE, connected rendering, and metadata-only reads. Rate, daily-run/audio-budget, timeout, and bounded-retry gates apply before provider calls. Deterministic fictional adapters cover ordinary validation without credentials or live calls.
Manual feature test
After the Docker steps above:
- Open
http://127.0.0.1:3000, sign in with the locally configured fictional demo user, and verify the echooo wordmark, personalized Home greeting, 14-day Tides surface with its seven-day window, lockstep display-only Memory River, bounded selected-day panel, four bookmark tabs, Recap entry, and avatar account popover. - Open New. Use the single composer to record a short fictional English note or upload an allow-listed WebM/Ogg/MP4/M4A file; text is always available. Verify duration, playback, replace, and remove states.
- Verify Photo — locked until voice or non-empty text is staged. Attach a fictional JPEG, PNG, or WebP, remove the final voice/text source, and verify the photo is automatically unstaged. Do not use a real photo or recording.
- Select Save to pending. Verify the app remains in New with Saved. Your note is in Pending — nothing is memory yet. and the numberless Pending pebble appears. Open Pending, select the newest tappable block, and verify its detail shows owner-scoped audio/image playback or preview plus the no-AI-on-capture boundary. Home carries the only numeric pending notice.
- In Pending notes (N), open the stable seeded capture. Verify the external-processing disclosure, the understandable provider-disabled state, and people, tags/topics, events, stories/details, and follow-ups.
- Edit one person, remove another, add a fictional person, remove/add a topic, choose a mood, and save. If a person name matches an existing canonical person, verify Link to existing <name> / Create new person — rename first; creating new requires a unique renamed display name. Verify Final confirmation explains why it is disabled until edits and conflicts are resolved and saved.
- Select Cancel review for now and verify the refreshed Pending list appears. Reload, reopen the capture, and verify the edited cancelled draft recovered with no confirmed memory. Select Reopen review and verify it returns to the refreshed list before reopening.
- Select Final confirmation and verify the refreshed Pending list appears with its count reduced, then select Open confirmed memory. Verify detail shows the title, summary, date, source, people, topics, event, story, follow-up, and retained-media availability.
- Correct the confirmed title, save it, and verify the protected-correction marker appears. Reload and verify the title and marker persist. Open Search, apply a person or topic under Manual filters, verify the shared active-filter pills and facet counts narrow with the connected results, then Clear filters.
- Open Delete confirmed memory, verify the warning distinguishes memory deletion from independent source deletion, select Keep memory, then reopen it and confirm deletion. Verify detail closes, History refreshes, and the success state remains understandable.
- Under Search, inspect AI search (spoken). With ordinary providers disabled, verify the disclosure and provider-disabled state are clear and recording cannot start. A successful authorized run must show both You said and removable interpreted-filter pills above the same result cards. A live browser-format provider check requires separate authorization; do not enable providers or use a real recording for this local test.
- For pending-media checks, create another capture, choose Delete audio or Delete image, and verify only that pending attachment disappears. Independent retained-media deletion requires a confirmed memory with retained media; the deterministic timed seed is text-only, so that path is covered by focused UI and isolated PostgreSQL fixtures rather than claimed by this seed.
- Sign in as Admin separately and verify the single Manage page shows total accounts, active/pending status, and Approve only for pending accounts. Verify the user workspace is absent and the demo user's capture/media/review/memory IDs still return not found.
The app stops recording automatically at 60 seconds and explains denied permission, unsupported browser format, empty recording, oversized audio/image, invalid server-side media, and retry states. A physical microphone must be tested in a secure browser context; http://127.0.0.1 is a secure-context exception for local desktop testing, while a phone requires HTTPS.
Use only fictional text. The checked demo fixture is: Met fictional friends Maya and Leo at the night market.
Quality and validation commands
make format-check
make lint
make typecheck
make test
TEST_DATABASE_URL=postgres://... make test-integration
make build
make smoke
The JavaScript project uses ESLint, Prettier, Vitest, Testing Library, and a pinned lockfile. Rust uses rustfmt, Clippy with warnings denied, cargo check, unit tests, and a pinned lockfile. The explicit integration target refreshes its isolated PostgreSQL schema per suite. Its nine current suites add release-schema coverage for empty and reused databases, exact legacy-ledger conversion with preserved data, the additive mood migration, ordinary-user preset backfill, partial-ledger rejection, deterministic seed/reset/verifier behavior, and the exact tables, enum labels, indexes, primary/unique constraints, foreign-key targets, and delete actions. The remaining suites cover account/media/organization/review behavior, every ordinary-user provisioning path, typed owner-scoped mood/people/topic/date/text retrieval and confirmed controls, deterministic explicit-mood spoken interpretation/execution, and demo-state verification. make smoke verifies that the static page and API liveness endpoint load from the same Axum origin without using a fallback database.
Dependency/security scans must be reported literally. On July 19, npm's online and cached/offline production audits found zero vulnerabilities. The cached Rust advisory scan reported the unfixed RUSTSEC-2023-0071 advisory only in an unused optional rsa lockfile branch and an unmaintained/future-incompatible build-time macro dependency through SeaORM. The final Docker Scout run reported one critical and two high unfixed advisories in Debian's perl package; the application does not invoke Perl, the image was current, and the recommended bases retained the same critical/high count. These are recorded upstream/tool limitations, not passing checks. cargo-deny, Trivy, and Grype were unavailable.
The recorded dependency/security commands were:
(cd api && cargo audit --file Cargo.lock --no-fetch)
(cd api && cargo report future-incompatibilities --id 1)
(cd web && npm audit --offline --omit=dev)
(cd web && npm audit --omit=dev)
docker scout cves --only-severity critical,high local://personal-memory-app-app:latest
docker scout recommendations local://personal-memory-app-app:latest
git ls-files
git grep -n -I -E 'BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|sk-[A-Za-z0-9_-]{20,}|api[_-]?key[[:space:]]*='
cargo-deny, Trivy, and Grype were not installed, so they were recorded as unavailable rather than passed.
Docker runtime checks:
docker compose up --build --detach --wait
docker compose exec -T app migrate
docker compose exec -T app migrate
docker compose exec -T app reset
docker compose exec -T app seed
docker compose exec -T app seed
docker compose exec -T app verify_demo_state
curl --fail http://127.0.0.1:3000/api/health/live
curl --fail http://127.0.0.1:3000/api/health/ready
docker compose ps --all
Run docker compose run --rm app reset to remove only the two deterministic fictional users and their cascading credentials, sessions, and captures, then rerun seed and verify_demo_state. Current seed and reset binaries fail closed when their migration set is pending or required tables are missing. Never point reset or integration tests at a database containing data you need.
Database validation uses SeaORM commands:
make migrate
make seed
make reset
The consolidated release baseline and subsequent additive mood/preset migration are tracked in PostgreSQL's seaql_migrations table. All application database access, migration compatibility work, and state-changing seed/reset behavior go through SeaORM/SeaQuery with no raw SQL. SQLx is only a transitive PostgreSQL driver.
Security and data boundaries
- Commit fictional data only. Never commit API keys, credentials, recordings, personal notes, real user data,
.env, local database files, or generated secrets. - Passwords are never stored by the application; Argon2id PHC hashes live in a one-to-one credential table. Real bootstrap/demo values come only from environment configuration.
- Session cookies are opaque, HttpOnly, SameSite=Strict, explicitly expired, rotated, and Secure in production. PostgreSQL stores only SHA-256 token digests; logout and password rotation revoke sessions.
- Unsafe cross-origin mutations are rejected before authentication/body processing. Unknown
/api/*routes fail closed as JSON instead of falling through to the static frontend. - API responses are private/no-store. All responses add no-sniff, frame-denial, no-referrer, same-origin resource, restrictive content, permissions, and content-free correlation headers.
- Pending and disabled accounts cannot sign in or use protected routes. Login failures and registrations have bounded in-memory rate limits for this single-process MVP.
- Owner authorization is explicit and does not grant Admins a content-reading bypass.
- Pending capture routes require an active account and filter by owner. A cross-owner capture identifier returns the same not-found response as an absent identifier.
- Media identifiers are opaque UUIDs. Open/delete routes require an active owner session, use private/no-store,
nosniff, same-origin resource policy, and safe inline disposition, and do not let Admin admission authority read another user's bytes. - Upload validation caps the whole request and each file, compares declared and detected types, fully decodes bounded raster images, rejects SVG/document/active formats, and commits capture/metadata/blob rows transactionally.
- Discarded pending text is erased rather than retained in the Inbox; only minimal discarded-state metadata remains.
- Organization requires a separate explicit active-owner request. Runtime providers are disabled by default; live calls additionally require the versioned disclosure acknowledgement and validated rate/budget configuration. Candidate output persists only as an owner-scoped unconfirmed review draft.
- Review edits and state transitions are owner-scoped and revision-checked. Cancel/reopen produces no confirmed rows. Final confirmation is one row-locked transaction; retry returns the existing memory and a failed transaction leaves the capture and draft recoverable.
- Confirmed title correction is owner-scoped, row-locked, revision-checked, and marked
user_corrected; organization cannot be rerun against a confirmed capture to silently overwrite it. - Confirmed-memory deletion is explicit and transactional. Independent retained-media deletion removes blob content while keeping its metadata/link source-unavailable, is safe to replay, and does not expose whether a cross-owner or absent identifier exists. Shared canonical people/topics survive while referenced by other memories.
- Typed search derives owner scope from the active session, validates a closed bounded contract, escapes
ILIKEmetacharacters, starts and finishes on confirmed owner rows, and never queries pending/review content or media blobs. The model cannot provide SQL. - Rust validates raw provider JSON and the exact configured model response before output can cross into candidate/search state. Organization records contain only safe lifecycle/provider metadata and content-free error codes; transcript, candidate/search JSON, source text, filenames, media paths/bytes, and personal identifiers are excluded.
- Application diagnostics contain only a random request ID, HTTP method, matched route template, status, duration, and outcome. They never log raw request URIs/resource IDs, cookies, credentials, source/transcript/candidate/search content, filenames, or media paths/bytes.
- Images never enter transcription or extraction provider requests. Extraction receives only a validated transcript or the owned text fallback.
- Public demos, tests, screenshots, fixtures, and logs must remain fictional.
- When explicitly enabled and acknowledged, audio is sent to Deepgram and transcript/text is sent to OpenAI GPT-5.6 only after a user-triggered organization action. Images are excluded from external AI processing in the MVP.
- PostgreSQL persistence is local in the first build, but planned transcription and GPT processing are external.
- The prototype is not a production privacy or security guarantee.
Codex and GPT-5.6
Codex was used to review product decisions, establish the GitHub repository, build the runtime foundation, implement account access, pending capture, organization, recoverable candidate review, atomic memory commitment, protected correction/deletion controls, typed retrieval, and bounded spoken retrieval, run the real Docker/PostgreSQL/browser path, and test owner isolation, unconfirmed exclusion, strict model-output rejection, shared-record preservation, and rollback. Keep the task containing the majority of core implementation as competition evidence.
GPT-5.6 is not called by the default runtime. M1-03's server-side Responses structured-output adapter was validated with a bounded fictional text capture while retaining the strict extraction_candidates.v1 Rust trust boundary and the fictional deterministic adapter. Spoken retrieval now uses a separate strict search_memories.v2 interpretation request with exact model gpt-5.6-luna; only a validated English transcript is sent, and Rust revalidates the bounded response—including the explicit-word rule for mood—before deterministic SeaORM execution. Extraction still has no mood field and cannot choose Me or any mood. Both live requests are stateless, output-bounded, and disabled unless every gate is configured. The model never emits or executes raw SQL.
Current limitations and next task
M1-08 completed the full local MVP proof and froze the provider-disabled Docker/browser method in the M1 demo runbook. Release consolidation added the direct one-command Compose path, guarded SeaORM release baseline, exact-schema compatibility coverage, and MIT license. The current build additionally provides ordinary users with canonical Me/Daily log/Ideas options and a human-only optional mood through review, correction, detail, History, manual filtering, and explicit-word spoken interpretation. Host gates, all nine isolated PostgreSQL suite binaries/10 tests, demo-chain verification, preserved/fresh Docker migration proofs, optimized build/smoke, and responsive browser checks passed with live providers disabled. The stable timed seed remains text-only and live browser WebM/Ogg/MP4-to-provider proof is still separately gated.
The repository-local MVP queue, hosted HTTPS product flow, and July 22 publication preflight are complete. Public video, core-build Codex /feedback evidence, final submission materials, and direct Devpost submission evidence remain.
Do not set ORGANIZATION_LIVE_PROVIDERS_ENABLED=true or send the disclosure acknowledgement except for separately authorized, bounded evaluation or runtime use.
Project documents
- Project state
- Product
- Architecture
- Accepted decisions
- Tasks and milestone criteria
- Competition requirements
- M1 demo runbook
- Clean Docker runbook
- Latest handoff
Repository and license status
The source is licensed under the MIT License, Copyright (c) 2026 hok-io. The Privacy Notice, Prototype Terms, Security Policy, and dated publication audit document the public prototype boundary.
Analysis
View
Metric
- 40
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- CSSIn code
- JavaScriptIn code
- Next.jsIn code
- ReactIn code
- RustIn code
- OpenAIClaimed
- PostgreSQLClaimed
5 of 7 appear in the indexed code. 2 claimed on Devpost could not be matched to code, which may simply mean the tool leaves no trace in the repository.
AI coding agents
- CodexConfig
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
1.4 MB
Source files
103
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
hok-io/personal-memory-app
148 files · 1.8 MB · @ 2af4882
Structure
Interface
3 files · 2%Screens, components and styles rendered to the user.
API & routing
31 files · 21%Request entry points: routes, handlers and controllers.
Application logic
33 files · 22%Domain rules, services and shared utilities.
Data & schema
31 files · 21%Schema definitions, migrations and data access.
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- Rust48%
- Markdown31%
- JavaScript18%
- CSS2%
- YAML0%
- Shell0%
Share of indexed source by file size. Binary and vendored files are excluded.
Dependencies
api/Cargo.toml
cargo · 21- anyhow
- argon2
- axum
- base64
- chrono
- cookie
- dotenvy
- image
- rand_core
- reqwest
- sea-orm
- sea-orm-migration
- serde
- serde_json
- sha2
- tokio
- tower-http
- tracing
- +3 more
web/package.json
npm · 11- next
- react
- react-dom
- +8 more
Declared in the repository’s manifests at the indexed commit. A declared package is not proof it is used, and runtime dependencies are listed first.
This project’s features have not been analysed yet.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.