-
Notifications
You must be signed in to change notification settings - Fork 0
feat: daily narrative memory tables + wagl day CLI (#120) #128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9ab3418
c3b141a
ac863a8
0ec67ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -495,6 +495,7 @@ impl MemoryDb { | |
| invalidate_reason TEXT,\ | ||
| created_at INTEGER NOT NULL\ | ||
| );\ | ||
| CREATE INDEX IF NOT EXISTS idx_daily_entries_date ON daily_entries (date);\ | ||
| CREATE TABLE IF NOT EXISTS daily_memories (\ | ||
| date TEXT,\ | ||
| memory_id TEXT,\ | ||
|
|
@@ -811,11 +812,21 @@ impl MemoryDb { | |
| } | ||
| } | ||
|
|
||
| // Preserve provenance from existing row if incoming does not supply it. | ||
| // This prevents idempotent writes from silently clearing previously captured provenance. | ||
| let environment = item.environment.clone().or(existing.environment.clone()); | ||
| let origin_domain = item | ||
| .origin_domain | ||
| .clone() | ||
| .or(existing.origin_domain.clone()); | ||
|
|
||
| let merged = MemoryItem { | ||
| id: existing.id, | ||
| created_at: existing.created_at, | ||
| tags, | ||
| files, | ||
| environment, | ||
| origin_domain, | ||
| ..item.clone() | ||
| }; | ||
|
|
||
|
|
@@ -979,16 +990,14 @@ impl MemoryDb { | |
| .conn | ||
| .query( | ||
| &format!( | ||
| "SELECT id, mem_type, content, environment, origin_domain, tags, created_at, \ | ||
| salience, primary_emotion, secondary_emotions, d_score, i_score, ev, files, actionable \ | ||
| FROM memory_items \ | ||
| "SELECT {} FROM memory_items \ | ||
| WHERE created_at >= ?1 \ | ||
| AND d_score IS NOT NULL \ | ||
| AND ABS(d_score) >= ?2 \ | ||
| AND {} \ | ||
| ORDER BY ABS(d_score) DESC, created_at DESC \ | ||
| LIMIT ?3", | ||
| actionable_filter | ||
| MEMORY_ITEM_SELECT, actionable_filter | ||
| ), | ||
| (cutoff.as_str(), threshold_f64, limit_i64), | ||
| ) | ||
|
|
@@ -1022,9 +1031,7 @@ impl MemoryDb { | |
| .conn | ||
| .query( | ||
| &format!( | ||
| "SELECT id, mem_type, content, environment, origin_domain, tags, created_at, \ | ||
| salience, primary_emotion, secondary_emotions, d_score, i_score, ev, files, actionable \ | ||
| FROM memory_items \ | ||
| "SELECT {} FROM memory_items \ | ||
| WHERE {} AND ( | ||
| LOWER(mem_type) LIKE '%task%' \ | ||
| OR LOWER(mem_type) LIKE '%open_loop%' \ | ||
|
|
@@ -1039,7 +1046,7 @@ impl MemoryDb { | |
| ) | ||
| ORDER BY created_at DESC \ | ||
| LIMIT ?1", | ||
| actionable_filter | ||
| MEMORY_ITEM_SELECT, actionable_filter | ||
| ), | ||
| [limit_i64], | ||
| ) | ||
|
|
@@ -1725,6 +1732,99 @@ impl MemoryDb { | |
| Ok(()) | ||
| } | ||
|
|
||
| /// Return all daily entries (including invalidated) across all dates, for bundle export. | ||
| pub async fn all_daily_entries(&self) -> anyhow::Result<Vec<DailyEntry>> { | ||
| let mut rows = self | ||
| .conn | ||
| .query( | ||
| "SELECT id, date, context, content, invalidated, invalidate_reason, created_at FROM daily_entries ORDER BY date ASC, created_at ASC", | ||
| (), | ||
| ) | ||
| .await | ||
| .context("all daily entries")?; | ||
|
|
||
| let mut out = Vec::new(); | ||
| while let Some(row) = rows.next().await? { | ||
| let invalidated_i: i64 = row.get(4)?; | ||
| out.push(DailyEntry { | ||
| id: row.get(0)?, | ||
| date: row.get(1)?, | ||
| context: row.get(2)?, | ||
| content: row.get(3)?, | ||
| invalidated: invalidated_i != 0, | ||
| invalidate_reason: row.get(5)?, | ||
| created_at: row.get(6)?, | ||
| }); | ||
| } | ||
| Ok(out) | ||
| } | ||
|
|
||
| /// Return all daily summaries across all dates, for bundle export. | ||
| pub async fn all_daily_summaries(&self) -> anyhow::Result<Vec<DailySummary>> { | ||
| let mut rows = self | ||
| .conn | ||
| .query( | ||
| "SELECT date, content, summary, updated_at FROM daily ORDER BY date ASC", | ||
| (), | ||
| ) | ||
| .await | ||
| .context("all daily summaries")?; | ||
|
|
||
| let mut out = Vec::new(); | ||
| while let Some(row) = rows.next().await? { | ||
| out.push(DailySummary { | ||
| date: row.get(0)?, | ||
| content: row.get(1)?, | ||
| summary: row.get(2)?, | ||
| updated_at: row.get(3)?, | ||
| }); | ||
| } | ||
| Ok(out) | ||
| } | ||
|
|
||
| /// Upsert a daily entry during bundle import (idempotent by id). | ||
| pub async fn upsert_daily_entry(&self, entry: &DailyEntry) -> anyhow::Result<()> { | ||
| let invalidated_i: i64 = if entry.invalidated { 1 } else { 0 }; | ||
| self.conn | ||
| .execute( | ||
| "INSERT OR IGNORE INTO daily_entries (id, date, context, content, invalidated, invalidate_reason, created_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| params![ | ||
| entry.id.clone(), | ||
| entry.date.clone(), | ||
| entry.context.clone(), | ||
| entry.content.clone(), | ||
| invalidated_i, | ||
| entry.invalidate_reason.clone(), | ||
| entry.created_at | ||
| ], | ||
| ) | ||
| .await | ||
| .context("upsert daily entry")?; | ||
| // Refresh the summary so it stays consistent with the entries. | ||
| self.refresh_daily_summary(&entry.date).await?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Upsert a daily summary during bundle import (idempotent by date). | ||
| /// Only restores the hand-written `summary` field; the `content` field is | ||
| /// regenerated by `refresh_daily_summary` when entries are imported, so we | ||
| /// only fill it in here when there are no entries to drive the refresh. | ||
| pub async fn upsert_daily_summary(&self, summary: &DailySummary) -> anyhow::Result<()> { | ||
| self.conn | ||
| .execute( | ||
| "INSERT INTO daily (date, content, summary, updated_at) VALUES (?1, ?2, ?3, ?4) ON CONFLICT(date) DO UPDATE SET summary = excluded.summary, updated_at = excluded.updated_at", | ||
| params![ | ||
| summary.date.clone(), | ||
| summary.content.clone(), | ||
| summary.summary.clone(), | ||
| summary.updated_at | ||
| ], | ||
| ) | ||
| .await | ||
| .context("upsert daily summary")?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| pub async fn insert_daily_entry( | ||
| &self, | ||
| date: &str, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.