memfs_commit presence signal on the bus after memory mutations
One event per memfs write/append/delete, replacing the three bespoke memory_* events nobody consumed. Payload carries op, agent pubkey (seed beside memfs), machine instance (machined-first), paths, head commit, branch — enough for a federated peer to know to fetch. Best-effort: missing identity warns and ships null, never blocks the write. MemoryRepo stays a pure store; grew head_commit_hex/current_branch.
This commit is contained in:
parent
116562a615
commit
8cfa992da2
1 changed files with 86 additions and 30 deletions
|
|
@ -539,6 +539,20 @@ impl MemoryRepo {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Hex hash of the current HEAD commit, if the repo has one.
|
||||
pub fn head_commit_hex(&self) -> Option<String> {
|
||||
let repo = self.open_git().ok()?;
|
||||
let head = repo.head().ok()?;
|
||||
head.peel_to_commit().ok().map(|c| c.id().to_string())
|
||||
}
|
||||
|
||||
/// Branch shorthand HEAD points at ("main", "primary", ...), if any.
|
||||
pub fn current_branch(&self) -> Option<String> {
|
||||
let repo = self.open_git().ok()?;
|
||||
let head = repo.head().ok()?;
|
||||
head.shorthand().map(|s| s.to_string())
|
||||
}
|
||||
|
||||
/// Get the root path of the memory repo.
|
||||
pub fn root(&self) -> &Path {
|
||||
&self.root
|
||||
|
|
@ -626,6 +640,75 @@ pub async fn read_file(path: &Path) -> Result<MemoryFile> {
|
|||
|
||||
// ── Tool Interface ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Fire a `memfs_commit` presence signal after a memfs mutation.
|
||||
///
|
||||
/// This is the federation heartbeat for memory: "I wrote, you should fetch."
|
||||
/// It carries routing metadata only — never the data itself. Peers holding a
|
||||
/// checkout of the same agent map `agent_pubkey` to their local repo and
|
||||
/// fetch the shared remote (FEDERATION.md, memory transport).
|
||||
///
|
||||
/// Emission is best-effort and never fails the write: the mutation already
|
||||
/// succeeded. Missing identity is warned loudly, not fabricated.
|
||||
fn fire_memfs_commit(
|
||||
ctx: &ToolContext,
|
||||
repo: &MemoryRepo,
|
||||
op: &str,
|
||||
paths: &[&str],
|
||||
urgency: f32,
|
||||
) {
|
||||
// A successful tool-path mutation auto-committed; if there is somehow no
|
||||
// head, there is nothing for a peer to fetch — do not signal.
|
||||
let Some(commit) = repo.head_commit_hex() else {
|
||||
tracing::warn!(op, ?paths, "memfs mutation without a head commit — memfs_commit not emitted");
|
||||
return;
|
||||
};
|
||||
let branch = repo.current_branch();
|
||||
|
||||
// Agent identity lives beside the memfs (`agents/{id}/seed/`) — same
|
||||
// convention as reach/consult in tools/agent.rs.
|
||||
let agent_pubkey = ctx
|
||||
.memory_root
|
||||
.as_ref()
|
||||
.and_then(|m| m.parent())
|
||||
.map(|p| p.join("seed"))
|
||||
.and_then(|dir| match crate::core::identity::SeedId::load_or_generate(&dir) {
|
||||
Ok(seed) => Some(seed.public_key_hex()),
|
||||
Err(e) => {
|
||||
tracing::warn!(op, "memfs_commit: agent seed unavailable ({e:#})");
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// The instance is the machine that wrote — machined-first, loud legacy
|
||||
// fallback, never generates.
|
||||
let base = dirs::home_dir().unwrap_or_default().join(".souveraine");
|
||||
let instance = match crate::machined::client::machine_pubkey_with_fallback(&base) {
|
||||
Ok((pk, _source)) => Some(pk),
|
||||
Err(e) => {
|
||||
tracing::warn!(op, "memfs_commit: machine identity unavailable ({e:#})");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
ctx.fire_event(crate::core::nervous::SensorEvent {
|
||||
sensor_name: "memory".into(),
|
||||
timestamp: chrono::Utc::now(),
|
||||
event_type: "memfs_commit".into(),
|
||||
target: paths.first().map(|p| p.to_string()),
|
||||
urgency,
|
||||
payload: Some(serde_json::json!({
|
||||
"op": op,
|
||||
"agent_pubkey": agent_pubkey,
|
||||
"instance": instance,
|
||||
"paths": paths,
|
||||
"commit": commit,
|
||||
"branch": branch,
|
||||
})),
|
||||
seed_id: None,
|
||||
reply_to: None,
|
||||
});
|
||||
}
|
||||
|
||||
/// Execute a memory command, optionally using context for agent identity.
|
||||
///
|
||||
/// When `ctx` is `Some` and carries an `agent_id`, that takes precedence over
|
||||
|
|
@ -668,32 +751,14 @@ pub async fn execute_memory_command_with_context(
|
|||
MemoryCommand::Write { path, content } => {
|
||||
repo.write(path, content).await?;
|
||||
if let Some(c) = ctx {
|
||||
c.fire_event(crate::core::nervous::SensorEvent {
|
||||
sensor_name: "memory".into(),
|
||||
timestamp: chrono::Utc::now(),
|
||||
event_type: "memory_write".into(),
|
||||
target: Some(path.clone()),
|
||||
urgency: 0.1,
|
||||
payload: None,
|
||||
seed_id: None,
|
||||
reply_to: None,
|
||||
});
|
||||
fire_memfs_commit(c, &repo, "write", &[path], 0.1);
|
||||
}
|
||||
Ok(format!("Wrote memory file: {}", path))
|
||||
}
|
||||
MemoryCommand::Append { path, content } => {
|
||||
repo.append(path, content).await?;
|
||||
if let Some(c) = ctx {
|
||||
c.fire_event(crate::core::nervous::SensorEvent {
|
||||
sensor_name: "memory".into(),
|
||||
timestamp: chrono::Utc::now(),
|
||||
event_type: "memory_append".into(),
|
||||
target: Some(path.clone()),
|
||||
urgency: 0.1,
|
||||
payload: None,
|
||||
seed_id: None,
|
||||
reply_to: None,
|
||||
});
|
||||
fire_memfs_commit(c, &repo, "append", &[path], 0.1);
|
||||
}
|
||||
Ok(format!("Appended to memory file: {}", path))
|
||||
}
|
||||
|
|
@ -753,16 +818,7 @@ pub async fn execute_memory_command_with_context(
|
|||
MemoryCommand::Delete { path } => {
|
||||
repo.delete(path).await?;
|
||||
if let Some(c) = ctx {
|
||||
c.fire_event(crate::core::nervous::SensorEvent {
|
||||
sensor_name: "memory".into(),
|
||||
timestamp: chrono::Utc::now(),
|
||||
event_type: "memory_delete".into(),
|
||||
target: Some(path.clone()),
|
||||
urgency: 0.2,
|
||||
payload: None,
|
||||
seed_id: None,
|
||||
reply_to: None,
|
||||
});
|
||||
fire_memfs_commit(c, &repo, "delete", &[path], 0.2);
|
||||
}
|
||||
Ok(format!("Deleted memory file: {}", path))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue