Requirements — second-brain mobile app v1

At a glance

  • What it is: a Flutter app (iOS first, Android for free) that puts vault retrieval in your pocket — chat/search UI over the v2 retrieval API, degrading gracefully to offline filters-only search against a synced index
  • Status: Draft v1 — Parked
  • Depends on: second-brain retrieval layer v2 (search_vault hybrid contract, read_note, get_vault_stats, --scope), vault-indexer Phase 0
  • Serves: Principle 5 — “Available where I am”, later O-B

1. Background and problem statement

The second-brain stack is desktop-bound: retrieval works only where Ollama, the index, and the vault coexist. Away from the laptop there is no way to ask “what did I decide about the cluster upgrade?” — even though the retrieval layer (v2) already defines exactly the API such a client needs.

Three observations shape the design:

  1. The v2 tool contract is the mobile API. search_vault(query, filters…), read_note(target), get_vault_stats() — a mobile app needs nothing more. The server side is a thin transport wrapper, not new retrieval logic.
  2. The phone doesn’t need Ollama for most value. Hybrid search needs embeddings, but the FR-12 filters-only path (folder + tags + dates, ordered by modified date) requires only SQLite — which runs natively on-device against a synced copy of vault-index.db.
  3. Privacy tiering already exists. Scoped server instances (v2 FR-4x) mean the phone can be pointed at a structurally restricted endpoint — the mobile attack surface never sees more than its scope.

2. Goals and non-goals

Goals

  • G1. Ask the vault questions from a phone: semantic + filtered search with snippets, one tap to full note.
  • G2. Offline degradation: filters-only search and cached note reading with no server reachable.
  • G3. Reuse, don’t reimplement: server-side retrieval stays in common.py; the app consumes it over HTTP.
  • G4. Private by construction: transport via Tailscale (no public endpoint), scope enforced server-side, read-only throughout.
  • G5. One codebase (Flutter/Dart), with iOS-specific vault access isolated behind a platform channel from day one.

Non-goals

  • NG1. No note editing, creation, or sync-back. Read-only, like the MCP server.
  • NG2. No on-device embedding/inference in v1 (Tier 3 — see §10). The architecture must not preclude it.
  • NG3. No public internet exposure of the API; no auth system beyond Tailscale identity in v1.
  • NG4. No chat-with-LLM in v1 — the app surfaces retrieval results, it does not answer questions. (An “open in Claude” share action is in scope; an embedded LLM conversation is not.)
  • NG5. No Obsidian feature emulation (graph view, backlinks UI, plugins).

3. Current state

  • Retrieval logic: common.py pure functions over SQLite + Ollama (v2 Phase 1), no HTTP surface.
  • MCP server: stdio transport only — not consumable from a phone.
  • Index: vault-index.db (files, tags, embeddings), path-keyed, content-hash aware.
  • Vault: Obsidian iOS keeps a copy in iCloud Drive (files may be non-materialized placeholders).
  • Network: homelab reachable via Tailscale from all personal devices.

4. Functional requirements

5. Non-functional requirements

6. Interface specification

POST /search        body: {query?, top_k?, folder?, tags?, modified_after?, modified_before?}
                    → {results: [Result], warnings: [str], degraded: bool}
GET  /note          ?target=<wikilink-or-path>     → {path, content, modified}
GET  /stats         → v2 stats + scope, as JSON
GET  /schema        → {folders: [...], tags: [{tag, count}], file_count, date_range}
GET  /health        → {ok, ollama, index_age_seconds}
GET  /index-delta   ?since=<indexed_at> → {upserts: [...], deletes: [...]}   (v1.1, see R-3)

Result = v2 Result unchanged:
  {wikilink, score: float|null, folder, tags: [str], modified, snippet}

App storage:

StoreContentsNotes
local.db (drift/sqflite)files, tags mirror + profile metadataper-profile namespace
note cachefull note bodiesLRU, 50 MB default, per-profile
secure prefsprofile URLsplatform keychain/keystore

7. Implementation phases

Waterfall-friendly; each phase is releasable (TestFlight from Phase 2).

  1. Phase 1 — API service: FastAPI wrapper over common.py, scope support, degraded-mode envelope, systemd unit + README. Exit: curl fulfills AC-1 end-to-end.
  2. Phase 2 — Online app: profiles, search screen with schema-driven chips, results, note view, share sheet (FR-2x, FR-40–42). Exit: daily-usable on TestFlight over Tailscale.
  3. Phase 3 — Offline: index mirror (whole-file sync), offline filters-only search, note cache with staleness markers (FR-30–33). Exit: airplane-mode walkthrough passes.
  4. Phase 4 — Vault access (iOS): FR-34 platform channel behind a feature flag; delta sync (/index-delta) if not landed in Phase 3.
  5. Phase 5 — Polish & Android pass: Android smoke QA, docs (README: architecture, hook-vs-MCP-vs-API table), CHANGELOG.

8. Testing requirements

9. Acceptance criteria

10. Risks and open questions