{"id":50248756,"url":"https://github.com/anchildress1/vestige","last_synced_at":"2026-05-27T00:30:31.872Z","repository":{"id":356609043,"uuid":"1233196257","full_name":"anchildress1/vestige","owner":"anchildress1","description":"Brain tracker that won't blow smoke up your ass. Gemma 4, Android, fully local.","archived":false,"fork":false,"pushed_at":"2026-05-23T02:55:33.000Z","size":20291,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T03:27:51.965Z","etag":null,"topics":["adhd-friendly","android","brain-tracker","cognition-engine","dev-challenge","gemma4","google-gemma","jetpack-compose","kotlin","litert-lm","llm-inference","local-llm","objectbox","on-device-ai"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anchildress1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-08T17:39:13.000Z","updated_at":"2026-05-22T17:41:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/anchildress1/vestige","commit_stats":null,"previous_names":["anchildress1/vestige"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/anchildress1/vestige","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anchildress1%2Fvestige","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anchildress1%2Fvestige/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anchildress1%2Fvestige/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anchildress1%2Fvestige/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anchildress1","download_url":"https://codeload.github.com/anchildress1/vestige/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anchildress1%2Fvestige/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33545458,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"ssl_error","status_checked_at":"2026-05-26T15:22:15.568Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adhd-friendly","android","brain-tracker","cognition-engine","dev-challenge","gemma4","google-gemma","jetpack-compose","kotlin","litert-lm","llm-inference","local-llm","objectbox","on-device-ai"],"created_at":"2026-05-27T00:30:30.033Z","updated_at":"2026-05-27T00:30:31.859Z","avatar_url":"https://github.com/anchildress1.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eVestige\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\u003cem\u003eA brain tracker that won't blow smoke up your ass. Gemma 4, Android, fully local.\u003c/em\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Built for the \u003ca href=\"https://dev.to/devteam/join-the-gemma-4-challenge-3000-prize-pool-for-ten-winners-23in\"\u003eGemma 4 Challenge\u003c/a\u003e — submission category: Build with Gemma 4.\u003cbr/\u003e\n  Canonical product spec lives under \u003ca href=\"./docs/\"\u003e\u003ccode\u003edocs/\u003c/code\u003e\u003c/a\u003e; see \u003ca href=\"AGENTS.md\"\u003e\u003ccode\u003eAGENTS.md\u003c/code\u003e\u003c/a\u003e for AI agent rules.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"CI GHA Status\" src=\"https://img.shields.io/github/actions/workflow/status/anchildress1/vestige/.github%2Fworkflows%2Fci.yml?style=for-the-badge\u0026logo=github\u0026label=CI\" /\u003e\n  \u003cimg alt=\"CodeQL GitHub Actions Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/anchildress1/vestige/.github%2Fworkflows%2Fcodeql.yml?style=for-the-badge\u0026logo=github\u0026label=CodeQL\" /\u003e\n  \u003cbr/\u003e\n  \u003cimg alt=\"Sonar Violations\" src=\"https://img.shields.io/sonar/violations/anchildress1_vestige?server=https%3A%2F%2Fsonarcloud.io\u0026format=short\u0026style=for-the-badge\" /\u003e\n  \u003cimg alt=\"Sonar Coverage\" src=\"https://img.shields.io/sonar/coverage/anchildress1_vestige?server=https%3A%2F%2Fsonarcloud.io\u0026style=for-the-badge\u0026color=limegreen\" /\u003e\n  \u003cimg alt=\"Sonar Tests\" src=\"https://img.shields.io/sonar/tests/anchildress1_vestige?server=https%3A%2F%2Fsonarcloud.io\u0026compact_message\u0026style=for-the-badge\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://repository-images.githubusercontent.com/1233196257/6d5cb58c-808a-4c73-8627-ee3d5dc7ad7c\" alt=\"Vestige social banner\" /\u003e\n\u003c/p\u003e\n\n## Table of Contents\n\n- [About](#about)\n- [Status](#status)\n- [Features](#features)\n- [Tech Stack](#tech-stack)\n- [Architecture](#architecture)\n- [Project Structure](#project-structure)\n- [Getting Started](#getting-started)\n- [Configuration](#configuration)\n- [Security \u0026 Privacy](#security--privacy)\n- [How to Contribute](#how-to-contribute)\n- [What's Next](#whats-next)\n- [Known Limitations](#known-limitations)\n- [License](#license)\n- [Acknowledgements](#acknowledgements)\n- [Author](#author)\n\n---\n\n## About\n\nVestige observes behavioral traces and surfaces patterns without therapy framing, mood scoring, or wellness vocabulary. It runs Gemma 4 E4B locally via LiteRT-LM — your voice never leaves the device, the audio bytes are discarded after inference, and entries can be exported as readable markdown at any time.\n\nThe positioning is deliberate: cognition tracker, not journal app. Patterns are sourced — every claim cites the entries it counted. Full product spec: [`docs/concept-locked.md`](docs/concept-locked.md).\n\n---\n\n## Status\n\nThe full loop is implemented and runs on-device: voice / typed capture → Gemma 4 E4B → 3-lens extraction → convergence resolver → ObjectBox, with deterministic pattern detection. EmbeddingGemma embeds each entry by its tone word and powers Vocab Drift: the `VOCAB_FREQUENCY` cluster mints on the demo corpus (the `Drained Vocab Frequency` pattern, verified on-device). The earlier ranked content-retrieval path was cut when the embedding axis moved to the tone word (see [Known Limitations](#known-limitations)). Entry Detail surfaces the model's actual work: the three-lens read, the picked archetype, the tone word, and a collapsible raw per-lens model-output view. Capture, history, pattern list + detail, settings, model-status, and onboarding model-download are all built against the canonical spec under [`./docs/`](docs). Pattern lifecycle is Skip / Drop / Restart — closure is model-detected only (v1.5, see [`backlog.md`](docs/backlog.md) §`pattern-auto-close`). The active phase is on-device prompt tuning against a seeded demo corpus; risk through phases 1–3 was managed via five stop-and-test points (STT-A–E), and on-device tuning since (STT-F–H) lifted the model off its `audit` default into differentiated archetypes. Screen-flow diagrams: [`docs/diagrams/user-flows.md`](docs/diagrams/user-flows.md).\n\n---\n\n## Features\n\n| Feature | What it does |\n|---|---|\n| Voice capture | `AudioCapture` → Gemma 4 E4B native audio modality. No third-party STT. Audio bytes discarded after inference. |\n| Multi-lens extraction | Each entry runs 3 lens passes (Literal / Inferential / Skeptical), each covering all 5 surfaces in one call; a convergence resolver votes every field consensus / candidate / ambiguous / consensus-with-conflict — tags, archetype, stated commitment, recurrence, and tone word. See [ADR-002](docs/adrs/ADR-002-multi-lens-extraction-pattern.md). |\n| Model transparency | Entry Detail exposes the model's actual work — the picked archetype, the per-lens read, the resolved field grid, and a collapsible raw per-lens model-output block. Nothing is hidden behind a score. |\n| Tone \u0026 vocab drift | The Inferential lens names a one-word tone per entry; entries are embedded by that tone word, and recurring/related tones surface as an EmbeddingGemma cluster (Vocab Drift). Minting on the demo corpus — the `Drained Vocab Frequency` cluster, verified on-device. |\n| Three personas | Witness / Hardass / Editor — tone-only variants. They do not fork extraction logic. |\n| Pattern detection | Six primitives counted over the last 90 days; sourced (counts, dates, snippets), no feelings or motivation interpretation. See [ADR-003](docs/adrs/ADR-003-pattern-detection-and-persistence.md). |\n| Storage | ObjectBox is the internal source of truth. Export renders readable markdown from rows on demand. |\n| Pattern lifecycle | Skip (returns in 7 days) / Drop (noise, archived) / Restart, with Undo. Closure is model-detected only — v1.5. |\n| Export | System-picker (SAF) zip of per-entry markdown. No storage permission; failures surface, never silent. |\n| Local-only | Zero outbound network calls during normal operation; model download is the only network event. Verified with `tcpdump`. |\n\n---\n\n## Tech Stack\n\n- Kotlin `2.3.21` + Jetpack Compose (BOM `2026.05.00`), AGP `9.2.1`\n- Gradle KTS + version catalog ([`gradle/libs.versions.toml`](gradle/libs.versions.toml))\n- Gemma 4 E4B via LiteRT-LM (`com.google.ai.edge.litertlm:litertlm-android:0.11.0`), on-device only\n- ObjectBox `5.4.2` (entries, tags, patterns, vectors) + generated markdown export\n- Android `minSdk 31` / `targetSdk 35` / `compileSdk 36`, JVM toolchain 25 (Java source/target compat 17)\n\n---\n\n## Architecture\n\nFour-module split with manual constructor injection through a single `AppContainer` ([ADR-001 §Q1–Q2](docs/adrs/ADR-001-stack-and-build-infra.md)). Foreground call returns transcription + persona-flavored follow-up fast; the 3-lens convergence pass runs in the background and writes consensus / candidate / ambiguous fields when it lands.\n\n```mermaid\nflowchart TB\n    User([User]) -- voice --\u003e Audio\n    User -- \"type · persists directly, skips FG (ADR-018)\" --\u003e BG\n\n    subgraph onDevice[\"on-device only — no network at runtime\"]\n      Audio[\"AudioCapture\u003cbr/\u003emono 16 kHz float32 · 30 s cap\"]\n      FG[\"ForegroundInference\u003cbr/\u003efast transcription + inline persona follow-up\"]\n      BG[\"BackgroundExtractionWorker\u003cbr/\u003e3 sequential lens passes × 5 surfaces\"]\n      Gemma[(\"Gemma 4 E4B\u003cbr/\u003evia LiteRT-LM\")]\n      Resolver[\"Convergence Resolver\u003cbr/\u003econsensus · candidate · ambiguous · w/ conflict\"]\n      ObjectBox[(\"ObjectBox (source of truth)\u003cbr/\u003eentries · tags · patterns · vectors\")]\n      Export[(\"Export renderer\u003cbr/\u003emarkdown + JSON snapshot\")]\n      Patterns[\"Pattern Detection\u003cbr/\u003e6 primitives · 90-day window · every 3 entries\"]\n\n      Audio --\u003e FG\n      FG -- \"prompt + audio\" --\u003e Gemma\n      Gemma -- \"transcription + follow-up\" --\u003e FG\n      FG --\u003e ObjectBox\n      FG -. \"hands off\" .-\u003e BG\n      BG -- \"3 lens prompts\" --\u003e Gemma\n      Gemma -- \"lens output\" --\u003e BG\n      BG --\u003e Resolver\n      Resolver --\u003e ObjectBox\n      ObjectBox --\u003e Patterns\n      Patterns --\u003e ObjectBox\n      ObjectBox --\u003e Export\n    end\n```\n\nModule boundaries: `:app` (UI), `:core-inference` (LiteRT-LM + lens composition), `:core-storage` (ObjectBox + markdown), `:core-model` (domain types). Ownership detail: [`docs/architecture-brief.md`](docs/architecture-brief.md).\n\n---\n\n## Project Structure\n\n```\n.\n├── app/                       # :app — Compose UI, navigation, AppContainer (manual DI)\n├── core-model/                # :core-model — domain types, manifests, no Android deps\n├── core-inference/            # :core-inference — LiteRT-LM engine + 3-lens composition\n├── core-storage/              # :core-storage — ObjectBox rows + export markdown renderer\n├── docs/                      # canonical product/architecture/UX spec\n│   ├── README.md              # reading order + file inventory\n│   ├── PRD.md                 # P0/P1/P2 requirements + phase schedule\n│   ├── concept-locked.md      # full product spec\n│   ├── adrs/                  # ADR-001..018, no 009 (stack, lenses, patterns, lifecycle, runtime, design, …)\n│   ├── architecture-brief.md\n│   ├── design-guidelines.md\n│   ├── ux-copy.md             # locked microcopy authority\n│   ├── spec-pattern-action-buttons.md\n│   ├── sample-data-scenarios.md\n│   ├── backlog.md             # deferred features w/ unblock conditions\n│   └── stories/               # phase-1..7 build queue\n├── poc/                       # Compose-port reference (screenshots)\n├── gradle/                    # version catalog + dependency verification\n├── scripts/                   # doctor, lint, secret scan helpers\n├── AGENTS.md                  # AI implementor guardrails (authoritative)\n├── CLAUDE.md                  # Claude Code → AGENTS.md pointer\n├── lefthook.yml               # pre-commit / commit-msg / pre-push hooks\n├── Makefile                   # local CI surface\n├── LICENSE\n└── README.md\n```\n\nFour-module split per [ADR-001](docs/adrs/ADR-001-stack-and-build-infra.md): `:app` (UI) depends on `:core-inference`, `:core-storage`, and `:core-model`; the core modules do not depend on `:app`.\n\n---\n\n## Getting Started\n\n### Prerequisites\n\n| Tool | Required for | Install |\n|---|---|---|\n| JDK 25 LTS (Temurin) | Gradle runtime + Kotlin toolchain | `brew install --cask temurin` |\n| Android SDK + `adb` | build + install on device | Android Studio, or `brew install --cask android-commandlinetools` |\n| System Gradle (optional) | regenerating the wrapper jar (`make bootstrap-wrapper`); not needed for routine builds, since `gradle/wrapper/gradle-wrapper.jar` is committed | `brew install gradle` |\n| `lefthook` | git hooks | `brew install lefthook` |\n| `gitleaks` | secret scan | `brew install gitleaks` |\n| `actionlint` | workflow lint | `brew install actionlint` |\n| `ktlint` | format + lint Kotlin | `brew install ktlint` |\n| `detekt` | static analysis Kotlin | `brew install detekt` |\n| `gh` | repo ops | `brew install gh` |\n\n`ANDROID_HOME` must be set and `$ANDROID_HOME/platform-tools` must be on `PATH` so `adb` resolves. Gradle dependency verification is pinned in `gradle/verification-metadata.xml`; refresh it only when changing dependencies.\nSonarCloud analysis runs through the Gradle `sonar` task in CI rather than a standalone scanner config, because Android builds deserve one source of truth at a time.\n\n### Build\n\n```bash\nmake setup      # bootstrap gradle wrapper, install lefthook hooks\nmake doctor     # verify local toolchain and environment variables\nmake build      # assemble debug APK\nmake test       # unit tests for changed modules (75% coverage gate runs in `make verify`)\nmake lint       # ktlint + detekt + Android lint\nmake verify     # lint + test + build + staged secret scan\nmake ci         # full local check (lint + test + build)\nmake clean\n```\n\n`make setup` is the hook/bootstrap target. `make install` is now device-only and requires `adb`; it does not install lefthook anymore.\n\n### Run on a device\n\nReference device: Galaxy S24 Ultra. External devices are best-effort; submission promise is Android 14+, 8 GB RAM, 6 GB free storage.\n\n```bash\nmake install    # assemble + adb install debug APK without wiping app data\nmake reinstall  # reinstall APK, push models, seed debug fixtures, tail logcat\n```\n\n### Demo data on hardware (dev builds only)\n\n\u003e **Just want to try Vestige?** Install the **release APK** from the GitHub release and onboard normally — you get a clean first-run (`Nothing on file.`) and capture your own entries. The seeded demo corpus below is a **dev-only reproduction aid** and never ships in the release APK.\n\nThe demo seed is a deterministic ~36-entry corpus loaded by `DebugPatternSeeder` through an ADB-triggered `DebugSeedReceiver` that exists **only in debug builds** (`app/src/debug/…`, registered in the debug manifest overlay). It writes rows straight to ObjectBox and marks onboarding complete, so the seeded build opens past first-run with history already populated.\n\n```bash\nmake reinstall ENV=dev            # clean debug install + push models + seed + launch + tail logcat\nmake reinstall ENV=dev EXTRACT=1  # same, plus run background extraction so cards carry real lens receipts\nmake seed-entries                 # re-seed an already-installed debug build (no reinstall)\nmake seed-entries EXTRACT=1       # re-seed + run extraction\n```\n\n- **What `EXTRACT=1` actually does — and why it's slow.** Seeding writes each entry as text **straight to ObjectBox** and **bypasses the foreground model call entirely** — there is no audio, no transcription, and **no model foreground response**. `EXTRACT=1` then runs the **live background extraction once per seeded entry** (the same sequential 3-lens convergence a real capture runs). Expect a **full GPU load of ~30 s per entry** while it churns. Across the **~36-entry** corpus that's **≈18 minutes** at 30 s/entry — in practice **budget ~25–30 minutes**: the measured full 3-lens pass is ~44 s/entry (STT-F), and entries that mint a pattern add an observation + title pass on top. Watch `DebugSeedReceiver` / `Vestige` in logcat for `seed complete`. **If you don't want to sit through that, install the clean release APK instead** (no seed, no extraction).\n- **Without `EXTRACT=1`** entries seed instantly but stay `PENDING` — no lens receipts, no patterns, no vocab clusters. Fine for a History/layout check, useless for the extraction and pattern beats.\n- **Idempotent** — each seed wipes the entry / tag / pattern / cooldown tables and reloads, so re-running never duplicates.\n- Seed timestamps are **local wall-clock** spanning `2026-04-25` → `2026-05-22` (entry prose names clock times like \"2am\", so the loader seeds in the device zone, not UTC).\n\n**What you'll see on-screen after seeding:**\n\n- **History** — the full timeline populated, newest first.\n- **Patterns** — a **Tuesday-afternoon meeting-crash** recurrence (template `Crashed`) forms once the third supporting entry lands, with a sourced callout. A **Thursday-evening** cluster is a deliberate *negative control* — same time slot, unrelated end-of-day logistics. Temporal detection is deterministic, so it **does** mint a `TEMPORAL_RELATIVE` \"Thursday evening\" pattern from the shared weekday + time block alone (≥ 3 distinct dates); the point of the control is that it surfaces as a **benign time-block observation**, not a cognitive recurrence — the demo shows the model can tell \"I always log at 5pm\" from \"I crash every Tuesday.\"\n- **Vocab Drift** (requires `EXTRACT=1`) — *intended* to group entries that share **no keywords** (\"drained\", \"wiped out\", \"running on empty\", \"depleted\", \"burnt out\", \"brain fog\") into one exhaustion cluster, with a separate positives cluster (\"locked-in\", \"clear\", \"good\", \"sharp\") — the embedding proof. On the demo seed this **mints** a `VOCAB_FREQUENCY` pattern (`Drained Vocab Frequency`), verified on-device 2026-05-24 — the embedding proof is live.\n- Type **\"I hate demos\"** as a live entry and it joins the seeded **demo-dread** cluster.\n\nRequired local artifact filenames match [`core-model/src/main/resources/model/manifest.properties`](core-model/src/main/resources/model/manifest.properties):\n\n```text\n~/Downloads/gemma-4-E4B-it.litertlm\n~/Downloads/embeddinggemma-300M_seq512_mixed-precision.tflite\n~/Downloads/sentencepiece.model\n```\n\nFor a production first-run check with no pushed model and no fixtures:\n\n```bash\nmake reinstall ENV=prod\n```\n\n**One-time phone setup**\n\n1. **Settings → About phone** → tap **Build number** 7 times to unlock developer options.\n2. **Settings → Developer options** → enable **USB debugging**. Optional: enable **Wireless debugging** if you'd rather not cable up.\n3. (Optional) **Stay awake** while charging — speeds iteration.\n\n**Connect**\n\nUSB:\n\n```bash\nadb devices\n# expect: \u003cserial\u003e    device\n# if \"unauthorized\", accept the prompt on the phone (check \"Always allow\")\n```\n\nWireless (Android 11+):\n\n```bash\n# On phone: Developer options → Wireless debugging → Pair device with pairing code\nadb pair \u003cip:port\u003e      # use the pair port + 6-digit code shown on phone\nadb connect \u003cip:port\u003e   # then use the connect port shown on phone\nadb devices             # verify\n```\n\n**Install + launch**\n\n```bash\n./gradlew :app:installDebug\nadb shell monkey -p dev.anchildress1.vestige -c android.intent.category.LAUNCHER 1\n```\n\n`installDebug` builds and installs in one step. The `monkey` invocation just opens the launcher activity without you having to tap the icon.\n\nManual APK install:\n\n```bash\n./gradlew :app:assembleDebug\nadb install -r app/build/outputs/apk/debug/app-debug.apk\n```\n\n**Tail logs**\n\n```bash\nadb logcat -s \"VestigeApplication:*\" \"AndroidRuntime:E\" \"*:F\"\n```\n\n**Reinstall clean**\n\n```bash\nadb uninstall dev.anchildress1.vestige\n./gradlew :app:installDebug\n```\n\n**Troubleshooting**\n\n| Symptom | Fix |\n|---|---|\n| `adb: command not found` | `export PATH=\"$ANDROID_HOME/platform-tools:$PATH\"` in your shell profile |\n| `INSTALL_FAILED_NO_MATCHING_ABIS` | APK didn't include `arm64-v8a`. Verify with `unzip -l app/build/outputs/apk/debug/app-debug.apk \\| grep arm64-v8a` |\n| `INSTALL_FAILED_USER_RESTRICTED` | Disable **Verify apps over USB** in Developer options |\n| App crashes on launch | `adb logcat AndroidRuntime:E *:S` for stack trace |\n| Themed monochrome icon on Android 13+ | Expected — placeholder icon; final design lands Phase 6 |\n\n---\n\n## Configuration\n\nv1 has effectively zero configuration. The model artifact downloads on first launch over Wi-Fi (3.66 GB) into `Context.filesDir/models/`. A cheap presence + size probe resolves the artifact state without hashing the multi-GB file on the UI thread; a full-size artifact is then SHA-256-verified off-thread before readiness flips to `Ready`, so a checksum-corrupt full-size file falls back to `Loading` rather than a false `Ready` (`AppContainer.probeModelReadiness`). The engine itself loads lazily on the first inference, not proactively, because proactive pre-warm regressed into a startup GPU-init crash ([ADR-012](docs/adrs/ADR-012-gpu-inference-performance-gaps.md)). Persona default is set during onboarding and changeable from settings. Pattern analysis runs periodically — every 3 completed entries ([ADR-014](docs/adrs/ADR-014-foreground-background-split-and-periodic-pattern-analysis.md)) — with a per-pattern callout cooldown of 3 ([ADR-016](docs/adrs/ADR-016-pattern-callout-cooldown-per-pattern.md)), hardcoded for v1. No env vars, no `.env` file, no remote-config layer — adding any of those is a P0 violation per [ADR-001 §Q7](docs/adrs/ADR-001-stack-and-build-infra.md).\n\n---\n\n## Security \u0026 Privacy\n\nPrivacy is the differentiator, not a side feature.\n\n- **Zero outbound network calls during normal operation.** The model download (one-time, Wi-Fi, Hugging Face) is the sole network event. Verified with `tcpdump`; the proof clip is part of the demo video.\n- **Audio bytes discarded after inference.** Transcription persists as text (the `entry_text` substrate); raw audio never lands on disk as product data.\n- **No analytics, telemetry, crash beacons, remote config, CDN fonts.** Crash logs are local; user can export from settings.\n- **Network enforcement is code, not vibes.** A `NetworkGate` abstraction owns the only HTTP path; default state is `SEALED`, `OPEN` only during model download. Direct `OkHttpClient` / `URL.openConnection` construction outside `NetworkGate` is forbidden and grep-checked in CI. See [ADR-001 §Q7](docs/adrs/ADR-001-stack-and-build-infra.md).\n- **No proactive crisis triage.** If the user explicitly asks for self-harm help, a static local message points to local emergency services. No diagnosis, no network call.\n\nContributors: do not introduce dependencies that pull in Firebase, Crashlytics, Segment, Mixpanel, or any analytics SaaS. Do not add a fonts CDN. Do not call out to a cloud LLM as a fallback. Any of these invalidates the entire submission.\n\n---\n\n## How to Contribute\n\nPRs are not accepted through the submission deadline (2026-05-24). Issues are welcome — use the GitHub issue tracker. Post-submission, see [`AGENTS.md`](AGENTS.md) and [`backlog.md`](docs/backlog.md) for the contribution surface.\n\nBranches and commits follow [`AGENTS.md`](AGENTS.md) and the repo conventions: atomic, GPG-signed, a `Generated-by:` footer on AI-authored commits (e.g. `Generated-by: claude-opus-4-7`), Conventional Commits, never on `main`.\n\n---\n\n## What's Next\n\nv1 ships 2026-05-24. Deferred features live in [`backlog.md`](docs/backlog.md) — v1.5 / v2 / STT-conditional, with explicit unblock-conditions per entry. No \"coming soon\" handwaving.\n\n---\n\n## Known Limitations\n\nWhat v1 actually does, stated straight.\n\n- **Embeddings power Vocab Drift only — no semantic search.** EmbeddingGemma 300M embeds each entry by its **tone word** (the felt quality), and the `VOCAB_FREQUENCY` cluster mints on the demo corpus — `Drained Vocab Frequency` is a live active pattern, verified on-device 2026-05-24. The earlier ranked content-retrieval path (`RetrievalRepo` — keyword + tag + recency + cosine) was **cut** when the embedding axis moved to the tone word: a content query can't score against a feeling vector, and it was never wired to a live surface. Semantic *search* across entries is not a v1 feature; embeddings exist to surface tone clustering, nothing more.\n- **Voice captures cap at 30 s.** `AudioCapture` emits one final chunk at 30 s; the \u003e30 s multi-chunk path is deferred ([`backlog.md`](docs/backlog.md) → `multi-chunk-foreground`). An audio cue at ~28 s warns before the cap fires.\n- **First inference is cold (~15 s).** The engine loads lazily on the first capture — proactive pre-warm was reverted after it regressed into a startup GPU-init crash ([ADR-012](docs/adrs/ADR-012-gpu-inference-performance-gaps.md)). Subsequent calls run ~7–11 s on E4B GPU; a full background 3-lens extraction is ~44 s/entry.\n\n---\n\n## License\n\n[Polyform Shield 1.0.0](LICENSE) + Supplemental Terms. Source-available, not open-source: read it, run it, modify it for personal or internal use. Don't sell it, don't ship a paid product on top of it, don't use it to compete with Vestige itself. The full grant and exceptions are in [LICENSE](LICENSE) — that is the legally-binding version; this paragraph is just the plain-English flavor.\n\n---\n\n## Acknowledgements\n\n- Google's **Gemma team** for the E4B model and the native audio modality that made this entire concept tractable on a phone.\n- The **LiteRT-LM team** ([`google-ai-edge/LiteRT-LM`](https://github.com/google-ai-edge/LiteRT-LM)) for the Android SDK that lets Kotlin code run a multimodal LLM without writing JNI by hand.\n- **ObjectBox** for an embedded DB that does not require an SQL ceremony.\n- **Hugging Face / `litert-community`** for hosting the pre-converted [`gemma-4-E4B-it-litert-lm`](https://huggingface.co/litert-community/gemma-4-E4B-it-litert-lm) artifact.\n- The **Polyform Project** for licenses that admit not every project is MIT-shaped.\n- **[DEV](https://dev.to) (dev.to)** for hosting the Gemma 4 Challenge — the venue this whole build was aimed at, and a community that rewards shipping over hand-waving.\n- **[Major League Hacking (MLH)](https://mlh.io)** for backing the challenge and the broader hackathon community that makes deadlines like this one fun instead of just terrifying.\n\n---\n\n## Author\n\n[Ashley Childress](https://github.com/anchildress1) ([@anchildress1](https://github.com/anchildress1)). Vestige is an Android side-build aimed at the Gemma 4 Challenge \"Build with Gemma 4\" prize. The brand voice and product opinions are entirely intentional.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanchildress1%2Fvestige","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanchildress1%2Fvestige","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanchildress1%2Fvestige/lists"}