From 273ff3495bba232575ce7d974bc7209e349e7b6e Mon Sep 17 00:00:00 2001 From: quangdang46 Date: Wed, 3 Jun 2026 14:35:15 +0700 Subject: [PATCH] feat(palace): add ActivityEvent sink on PalaceBuilder (jcode shape) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jcode's `MemoryEventSink = Arc` is the per-call activity stream that drives the TUI info widget's `MemoryActivity` snapshot. The snapshot has 4 pipeline states (search → verify → inject → maintain) and 24 event kinds; jcode publishes transitions at every meaningful point. mempalace has no equivalent on the public API today. The closest is the internal `background.rs` `tracing::info!()` calls, which aren't visible to library consumers and don't carry a typed shape. Add: - `pub struct ActivityEvent { state, detail, timestamp }` - `pub enum ActivityState { Idle, Embedding, SidecarChecking, FoundRelevant, Extracting, Maintaining, ToolAction { action } }` - `PalaceBuilder::activity_sink(sink)` builder method - `Palace::activity_sink` pub field - `Palace::emit_activity(state, detail)` private helper that no-ops when no sink is set This PR is the **API shape** for activity streaming; it does NOT yet wire the emit calls into the `impl MemoryProvider for Palace` methods (that's a follow-up that touches `add_drawer`, `forget`, `search`, `extract_from_transcript`, and the `background.rs` consolidation/lesson-decay loops). The jcode adapter can already use the API as-is to bridge its `MemoryEventSink` — it will receive no events until the follow-up lands, but the type and registration path are stable. This is PR 5/8 in the jcode → mempalace Mode C library migration series (the runtime-wiring half; PRs 1+2+3+4+6+7+8 are the trait- method/field/type half). Co-Authored-By: Claude Opus 4.8 --- crates/core/src/palace.rs | 70 +++++++++++++++++++++++++++++++ crates/core/src/palace/builder.rs | 23 ++++++++++ 2 files changed, 93 insertions(+) diff --git a/crates/core/src/palace.rs b/crates/core/src/palace.rs index 0f31326..b3196bd 100644 --- a/crates/core/src/palace.rs +++ b/crates/core/src/palace.rs @@ -579,6 +579,63 @@ pub struct Palace { pub llm: Option>, /// Session store for lifecycle observations (Phase 0 / bead 0.3). pub sessions: Option>, + /// mp-migration 5/8: optional activity sink. When set, the + /// `Palace` impl fires `ActivityEvent`s at meaningful pipeline + /// points (search start/done, found relevant, tool action, etc). + /// Used by the jcode adapter to drive its `MemoryEventSink`. + pub activity_sink: Option>, +} + +// --------------------------------------------------------------------------- +// ActivityEvent (mp-migration 5/8) +// --------------------------------------------------------------------------- + +/// A single lifecycle event from the palace's per-call pipeline. +/// +/// Mirrors jcode's `MemoryEventKind` + `MemoryState` enums in a +/// single struct. jcode's adapter maps these to its own +/// `ServerEvent::MemoryActivity { activity }` shape. +/// +/// The pipeline progresses as: +/// Idle → Embedding → FoundRelevant → Idle +/// Idle → Extracting → Idle +/// Idle → Maintaining → Idle +/// Idle → ToolAction → Idle (for tool-driven calls) +/// +/// `detail` carries an optional human-readable string (e.g. the +/// query that was searched, the drawer id that was filed, the +/// tag that was applied). +#[derive(Debug, Clone)] +pub struct ActivityEvent { + /// Pipeline state this event represents. + pub state: ActivityState, + /// Optional human-readable detail. + pub detail: Option, + /// Wall-clock timestamp when the event was emitted. + pub timestamp: chrono::DateTime, +} + +/// Pipeline state for an [`ActivityEvent`]. Mirrors jcode's +/// `MemoryState` enum. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ActivityState { + /// No activity. Default / after-pipeline. + Idle, + /// Running embedding search. + Embedding, + /// Sidecar (LLM) checking relevance of candidates. + SidecarChecking, + /// Embedding search found relevant results. + FoundRelevant, + /// Extracting memories from a transcript. + Extracting, + /// Background maintenance (consolidation, lesson decay, etc). + Maintaining, + /// Agent is using a memory tool (remember/recall/etc). + ToolAction { + /// "remember" | "recall" | "search" | "list" | "forget" | "tag" | "link" | "related" + action: &'static str, + }, } impl std::fmt::Debug for Palace { @@ -592,6 +649,19 @@ impl std::fmt::Debug for Palace { // --------------------------------------------------------------------------- impl Palace { + /// Fire an activity event if a sink is registered. mp-migration + /// 5/8. Called from the `impl MemoryProvider for Palace` methods + /// to publish state transitions; no-op when no sink is set. + fn emit_activity(&self, state: ActivityState, detail: Option) { + if let Some(sink) = &self.activity_sink { + sink(ActivityEvent { + state, + detail, + timestamp: chrono::Utc::now(), + }); + } + } + fn derive_drawer_id(content: &str) -> DrawerId { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; diff --git a/crates/core/src/palace/builder.rs b/crates/core/src/palace/builder.rs index 20c59a9..97569b3 100644 --- a/crates/core/src/palace/builder.rs +++ b/crates/core/src/palace/builder.rs @@ -84,6 +84,12 @@ pub struct PalaceBuilder { store: Option>, llm: Option>, session_store: Option>, + /// mp-migration 5/8: optional callback fired from + /// `Palace::add_drawer` / `forget` / `search` etc. with an + /// `ActivityEvent`. Used by the jcode adapter to mirror jcode's + /// `MemoryEventSink = Arc` and feed the + /// `MemoryActivity` snapshot the TUI info widget reads. + activity_sink: Option>, } impl std::fmt::Debug for PalaceBuilder { @@ -105,6 +111,7 @@ impl PalaceBuilder { store: None, llm: None, session_store: None, + activity_sink: None, } } @@ -154,6 +161,21 @@ impl PalaceBuilder { self } + /// Set the activity sink (mp-migration 5/8). + /// + /// When set, the [`Palace`] fires an [`super::ActivityEvent`] at + /// every meaningful point in the per-call pipeline (search start, + /// search done, found relevant, sidecar checking, extracting, + /// maintaining, tool action). jcode's adapter plugs this into + /// its `MemoryEventSink = Arc` to drive the + /// TUI info widget. + /// + /// Default: no sink. Calls complete silently. + pub fn activity_sink(mut self, sink: Arc) -> Self { + self.activity_sink = Some(sink); + self + } + /// Open the palace. Validates all required fields and initializes /// storage. Returns an error if config or embedder is missing, or /// if the embedder fails to load. @@ -227,6 +249,7 @@ impl PalaceBuilder { store, llm: self.llm, sessions: self.session_store, + activity_sink: self.activity_sink, }) } }