Archived
feat: Engram sync layer — swarm memory protocol, peer delta sync, distributed activation
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/// Swarm routes — distributed activation across the peer network.
|
||||
///
|
||||
/// POST /swarm/activate — SwarmActivateRequest → SwarmActivateResponse
|
||||
/// GET /swarm/status — peer health check and last sync times
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
Json,
|
||||
};
|
||||
use engram_sync::{
|
||||
client::SyncClient, merge_activation_results, Peer, PeerActivationResult, PeerStatus,
|
||||
SerializableActivatedNode, SwarmActivateRequest, SwarmActivateResponse,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
/// POST /swarm/activate
|
||||
///
|
||||
/// Runs spreading activation locally, then (if include_peers=true) fans out
|
||||
/// to all trusted peers in parallel and returns merged results.
|
||||
pub async fn swarm_activate(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<SwarmActivateRequest>,
|
||||
) -> Result<Json<SwarmActivateResponse>, StatusCode> {
|
||||
// Step 1: Run local activation. Lock db, compute, drop immediately.
|
||||
let local_results: Vec<SerializableActivatedNode> = {
|
||||
let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let activated = db
|
||||
.activate(&req.seeds, &req.query_embedding, req.max_depth, req.limit)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
activated.into_iter().map(Into::into).collect()
|
||||
// db lock released here
|
||||
};
|
||||
|
||||
// Step 2: Snapshot peer list (lock, read, drop).
|
||||
let (our_id, trusted_peers): (Uuid, Vec<Peer>) = {
|
||||
let engine = state.sync_engine.lock().await;
|
||||
let id = engine.our_id();
|
||||
let peers = engine.list_peers().iter().filter(|p| p.trusted).cloned().collect();
|
||||
(id, peers)
|
||||
// engine lock released here
|
||||
};
|
||||
|
||||
// Step 3: Fan out to peers — no locks held across these awaits.
|
||||
let mut peer_results: Vec<PeerActivationResult> = Vec::new();
|
||||
|
||||
if req.include_peers {
|
||||
let mut handles = Vec::new();
|
||||
for peer in trusted_peers {
|
||||
let seeds = req.seeds.clone();
|
||||
let embedding = req.query_embedding.clone();
|
||||
let max_depth = req.max_depth;
|
||||
let limit = req.limit;
|
||||
|
||||
handles.push(tokio::spawn(async move {
|
||||
let client = SyncClient::new(peer.clone(), our_id);
|
||||
match client.remote_activate(&seeds, &embedding, max_depth, limit).await {
|
||||
Ok(results) => PeerActivationResult {
|
||||
peer_id: peer.id,
|
||||
peer_name: peer.name,
|
||||
results,
|
||||
error: None,
|
||||
},
|
||||
Err(e) => PeerActivationResult {
|
||||
peer_id: peer.id,
|
||||
peer_name: peer.name,
|
||||
results: Vec::new(),
|
||||
error: Some(e.to_string()),
|
||||
},
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
if let Ok(result) = handle.await {
|
||||
peer_results.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Merge and return.
|
||||
let merged = merge_activation_results(&local_results, &peer_results, req.limit);
|
||||
|
||||
Ok(Json(SwarmActivateResponse {
|
||||
local_results,
|
||||
peer_results,
|
||||
merged,
|
||||
}))
|
||||
}
|
||||
|
||||
/// GET /swarm/status
|
||||
///
|
||||
/// Returns peer list with reachability status and last sync times.
|
||||
pub async fn swarm_status(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<Vec<PeerStatus>>, StatusCode> {
|
||||
// Snapshot peer list and our_id without holding the lock across awaits.
|
||||
let (our_id, peers): (Uuid, Vec<Peer>) = {
|
||||
let engine = state.sync_engine.lock().await;
|
||||
let id = engine.our_id();
|
||||
let peers = engine.list_peers().to_vec();
|
||||
(id, peers)
|
||||
};
|
||||
|
||||
let mut statuses: Vec<PeerStatus> = Vec::new();
|
||||
for peer in peers {
|
||||
use engram_core::types::now_ms;
|
||||
let client = SyncClient::new(peer.clone(), our_id);
|
||||
let reachable = client.pull_delta(now_ms()).await.is_ok();
|
||||
statuses.push(PeerStatus {
|
||||
peer_id: peer.id,
|
||||
peer_name: peer.name,
|
||||
address: peer.address,
|
||||
last_sync_at: peer.last_sync_at,
|
||||
reachable,
|
||||
sync_tiers: peer.sync_tiers,
|
||||
trusted: peer.trusted,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Json(statuses))
|
||||
}
|
||||
Reference in New Issue
Block a user