Skip to main content

ipfrs_tensorlogic/remote_reasoning/
mod.rs

1//! Remote Knowledge Retrieval and Distributed Reasoning
2//!
3//! This module provides protocols and interfaces for distributed reasoning
4//! across a network of IPFS nodes. It defines the abstractions needed for:
5//!
6//! - Remote predicate lookup
7//! - Fact discovery from network peers
8//! - Incremental fact loading
9//! - Distributed goal resolution
10//! - Proof assembly from distributed fragments
11//!
12//! # Architecture
13//!
14//! The remote reasoning system is designed to work with ipfrs-network once
15//! integrated. The traits defined here provide the interface that network
16//! implementations will satisfy.
17//!
18//! ## Example
19//!
20//! ```ignore
21//! use ipfrs_tensorlogic::{RemoteKnowledgeProvider, QueryRequest};
22//!
23//! // Once ipfrs-network is integrated:
24//! let provider = NetworkKnowledgeProvider::new(network_client);
25//! let results = provider.query_predicate("parent", vec!["Alice"]).await?;
26//! ```
27
28pub mod session;
29
30pub use session::{
31    DistributedInferenceSession, DistributedReasonerConfig, DistributedReasonerV2,
32    InferenceRequest, InferenceResponse, InferenceResultStream, PartialResult, ReasoningError,
33    RemoteResult, SessionMetrics, SessionStats,
34};
35
36use crate::ir::{KnowledgeBase, Predicate, Rule, Term};
37use crate::proof_storage::{ProofFragment, ProofFragmentRef};
38use crate::reasoning::{Proof, Substitution};
39use async_trait::async_trait;
40use ipfrs_core::Cid;
41use serde::{Deserialize, Serialize};
42use std::collections::{HashMap, HashSet};
43use std::sync::Arc;
44use thiserror::Error;
45
46/// Errors that can occur during remote reasoning
47#[derive(Debug, Error)]
48pub enum RemoteReasoningError {
49    #[error("Network error: {0}")]
50    NetworkError(String),
51
52    #[error("Timeout waiting for remote response")]
53    Timeout,
54
55    #[error("Invalid response from peer: {0}")]
56    InvalidResponse(String),
57
58    #[error("Peer not found: {0}")]
59    PeerNotFound(String),
60
61    #[error("No peers available for query")]
62    NoPeersAvailable,
63
64    #[error("Serialization error: {0}")]
65    SerializationError(String),
66
67    #[error("Remote query failed: {0}")]
68    QueryFailed(String),
69}
70
71/// Query request for remote predicate lookup
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct QueryRequest {
74    /// Predicate name to query
75    pub predicate_name: String,
76
77    /// Ground arguments (constants only)
78    pub ground_args: Vec<String>,
79
80    /// Maximum number of results to return
81    pub max_results: usize,
82
83    /// Query depth limit
84    pub max_depth: usize,
85
86    /// Request ID for tracking
87    pub request_id: String,
88}
89
90/// Query response containing facts from remote peer
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct QueryResponse {
93    /// Request ID this response is for
94    pub request_id: String,
95
96    /// Predicates matching the query
97    pub predicates: Vec<Predicate>,
98
99    /// Rules matching the query
100    pub rules: Vec<Rule>,
101
102    /// Proof fragments (if proofs requested)
103    pub proof_fragments: Vec<ProofFragmentRef>,
104
105    /// Peer ID that responded
106    pub peer_id: String,
107
108    /// Whether more results are available
109    pub has_more: bool,
110
111    /// Continuation token for pagination
112    pub continuation_token: Option<String>,
113}
114
115/// Fact discovery request for network-wide search
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct FactDiscoveryRequest {
118    /// Predicate name to discover
119    pub predicate_name: String,
120
121    /// Optional argument patterns (None = wildcard)
122    pub arg_patterns: Vec<Option<String>>,
123
124    /// Maximum hops for multi-hop search
125    pub max_hops: usize,
126
127    /// TTL for the request
128    pub ttl: u32,
129
130    /// Exclude peers already queried
131    pub exclude_peers: HashSet<String>,
132}
133
134/// Fact discovery response
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct FactDiscoveryResponse {
137    /// Discovered facts
138    pub facts: Vec<Predicate>,
139
140    /// Peer ID that provided each fact
141    pub sources: HashMap<usize, String>, // fact index -> peer ID
142
143    /// Number of peers queried
144    pub peers_queried: usize,
145
146    /// Hops taken to find facts
147    pub hops: HashMap<usize, usize>, // fact index -> hops
148}
149
150/// Incremental loading request for streaming facts
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct IncrementalLoadRequest {
153    /// Predicate name to load
154    pub predicate_name: String,
155
156    /// Batch size for incremental loading
157    pub batch_size: usize,
158
159    /// Offset for pagination
160    pub offset: usize,
161
162    /// Filter criteria (optional)
163    pub filter: Option<HashMap<String, String>>,
164}
165
166/// Incremental loading response
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct IncrementalLoadResponse {
169    /// Batch of predicates
170    pub batch: Vec<Predicate>,
171
172    /// Total count available
173    pub total_count: usize,
174
175    /// Next offset for continuation
176    pub next_offset: Option<usize>,
177
178    /// Whether this is the last batch
179    pub is_last: bool,
180}
181
182/// Goal resolution request for distributed solving
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct GoalResolutionRequest {
185    /// Goal to solve
186    pub goal: Predicate,
187
188    /// Current substitution
189    pub substitution: HashMap<String, Term>,
190
191    /// Depth in the proof tree
192    pub depth: usize,
193
194    /// Requesting peer ID
195    pub requester: String,
196
197    /// Request ID for tracking
198    pub request_id: String,
199}
200
201/// Goal resolution response
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct GoalResolutionResponse {
204    /// Request ID this response is for
205    pub request_id: String,
206
207    /// Whether the goal was solved
208    pub solved: bool,
209
210    /// Substitutions that solve the goal
211    pub solutions: Vec<HashMap<String, Term>>,
212
213    /// Proof (if requested)
214    pub proof: Option<Proof>,
215
216    /// Proof fragments for assembly
217    pub proof_fragments: Vec<ProofFragmentRef>,
218}
219
220/// Trait for remote knowledge retrieval
221#[async_trait]
222pub trait RemoteKnowledgeProvider: Send + Sync {
223    /// Query a predicate from remote peers
224    async fn query_predicate(
225        &self,
226        request: QueryRequest,
227    ) -> Result<QueryResponse, RemoteReasoningError>;
228
229    /// Discover facts across the network
230    async fn discover_facts(
231        &self,
232        request: FactDiscoveryRequest,
233    ) -> Result<FactDiscoveryResponse, RemoteReasoningError>;
234
235    /// Load facts incrementally
236    async fn load_incremental(
237        &self,
238        request: IncrementalLoadRequest,
239    ) -> Result<IncrementalLoadResponse, RemoteReasoningError>;
240
241    /// Resolve a goal using remote peers
242    async fn resolve_goal(
243        &self,
244        request: GoalResolutionRequest,
245    ) -> Result<GoalResolutionResponse, RemoteReasoningError>;
246
247    /// Get available peers for querying
248    async fn get_available_peers(&self) -> Result<Vec<String>, RemoteReasoningError>;
249}
250
251/// Distributed goal resolver
252pub struct DistributedGoalResolver {
253    /// Local knowledge base
254    local_kb: Arc<KnowledgeBase>,
255
256    /// Remote knowledge provider
257    remote_provider: Option<Arc<dyn RemoteKnowledgeProvider>>,
258
259    /// Maximum depth for distributed resolution
260    max_depth: usize,
261
262    /// Timeout for remote queries (milliseconds)
263    timeout_ms: u64,
264
265    /// Cache for remote facts
266    remote_fact_cache: HashMap<String, Vec<Predicate>>,
267}
268
269impl DistributedGoalResolver {
270    /// Create a new distributed goal resolver
271    pub fn new(local_kb: Arc<KnowledgeBase>) -> Self {
272        Self {
273            local_kb,
274            remote_provider: None,
275            max_depth: 10,
276            timeout_ms: 5000,
277            remote_fact_cache: HashMap::new(),
278        }
279    }
280
281    /// Set the remote knowledge provider
282    pub fn with_provider(mut self, provider: Arc<dyn RemoteKnowledgeProvider>) -> Self {
283        self.remote_provider = Some(provider);
284        self
285    }
286
287    /// Set maximum depth
288    pub fn with_max_depth(mut self, max_depth: usize) -> Self {
289        self.max_depth = max_depth;
290        self
291    }
292
293    /// Set timeout in milliseconds
294    pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
295        self.timeout_ms = timeout_ms;
296        self
297    }
298
299    /// Resolve a goal using both local and remote knowledge
300    pub async fn resolve(
301        &mut self,
302        goal: &Predicate,
303        substitution: &Substitution,
304    ) -> Result<Vec<Substitution>, RemoteReasoningError> {
305        // First, try local resolution
306        let local_solutions = self.resolve_local(goal, substitution);
307
308        if !local_solutions.is_empty() {
309            return Ok(local_solutions);
310        }
311
312        // If no local solutions and remote provider available, try remote
313        if let Some(provider) = self.remote_provider.clone() {
314            let remote_solutions = self.resolve_remote(goal, substitution, &provider).await?;
315            Ok(remote_solutions)
316        } else {
317            Ok(Vec::new())
318        }
319    }
320
321    /// Resolve a goal locally
322    fn resolve_local(&self, goal: &Predicate, _substitution: &Substitution) -> Vec<Substitution> {
323        // Check if goal matches any facts in local KB
324        let facts = self.local_kb.get_predicates(&goal.name);
325
326        let mut solutions = Vec::new();
327        for fact in facts {
328            if let Some(subst) =
329                crate::reasoning::unify_predicates(goal, fact, &Substitution::new())
330            {
331                solutions.push(subst);
332            }
333        }
334
335        solutions
336    }
337
338    /// Resolve a goal remotely
339    async fn resolve_remote(
340        &mut self,
341        goal: &Predicate,
342        substitution: &Substitution,
343        provider: &Arc<dyn RemoteKnowledgeProvider>,
344    ) -> Result<Vec<Substitution>, RemoteReasoningError> {
345        // Create goal resolution request
346        let request = GoalResolutionRequest {
347            goal: goal.clone(),
348            substitution: substitution.clone(),
349            depth: 0,
350            requester: "local".to_string(),
351            request_id: uuid::Uuid::new_v4().to_string(),
352        };
353
354        // Query remote peers
355        let response = provider.resolve_goal(request).await?;
356
357        // Convert solutions
358        Ok(response.solutions)
359    }
360
361    /// Prefetch facts for a predicate from remote peers
362    pub async fn prefetch_facts(
363        &mut self,
364        predicate_name: &str,
365    ) -> Result<usize, RemoteReasoningError> {
366        let Some(provider) = &self.remote_provider else {
367            return Ok(0);
368        };
369
370        // Create discovery request
371        let request = FactDiscoveryRequest {
372            predicate_name: predicate_name.to_string(),
373            arg_patterns: Vec::new(),
374            max_hops: 3,
375            ttl: 30,
376            exclude_peers: HashSet::new(),
377        };
378
379        // Discover facts
380        let response = provider.discover_facts(request).await?;
381
382        // Cache the facts
383        let count = response.facts.len();
384        self.remote_fact_cache
385            .insert(predicate_name.to_string(), response.facts);
386
387        Ok(count)
388    }
389
390    /// Get cached remote facts
391    pub fn get_cached_facts(&self, predicate_name: &str) -> Option<&[Predicate]> {
392        self.remote_fact_cache
393            .get(predicate_name)
394            .map(|v| v.as_slice())
395    }
396
397    /// Clear the remote fact cache
398    pub fn clear_cache(&mut self) {
399        self.remote_fact_cache.clear();
400    }
401}
402
403/// Proof assembler for distributed proofs
404pub struct DistributedProofAssembler {
405    /// Remote knowledge provider
406    remote_provider: Arc<dyn RemoteKnowledgeProvider>,
407
408    /// Cache of proof fragments
409    fragment_cache: HashMap<Cid, ProofFragment>,
410
411    /// Maximum proof depth
412    #[allow(dead_code)]
413    max_depth: usize,
414}
415
416impl DistributedProofAssembler {
417    /// Create a new distributed proof assembler
418    pub fn new(remote_provider: Arc<dyn RemoteKnowledgeProvider>) -> Self {
419        Self {
420            remote_provider,
421            fragment_cache: HashMap::new(),
422            max_depth: 100,
423        }
424    }
425
426    /// Assemble a proof from distributed fragments
427    pub async fn assemble_proof(
428        &mut self,
429        goal: &Predicate,
430    ) -> Result<Option<Proof>, RemoteReasoningError> {
431        // Request goal resolution with proof
432        let request = GoalResolutionRequest {
433            goal: goal.clone(),
434            substitution: HashMap::new(),
435            depth: 0,
436            requester: "local".to_string(),
437            request_id: uuid::Uuid::new_v4().to_string(),
438        };
439
440        let response = self.remote_provider.resolve_goal(request).await?;
441
442        if response.solved {
443            Ok(response.proof)
444        } else {
445            Ok(None)
446        }
447    }
448
449    /// Fetch a proof fragment from the network
450    pub async fn fetch_fragment(
451        &mut self,
452        cid: Cid,
453    ) -> Result<ProofFragment, RemoteReasoningError> {
454        // Check cache first
455        if let Some(fragment) = self.fragment_cache.get(&cid) {
456            return Ok(fragment.clone());
457        }
458
459        // In a real implementation, this would fetch from IPFS
460        // For now, return an error
461        Err(RemoteReasoningError::NetworkError(
462            "Fragment fetch not yet implemented".to_string(),
463        ))
464    }
465}
466
467/// Mock implementation for testing (will be replaced with network implementation)
468pub struct MockRemoteKnowledgeProvider {
469    /// Mock knowledge base
470    mock_kb: Arc<KnowledgeBase>,
471}
472
473impl MockRemoteKnowledgeProvider {
474    /// Create a new mock provider
475    pub fn new(mock_kb: Arc<KnowledgeBase>) -> Self {
476        Self { mock_kb }
477    }
478}
479
480#[async_trait]
481impl RemoteKnowledgeProvider for MockRemoteKnowledgeProvider {
482    async fn query_predicate(
483        &self,
484        request: QueryRequest,
485    ) -> Result<QueryResponse, RemoteReasoningError> {
486        let predicates = self
487            .mock_kb
488            .get_predicates(&request.predicate_name)
489            .into_iter()
490            .take(request.max_results)
491            .cloned()
492            .collect();
493
494        let rules = self
495            .mock_kb
496            .get_rules(&request.predicate_name)
497            .into_iter()
498            .take(request.max_results)
499            .cloned()
500            .collect();
501
502        Ok(QueryResponse {
503            request_id: request.request_id,
504            predicates,
505            rules,
506            proof_fragments: Vec::new(),
507            peer_id: "mock_peer".to_string(),
508            has_more: false,
509            continuation_token: None,
510        })
511    }
512
513    async fn discover_facts(
514        &self,
515        request: FactDiscoveryRequest,
516    ) -> Result<FactDiscoveryResponse, RemoteReasoningError> {
517        let facts: Vec<Predicate> = self
518            .mock_kb
519            .get_predicates(&request.predicate_name)
520            .into_iter()
521            .cloned()
522            .collect();
523
524        let sources: HashMap<usize, String> = (0..facts.len())
525            .map(|i| (i, "mock_peer".to_string()))
526            .collect();
527
528        let hops: HashMap<usize, usize> = (0..facts.len()).map(|i| (i, 0)).collect();
529
530        Ok(FactDiscoveryResponse {
531            facts,
532            sources,
533            peers_queried: 1,
534            hops,
535        })
536    }
537
538    async fn load_incremental(
539        &self,
540        request: IncrementalLoadRequest,
541    ) -> Result<IncrementalLoadResponse, RemoteReasoningError> {
542        let all_facts: Vec<Predicate> = self
543            .mock_kb
544            .get_predicates(&request.predicate_name)
545            .into_iter()
546            .cloned()
547            .collect();
548
549        let total_count = all_facts.len();
550        let start = request.offset;
551        let end = (start + request.batch_size).min(total_count);
552
553        let batch = all_facts[start..end].to_vec();
554        let is_last = end >= total_count;
555        let next_offset = if is_last { None } else { Some(end) };
556
557        Ok(IncrementalLoadResponse {
558            batch,
559            total_count,
560            next_offset,
561            is_last,
562        })
563    }
564
565    async fn resolve_goal(
566        &self,
567        request: GoalResolutionRequest,
568    ) -> Result<GoalResolutionResponse, RemoteReasoningError> {
569        // Simple fact matching
570        let facts = self.mock_kb.get_predicates(&request.goal.name);
571        let mut solutions = Vec::new();
572
573        for fact in facts {
574            if let Some(subst) =
575                crate::reasoning::unify_predicates(&request.goal, fact, &Substitution::new())
576            {
577                solutions.push(subst);
578            }
579        }
580
581        let solved = !solutions.is_empty();
582        let proof = if solved {
583            Some(Proof::fact(request.goal.clone()))
584        } else {
585            None
586        };
587
588        Ok(GoalResolutionResponse {
589            request_id: request.request_id,
590            solved,
591            solutions,
592            proof,
593            proof_fragments: Vec::new(),
594        })
595    }
596
597    async fn get_available_peers(&self) -> Result<Vec<String>, RemoteReasoningError> {
598        Ok(vec!["mock_peer".to_string()])
599    }
600}
601
602#[cfg(test)]
603mod tests {
604    use super::*;
605    use crate::ir::Constant;
606
607    #[tokio::test]
608    async fn test_query_request_serialization() {
609        let request = QueryRequest {
610            predicate_name: "parent".to_string(),
611            ground_args: vec!["Alice".to_string()],
612            max_results: 10,
613            max_depth: 5,
614            request_id: "test_123".to_string(),
615        };
616
617        let json = serde_json::to_string(&request).expect("test: should succeed");
618        let decoded: QueryRequest = serde_json::from_str(&json).expect("test: should succeed");
619
620        assert_eq!(request.predicate_name, decoded.predicate_name);
621        assert_eq!(request.ground_args, decoded.ground_args);
622    }
623
624    #[tokio::test]
625    async fn test_mock_provider_query() {
626        let mut kb = KnowledgeBase::new();
627        kb.add_fact(Predicate::new(
628            "parent".to_string(),
629            vec![
630                Term::Const(Constant::String("Alice".to_string())),
631                Term::Const(Constant::String("Bob".to_string())),
632            ],
633        ));
634
635        let provider = MockRemoteKnowledgeProvider::new(Arc::new(kb));
636
637        let request = QueryRequest {
638            predicate_name: "parent".to_string(),
639            ground_args: vec![],
640            max_results: 10,
641            max_depth: 5,
642            request_id: "test_123".to_string(),
643        };
644
645        let response = provider
646            .query_predicate(request)
647            .await
648            .expect("test: should succeed");
649        assert_eq!(response.predicates.len(), 1);
650        assert_eq!(response.predicates[0].name, "parent");
651    }
652
653    #[tokio::test]
654    async fn test_distributed_resolver() {
655        let mut local_kb = KnowledgeBase::new();
656        local_kb.add_fact(Predicate::new(
657            "parent".to_string(),
658            vec![
659                Term::Const(Constant::String("Alice".to_string())),
660                Term::Const(Constant::String("Bob".to_string())),
661            ],
662        ));
663
664        let mut resolver = DistributedGoalResolver::new(Arc::new(local_kb));
665
666        let goal = Predicate::new(
667            "parent".to_string(),
668            vec![
669                Term::Const(Constant::String("Alice".to_string())),
670                Term::Var("X".to_string()),
671            ],
672        );
673
674        let solutions = resolver
675            .resolve(&goal, &Substitution::new())
676            .await
677            .expect("test: should succeed");
678        assert!(!solutions.is_empty());
679    }
680
681    #[tokio::test]
682    async fn test_fact_discovery() {
683        let mut kb = KnowledgeBase::new();
684        kb.add_fact(Predicate::new(
685            "parent".to_string(),
686            vec![
687                Term::Const(Constant::String("Alice".to_string())),
688                Term::Const(Constant::String("Bob".to_string())),
689            ],
690        ));
691        kb.add_fact(Predicate::new(
692            "parent".to_string(),
693            vec![
694                Term::Const(Constant::String("Bob".to_string())),
695                Term::Const(Constant::String("Charlie".to_string())),
696            ],
697        ));
698
699        let provider = MockRemoteKnowledgeProvider::new(Arc::new(kb));
700
701        let request = FactDiscoveryRequest {
702            predicate_name: "parent".to_string(),
703            arg_patterns: vec![],
704            max_hops: 3,
705            ttl: 30,
706            exclude_peers: HashSet::new(),
707        };
708
709        let response = provider
710            .discover_facts(request)
711            .await
712            .expect("test: should succeed");
713        assert_eq!(response.facts.len(), 2);
714        assert_eq!(response.peers_queried, 1);
715    }
716
717    #[tokio::test]
718    async fn test_incremental_loading() {
719        let mut kb = KnowledgeBase::new();
720        for i in 0..10 {
721            kb.add_fact(Predicate::new(
722                "number".to_string(),
723                vec![Term::Const(Constant::Int(i))],
724            ));
725        }
726
727        let provider = MockRemoteKnowledgeProvider::new(Arc::new(kb));
728
729        // Load first batch
730        let request = IncrementalLoadRequest {
731            predicate_name: "number".to_string(),
732            batch_size: 3,
733            offset: 0,
734            filter: None,
735        };
736
737        let response = provider
738            .load_incremental(request)
739            .await
740            .expect("test: should succeed");
741        assert_eq!(response.batch.len(), 3);
742        assert_eq!(response.total_count, 10);
743        assert!(!response.is_last);
744        assert_eq!(response.next_offset, Some(3));
745    }
746
747    #[tokio::test]
748    async fn test_goal_resolution() {
749        let mut kb = KnowledgeBase::new();
750        kb.add_fact(Predicate::new(
751            "parent".to_string(),
752            vec![
753                Term::Const(Constant::String("Alice".to_string())),
754                Term::Const(Constant::String("Bob".to_string())),
755            ],
756        ));
757
758        let provider = MockRemoteKnowledgeProvider::new(Arc::new(kb));
759
760        let goal = Predicate::new(
761            "parent".to_string(),
762            vec![
763                Term::Const(Constant::String("Alice".to_string())),
764                Term::Var("X".to_string()),
765            ],
766        );
767
768        let request = GoalResolutionRequest {
769            goal,
770            substitution: HashMap::new(),
771            depth: 0,
772            requester: "test".to_string(),
773            request_id: "test_123".to_string(),
774        };
775
776        let response = provider
777            .resolve_goal(request)
778            .await
779            .expect("test: should succeed");
780        assert!(response.solved);
781        assert!(!response.solutions.is_empty());
782        assert!(response.proof.is_some());
783    }
784}