Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 70 additions & 0 deletions crates/core/src/palace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,63 @@ pub struct Palace {
pub llm: Option<Arc<dyn crate::llm::LlmProvider>>,
/// Session store for lifecycle observations (Phase 0 / bead 0.3).
pub sessions: Option<Arc<crate::session::SessionStore>>,
/// 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<Arc<dyn Fn(ActivityEvent) + Send + Sync>>,
}

// ---------------------------------------------------------------------------
// 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<String>,
/// Wall-clock timestamp when the event was emitted.
pub timestamp: chrono::DateTime<chrono::Utc>,
}

/// 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 {
Expand All @@ -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<String>) {
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};
Expand Down
23 changes: 23 additions & 0 deletions crates/core/src/palace/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ pub struct PalaceBuilder {
store: Option<Arc<dyn super::PalaceStore>>,
llm: Option<Arc<dyn crate::llm::LlmProvider>>,
session_store: Option<Arc<crate::session::SessionStore>>,
/// 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<dyn Fn(ServerEvent)>` and feed the
/// `MemoryActivity` snapshot the TUI info widget reads.
activity_sink: Option<Arc<dyn Fn(super::ActivityEvent) + Send + Sync>>,
}

impl std::fmt::Debug for PalaceBuilder {
Expand All @@ -105,6 +111,7 @@ impl PalaceBuilder {
store: None,
llm: None,
session_store: None,
activity_sink: None,
}
}

Expand Down Expand Up @@ -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<dyn Fn(ServerEvent)>` to drive the
/// TUI info widget.
///
/// Default: no sink. Calls complete silently.
pub fn activity_sink(mut self, sink: Arc<dyn Fn(super::ActivityEvent) + Send + Sync>) -> 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.
Expand Down Expand Up @@ -227,6 +249,7 @@ impl PalaceBuilder {
store,
llm: self.llm,
sessions: self.session_store,
activity_sink: self.activity_sink,
})
}
}
Expand Down
Loading