Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4adcf37
docs(spec): life-capture layer design
jwalinsshah Apr 22, 2026
36eb444
docs(spec): cut Slack from life-capture v1, defer to v1.1
jwalinsshah Apr 22, 2026
f166a7b
docs(plans): Track 1 ship pipeline + Life-Capture #1 foundation
jwalinsshah Apr 22, 2026
e5b8c5c
fix(install): resolver function + reachability retry + smoke test
jwalinsshah Apr 22, 2026
e160e58
docs(plans): incorporate Onyx insights — ACL on items, sync_state tab…
jwalinsshah Apr 22, 2026
d7f2284
docs(plans): drop ubuntu from CI matrix in Track 1; preserve resolver…
jwalinsshah Apr 22, 2026
78fb199
docs(plans): add F15-F17 — curated MEMORY.md/USER.md layer
jwalinsshah Apr 22, 2026
0cdf247
feat(life_capture): module skeleton + sqlite-vec dep
jwalinsshah Apr 22, 2026
1d45ffb
feat(life_capture): core types — Item, Source, Person, Query, Hit, In…
jwalinsshah Apr 22, 2026
da09f01
feat(life_capture): PII redaction (email, phone, SSN, CC)
jwalinsshah Apr 22, 2026
a938f90
feat(life_capture): strip quoted replies from email bodies
jwalinsshah Apr 22, 2026
24d8264
feat(life_capture): SQLite schema + migration loader (rusqlite)
jwalinsshah Apr 22, 2026
ae57f28
feat(life_capture): Embedder trait + HostedEmbedder (OpenAI-compatible)
jwalinsshah Apr 22, 2026
f0441b5
fix(life_capture): make country code prefix fully optional in PHONE r…
jwalinsshah Apr 22, 2026
37da5e1
feat(curated_memory): MemoryStore for MEMORY.md + USER.md (atomic wri…
jwalinsshah Apr 22, 2026
5f97a00
feat(life_capture): sqlite-vec extension loading + 1536d item_vectors…
jwalinsshah Apr 22, 2026
be8e6a7
feat(life_capture): IndexWriter — upsert items with (source, external…
jwalinsshah Apr 22, 2026
2b11731
feat(life_capture): IndexReader keyword + vector search
jwalinsshah Apr 22, 2026
a57ff1c
feat(life_capture): hybrid_search combining vector + keyword + recency
jwalinsshah Apr 22, 2026
0f9d9b1
feat(life_capture): controller schemas + RPC handlers (get_stats, sea…
jwalinsshah Apr 22, 2026
35eac19
test(life_capture): end-to-end (redact → quote-strip → embed → upsert…
jwalinsshah Apr 22, 2026
3ae04ff
feat(life_capture): wire PersonalIndex + Embedder into core startup
jwalinsshah Apr 22, 2026
13b3276
feat(curated_memory): snapshot_pair() — frozen capture for prompt inj…
jwalinsshah Apr 22, 2026
89797c0
feat(curated_memory): expose memory_curated.{read,add,replace,remove}…
jwalinsshah Apr 22, 2026
1019693
fix(life_capture, curated_memory): address second-opinion review find…
jwalinsshah Apr 22, 2026
2efab6a
style(life_capture, curated_memory): apply rustfmt + finish curated_m…
jwalinsshah Apr 22, 2026
b10b953
fix(life_capture, curated_memory): address CodeRabbit review findings
jwalinsshah Apr 23, 2026
0d9caa9
feat(curated_memory): wire snapshot_pair() into agent prompt builders
jwalinsshah Apr 23, 2026
b3ba61c
test(life_capture): retrieval eval fixture + runner (top-K containment)
jwalinsshah Apr 23, 2026
65a4e8a
feat(life_capture): r2d2 connection pool for concurrent WAL readers
jwalinsshah Apr 23, 2026
be1112f
feat(life_capture): add ingest controller; wire iMessage scanner to it
jwalinsshah Apr 23, 2026
37d6b6b
feat(people): contact resolution + scoring module (A5)
jwalinsshah Apr 23, 2026
71260be
fix(people): wire address book resolver + migrate to CNContactStore (…
jwalinsshah Apr 23, 2026
b9c3cd6
fix(test): add missing curated_snapshot field to ParentExecutionConte…
jwalinsshah Apr 23, 2026
d7772f3
style(people): apply rustfmt
jwalinsshah Apr 23, 2026
d93cbd4
Merge remote-tracking branch 'upstream/main' into feat/a5-people
jwalinsshah Apr 23, 2026
02a9aba
fix(clippy): remove PersonId::to_string shadow + derive Default on we…
jwalinsshah Apr 23, 2026
6bbcdc3
merge upstream/main into feat/a5-people
jwalinsshah May 4, 2026
45bcd9a
merge upstream/main into feat/a5-people
jwalinsshah May 4, 2026
f01117b
fix(agent-prompts): add curated_snapshot to PromptContext and test fi…
jwalinsshah May 4, 2026
c0ba19d
style(rust): format curated_memory and eventkit modules
jwalinsshah May 4, 2026
bd55b19
fix(prompts): borrow curated_snapshot from PromptContext
jwalinsshah May 4, 2026
637b5de
fix(agent-prompts): decouple curated snapshot type and repair merge d…
jwalinsshah May 4, 2026
9dc3bd8
fix(agent): wire curated_snapshot on all ParentExecutionContext literals
jwalinsshah May 4, 2026
7170677
fix(people): address registry and prompt review feedback
jwalinsshah May 5, 2026
3a2511e
fix(people): expose people module
jwalinsshah May 5, 2026
08fafe7
fix(people): apply rustfmt wrapping
jwalinsshah May 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
573 changes: 521 additions & 52 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ tokio-util = { version = "0.7", features = ["rt"] }
tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] }
futures = "0.3"
rusqlite = { version = "0.37", features = ["bundled"] }
r2d2 = "0.8"
r2d2_sqlite = "0.31"
sqlite-vec = "0.1"
chrono = { version = "0.4", features = ["serde"] }
iana-time-zone = "0.1"
cron = "0.12"
Expand Down Expand Up @@ -142,12 +145,19 @@ wacore = { version = "0.5", optional = true, default-features = false }

[target.'cfg(target_os = "macos")'.dependencies]
whisper-rs = { version = "0.16", features = ["metal"] }
# Contacts framework bindings for address book seeding (A5).
# Version matched to objc2-event-kit = "0.3.2" used by A7.
objc2 = "0.6"
objc2-foundation = { version = "0.3", features = ["NSArray", "NSError", "NSObject", "NSString", "NSPredicate"] }
objc2-contacts = { version = "0.3.2", features = ["CNContact", "CNContactFetchRequest", "CNContactStore", "CNLabeledValue", "CNPhoneNumber"] }
block2 = "0.6"

[target.'cfg(target_os = "linux")'.dependencies]
landlock = { version = "0.4", optional = true }
rppal = { version = "0.22", optional = true }

[dev-dependencies]
httpmock = "0.7"

[features]
sandbox-landlock = ["dep:landlock"]
Expand Down
33 changes: 22 additions & 11 deletions app/src-tauri/src/imessage_scanner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,23 +417,26 @@ async fn ingest_group(account_id: &str, key: &str, transcript: String) -> anyhow
let (chat_id, day) = key.split_once(':').unwrap_or((key, ""));
let url = crate::core_rpc::core_rpc_url_value();

// Use anchor timestamp = start of the chat-day in UTC so re-ingests for the
// same day map to a stable `ts` field. Scanner's cursor + PersonalIndex's
// upsert-by-(source, external_id) together guarantee no duplicates.
let ts = parse_day_to_unix(day).unwrap_or_else(|| chrono::Utc::now().timestamp());

let body = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "openhuman.memory_doc_ingest",
"method": "openhuman.life_capture_ingest",
"params": {
"namespace": format!("imessage:{}", account_id),
"key": key,
"title": format!("Messages — {} — {}", chat_id, day),
"content": transcript,
"source_type": "imessage",
"tags": ["chat", "imessage"],
"source": "imessage",
"external_id": key,
"ts": ts,
"subject": format!("Messages — {} — {}", chat_id, day),
"text": transcript,
"metadata": {
"chat_identifier": chat_id,
"day": day,
"source": "imessage"
},
"category": "chat"
"account_id": account_id
}
}
});

Expand All @@ -444,10 +447,18 @@ async fn ingest_group(account_id: &str, key: &str, transcript: String) -> anyhow
anyhow::bail!("core rpc {}: {}", res.status(), res.text().await?);
}

log::info!("[imessage] memory upsert ok key={}", key);
log::info!("[imessage] life_capture ingest ok key={}", key);
Ok(())
}

/// Parse a "YYYY-MM-DD" day string to a unix-seconds timestamp at 00:00 UTC.
/// Returns None for malformed input; caller falls back to wall-clock time.
#[cfg(target_os = "macos")]
fn parse_day_to_unix(day: &str) -> Option<i64> {
let d = chrono::NaiveDate::parse_from_str(day, "%Y-%m-%d").ok()?;
Some(d.and_hms_opt(0, 0, 0)?.and_utc().timestamp())
}

// Non-macOS stub so the rest of the app compiles unchanged.
#[cfg(not(target_os = "macos"))]
pub struct ScannerRegistry;
Expand Down
11 changes: 10 additions & 1 deletion docs/superpowers/plans/2026-04-21-imessage-live-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,16 @@ Prod path wraps: `HttpDeps { base_url }` implements `TickDeps` with existing JSO
- [ ] Step 3: Move tick body 129-209 into `run_single_tick`. Green.
- [ ] Step 4: Write `HttpDeps` wrapping existing gate+ingest fns. Use in `run_scanner`.
- [ ] Step 5: Existing 9 unit tests + 2 ignored still green.
- [ ] Step 6: Write ignored live-sidecar test. Needs sidecar binary staged; use `cargo build --bin openhuman` at root first.
- [x] Step 6: Ingest bridge + ignored e2e test. Scanner now POSTs to
`openhuman.life_capture_ingest` (bridging into `PersonalIndex` via
`IndexWriter::upsert` — idempotent by `(source, external_id)`) instead
of `openhuman.memory_doc_ingest`. Verification surface is
`openhuman.life_capture_search`, not a raw `memory.db` query. Test at
`tests/imessage_ingest_e2e.rs` boots `build_core_http_router` in-process
with a deterministic embedder and asserts: (a) first ingest inserts,
(b) search surfaces the item with `source=="imessage"`, (c) re-ingest
with same external_id sets `replaced=true` and leaves
`get_stats.total_items` unchanged (process-once guarantee).
- [ ] Step 7: Update `docs/superpowers/runbooks/imessage-verification.md` with harness commands.
- [ ] Step 8: Run `cargo check` + `cargo fmt` in app/src-tauri.

Expand Down
Loading
Loading