Skip to content
Open
Changes from 1 commit
Commits
Show all changes
71 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
db85d70
feat(life_capture): composio ingest bridge for Gmail + Calendar (A2)
jwalinsshah Apr 23, 2026
37d6b6b
feat(people): contact resolution + scoring module (A5)
jwalinsshah Apr 23, 2026
87e468a
feat(eventkit): Calendar read + Reminders write bridge (A7)
jwalinsshah Apr 23, 2026
71260be
fix(people): wire address book resolver + migrate to CNContactStore (…
jwalinsshah Apr 23, 2026
b9c4233
chore(shell): add macOS permission usage strings for Contacts, Calend…
jwalinsshah Apr 23, 2026
6582bfc
fix(eventkit): cache EKEventStore + fix block lifetime + dedupe corre…
jwalinsshah Apr 23, 2026
53dd7f6
Merge A2: Composio→life_capture bridge for Gmail + Calendar
jwalinsshah Apr 23, 2026
48df662
Merge A5: people resolver + scorer + CNContactStore
jwalinsshah Apr 23, 2026
9c48626
Merge A7: EventKit Calendar read + Reminders write
jwalinsshah Apr 23, 2026
82b739c
refactor(agent/prompts): extract tests from mod.rs to sibling
jwalinsshah Apr 23, 2026
b946aff
refactor(channels/telegram): extract tests to sibling file
jwalinsshah Apr 23, 2026
0a67c89
fix(test): add missing curated_snapshot field to ParentExecutionConte…
jwalinsshah Apr 23, 2026
2452a1b
style(refactor): apply rustfmt to extracted test siblings
jwalinsshah Apr 23, 2026
0391899
style: cargo fmt --all
jwalinsshah Apr 23, 2026
af8860a
fix(clippy): remove PersonId::to_string shadow + derive Default on we…
jwalinsshah Apr 23, 2026
f969703
Merge remote-tracking branch 'upstream/main' into test/imessage-live-e2e
jwalinsshah Apr 23, 2026
9ec6494
fix(eventkit): redact PII from logs + fail closed on cache errors
jwalinsshah Apr 24, 2026
e6184d1
fix(life_capture): redact external_id from composio ingest logs
jwalinsshah Apr 24, 2026
6b301f2
fix(life_capture): derive embedder dim from model config instead of h…
jwalinsshah Apr 24, 2026
6246f37
fix(life_capture): pre-embed before item upsert to avoid partial-fail…
jwalinsshah Apr 24, 2026
5f3e9b7
fix(life_capture): align metadata schema field type to TypeSchema::Json
jwalinsshah Apr 24, 2026
c00d282
fix(people): resolve_or_create returns (PersonId, was_created) tuple
jwalinsshah Apr 24, 2026
4a98c26
fix(people): use unchecked_transaction + add debug logging in migrations
jwalinsshah Apr 24, 2026
c3b9689
fix(eventkit): serialize runtime tests on env var lock + assert bad-p…
jwalinsshah Apr 24, 2026
d8d9a82
fix(tests): fail fast if life-capture runtime already initialized in …
jwalinsshah Apr 24, 2026
9f9a096
fix(curated_memory): add canonical all_controller_schemas/all_registe…
jwalinsshah Apr 24, 2026
26664ca
test(life_capture): serialize iMessage e2e tests on shared OnceCell r…
jwalinsshah Apr 26, 2026
c03e59a
feat(examples): real iMessage demo — ingest chat.db, FTS5 search
jwalinsshah Apr 26, 2026
1ff30d1
merge upstream/main into test/imessage-live-e2e
jwalinsshah May 4, 2026
ca0faa4
merge upstream/main into test/imessage-live-e2e
jwalinsshah May 4, 2026
86c1b65
fix(agent-prompts): add curated_snapshot to PromptContext and test fi…
jwalinsshah May 4, 2026
c61c415
style(rust): format curated_memory and eventkit modules
jwalinsshah May 4, 2026
c241c9c
ci: retrigger checks after PR checklist update
jwalinsshah May 4, 2026
e7406c5
fix(prompts): borrow curated_snapshot from PromptContext
jwalinsshah May 4, 2026
309a4cb
fix(agent-prompts): decouple curated snapshot type and repair merge d…
jwalinsshah May 4, 2026
a474cae
fix(life-capture): add job_name to CronJobTriggered test event
jwalinsshah May 4, 2026
0b9ef6a
fix(agent): wire curated snapshot into parent contexts
jwalinsshah May 5, 2026
4750108
fix(agent): initialize curated snapshot in test contexts
jwalinsshah May 5, 2026
37ae8da
fix(agent): initialize calendar test curated snapshot
jwalinsshah May 5, 2026
f4c0b01
fix(agent): initialize public harness test curated snapshot
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
Prev Previous commit
Next Next commit
feat(examples): real iMessage demo — ingest chat.db, FTS5 search
Reads last N messages from $CHAT_DB (default ~/Library/Messages/chat.db),
ingests into an in-memory PersonalIndex via IndexWriter::upsert, then
runs IndexReader::keyword_search over a hardcoded query list. No
embeddings — pure FTS5/bm25 over real personal data.

Use to benchmark ingest throughput on a real corpus and to sanity-check
that keyword search ranking + snippet generation behave on real text.
On a 5000-message slice: ingest at ~47k items/s, search returns
3 hits with snippets in sub-millisecond time.

Run: cargo run --example real_imessage_demo --release
Requires Full Disk Access for the running terminal.
  • Loading branch information
jwalinsshah committed Apr 26, 2026
commit c03e59a26dfb6d6ba65c67136cd1ff73ca56340f
144 changes: 144 additions & 0 deletions examples/real_imessage_demo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//! REAL DATA demo: ingest a slice of ~/Library/Messages/chat.db into an
//! in-memory life_capture PersonalIndex and run keyword searches.
//!
//! No embeddings (skips OpenAI) — just SQLite FTS5 over real iMessages, to
//! prove the loop works on real personal data.
//!
//! Run with: `cargo run --example real_imessage_demo --release`
//! (Requires Full Disk Access for the running terminal.)

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use openhuman_core::openhuman::life_capture::index::{IndexReader, IndexWriter, PersonalIndex};
use openhuman_core::openhuman::life_capture::types::{Item, Person, Source};
use rusqlite::{Connection, OpenFlags};

const N_MESSAGES: usize = 5000;
const QUERIES: &[&str] = &[
"burger", "flight", "meeting", "dinner", "love", "uber", "thanks",
];

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let chat_db = std::env::var("CHAT_DB").unwrap_or_else(|_| {
let home = std::env::var("HOME").expect("HOME unset");
format!("{home}/Library/Messages/chat.db")
});

println!("→ opening chat.db at {chat_db}");
let conn = Connection::open_with_flags(&chat_db, OpenFlags::SQLITE_OPEN_READ_ONLY)
.with_context(|| format!("open chat.db at {chat_db}"))?;

println!("→ pulling last {N_MESSAGES} messages with text…");
let mut stmt = conn.prepare(
"SELECT m.ROWID, \
COALESCE(m.text, '') as text, \
m.date, \
m.is_from_me, \
COALESCE(h.id, '') as handle \
FROM message m \
LEFT JOIN handle h ON h.ROWID = m.handle_id \
WHERE m.text IS NOT NULL AND length(m.text) > 0 \
ORDER BY m.date DESC \
LIMIT ?1",
)?;

// Apple stores dates in nanoseconds since 2001-01-01 UTC.
const APPLE_EPOCH_OFFSET: i64 = 978_307_200; // unix seconds for 2001-01-01
let rows = stmt.query_map([N_MESSAGES as i64], |row| {
let rowid: i64 = row.get(0)?;
let text: String = row.get(1)?;
let raw_date: i64 = row.get(2)?;
let is_from_me: i64 = row.get(3)?;
let handle: String = row.get(4)?;
let unix_secs = APPLE_EPOCH_OFFSET + (raw_date / 1_000_000_000);
Ok((rowid, text, unix_secs, is_from_me != 0, handle))
})?;

let mut items = Vec::with_capacity(N_MESSAGES);
for r in rows {
let (rowid, text, unix_secs, is_from_me, handle) = r?;
let ts = DateTime::<Utc>::from_timestamp(unix_secs, 0)
.unwrap_or_else(|| DateTime::<Utc>::from_timestamp(0, 0).unwrap());
let author = if is_from_me {
None
} else if handle.is_empty() {
None
} else {
Some(Person {
display_name: None,
email: None,
source_id: Some(handle.clone()),
})
};
items.push(Item {
id: uuid::Uuid::new_v4(),
source: Source::IMessage,
external_id: format!("imsg:rowid:{rowid}"),
ts,
author,
subject: None,
text,
metadata: serde_json::json!({"is_from_me": is_from_me, "handle": handle}),
});
}
println!(" pulled {} messages", items.len());

println!("→ opening in-memory PersonalIndex…");
let idx = PersonalIndex::open_in_memory().await?;
let writer = IndexWriter::new(&idx);
let reader = IndexReader::new(&idx);

println!("→ ingesting (FTS5 only, no embeddings)…");
let t0 = std::time::Instant::now();
// Upsert in chunks to keep transaction sizes sane.
for chunk in items.chunks_mut(500) {
writer.upsert(chunk).await?;
}
let elapsed = t0.elapsed();
println!(
" ingested {} items in {:.2}s ({:.0} items/s)",
items.len(),
elapsed.as_secs_f64(),
items.len() as f64 / elapsed.as_secs_f64()
);

println!("\n=== KEYWORD SEARCH RESULTS ON REAL iMESSAGE DATA ===\n");
for q in QUERIES {
let hits = reader.keyword_search(q, 3).await?;
println!("─── query: \"{}\" ({} hits) ───", q, hits.len());
for (i, h) in hits.iter().enumerate() {
let dt = h.item.ts.format("%Y-%m-%d %H:%M");
let from = h
.item
.author
.as_ref()
.and_then(|p| p.source_id.as_deref())
.unwrap_or("me");
let snippet = if h.snippet.is_empty() {
h.item.text.chars().take(120).collect::<String>()
} else {
h.snippet.clone()
};
println!(
" {}. [{}] [{:>20}] (score {:.2}) {}",
i + 1,
dt,
truncate(from, 20),
h.score,
snippet.replace('\n', " ")
);
}
println!();
}

Ok(())
}

fn truncate(s: &str, n: usize) -> String {
if s.chars().count() <= n {
s.to_string()
} else {
s.chars().take(n.saturating_sub(1)).collect::<String>() + "…"
}
}