pub struct CaptureService {
config: Config,
secret_detector: SecretDetector,
redactor: ContentRedactor,
embedder: Option<Arc<dyn Embedder>>,
index: Option<Arc<dyn IndexBackend + Send + Sync>>,
vector: Option<Arc<dyn VectorBackend + Send + Sync>>,
entity_extraction: Option<EntityExtractionCallback>,
expiration_config: Option<ExpirationConfig>,
org_index: Option<Arc<dyn IndexBackend + Send + Sync>>,
}Expand description
Service for capturing memories.
§Storage Layers
When fully configured, captures are written to two layers:
- Index (
SQLiteFTS5) - Authoritative storage with full-text search via BM25 - Vector (usearch) - Semantic similarity search
§Entity Extraction
When configured with an entity extraction callback and the feature is enabled, entities (people, organizations, technologies, concepts) are automatically extracted from captured content and stored in the knowledge graph for graph-augmented retrieval (Graph RAG).
§Graceful Degradation
If embedder or vector backend is unavailable:
- Capture still succeeds (Index layer is authoritative)
- A warning is logged for each failed secondary store
- The memory may not be searchable via semantic similarity
If entity extraction fails:
- Capture still succeeds
- A warning is logged
- The memory won’t have graph relationships
Fields§
§config: ConfigConfiguration.
secret_detector: SecretDetectorSecret detector.
redactor: ContentRedactorContent redactor.
embedder: Option<Arc<dyn Embedder>>Embedder for generating embeddings (optional).
index: Option<Arc<dyn IndexBackend + Send + Sync>>Index backend for full-text search (optional).
vector: Option<Arc<dyn VectorBackend + Send + Sync>>Vector backend for similarity search (optional).
entity_extraction: Option<EntityExtractionCallback>Entity extraction callback for graph-augmented retrieval (optional).
expiration_config: Option<ExpirationConfig>Expiration configuration for probabilistic TTL cleanup (optional).
When set, a probabilistic cleanup of TTL-expired memories is triggered after each successful capture (default 5% probability).
org_index: Option<Arc<dyn IndexBackend + Send + Sync>>Organization-scoped index backend (optional).
When set and the capture request has scope: Some(DomainScope::Org),
the memory is stored in the org-shared index instead of the user-local index.
Implementations§
Source§impl CaptureService
impl CaptureService
Sourcepub fn new(config: Config) -> Self
pub fn new(config: Config) -> Self
Creates a new capture service (persistence only).
For full searchability, use with_backends to
also configure index and vector backends.
§Examples
use subcog::config::Config;
use subcog::services::CaptureService;
let config = Config::default();
let service = CaptureService::new(config);
assert!(!service.has_embedder());
assert!(!service.has_index());Sourcepub fn with_backends(
config: Config,
embedder: Arc<dyn Embedder>,
index: Arc<dyn IndexBackend + Send + Sync>,
vector: Arc<dyn VectorBackend + Send + Sync>,
) -> Self
pub fn with_backends( config: Config, embedder: Arc<dyn Embedder>, index: Arc<dyn IndexBackend + Send + Sync>, vector: Arc<dyn VectorBackend + Send + Sync>, ) -> Self
Creates a capture service with all storage backends.
This enables:
- Embedding generation during capture
- Immediate indexing for text search
- Immediate vector upsert for semantic search
Sourcepub fn with_embedder(self, embedder: Arc<dyn Embedder>) -> Self
pub fn with_embedder(self, embedder: Arc<dyn Embedder>) -> Self
Adds an embedder to an existing capture service.
Sourcepub fn with_index(self, index: Arc<dyn IndexBackend + Send + Sync>) -> Self
pub fn with_index(self, index: Arc<dyn IndexBackend + Send + Sync>) -> Self
Adds an index backend to an existing capture service.
Sourcepub fn with_vector(self, vector: Arc<dyn VectorBackend + Send + Sync>) -> Self
pub fn with_vector(self, vector: Arc<dyn VectorBackend + Send + Sync>) -> Self
Adds a vector backend to an existing capture service.
Sourcepub fn with_org_index(self, index: Arc<dyn IndexBackend + Send + Sync>) -> Self
pub fn with_org_index(self, index: Arc<dyn IndexBackend + Send + Sync>) -> Self
Adds an org-scoped index backend for shared memory storage.
When configured and a capture request has scope: Some(DomainScope::Org),
the memory will be stored in the org-shared index instead of the user-local index.
Sourcepub fn has_org_index(&self) -> bool
pub fn has_org_index(&self) -> bool
Returns whether an org-scoped index is configured.
Sourcepub fn with_entity_extraction(self, callback: EntityExtractionCallback) -> Self
pub fn with_entity_extraction(self, callback: EntityExtractionCallback) -> Self
Adds an entity extraction callback for graph-augmented retrieval.
When configured, entities (people, organizations, technologies, concepts) are automatically extracted from captured content and stored in the knowledge graph. This enables Graph RAG (Retrieval-Augmented Generation) by finding related memories through entity relationships.
§Arguments
callback- A callback that extracts entities from content and stores them. The callback receives the content and memory ID, and should return stats. Errors are logged but don’t fail the capture operation.
§Examples
use std::sync::Arc;
use subcog::services::{CaptureService, EntityExtractionStats};
let callback = Arc::new(|content: &str, memory_id: &MemoryId| {
// Extract entities and store in graph...
Ok(EntityExtractionStats::default())
});
let service = CaptureService::default()
.with_entity_extraction(callback);Sourcepub fn has_entity_extraction(&self) -> bool
pub fn has_entity_extraction(&self) -> bool
Returns whether entity extraction is configured.
Sourcepub fn has_embedder(&self) -> bool
pub fn has_embedder(&self) -> bool
Returns whether embedding generation is available.
Sourcepub fn has_vector(&self) -> bool
pub fn has_vector(&self) -> bool
Returns whether vector upsert is available.
Sourcepub const fn with_expiration_config(self, config: ExpirationConfig) -> Self
pub const fn with_expiration_config(self, config: ExpirationConfig) -> Self
Adds expiration configuration for probabilistic TTL cleanup.
When configured, a probabilistic cleanup of TTL-expired memories is triggered after each successful capture. By default, there is a 5% chance of running cleanup after each capture.
§Examples
use subcog::services::CaptureService;
use subcog::gc::ExpirationConfig;
// Enable expiration cleanup with default 5% probability
let service = CaptureService::default()
.with_expiration_config(ExpirationConfig::default());
// Or with custom probability (10%)
let config = ExpirationConfig::new().with_cleanup_probability(0.10);
let service = CaptureService::default()
.with_expiration_config(config);Sourcepub const fn has_expiration(&self) -> bool
pub const fn has_expiration(&self) -> bool
Returns whether expiration cleanup is configured.
Sourcepub fn capture(&self, request: CaptureRequest) -> Result<CaptureResult>
pub fn capture(&self, request: CaptureRequest) -> Result<CaptureResult>
Captures a memory.
§Errors
Returns an error if:
- The content is empty
- The content contains unredacted secrets (when blocking is enabled)
- Storage fails
§Examples
use subcog::services::CaptureService;
use subcog::models::{CaptureRequest, Namespace, Domain};
let service = CaptureService::default();
let request = CaptureRequest {
content: "Use SQLite for local development".to_string(),
namespace: Namespace::Decisions,
domain: Domain::default(),
tags: vec!["database".to_string(), "architecture".to_string()],
source: Some("src/config.rs".to_string()),
skip_security_check: false,
ttl_seconds: None,
scope: None,
..Default::default()
};
let result = service.capture(request)?;
assert!(!result.memory_id.as_str().is_empty());
assert!(result.urn.starts_with("subcog://"));Sourcefn maybe_run_expiration_cleanup(&self)
fn maybe_run_expiration_cleanup(&self)
Probabilistically runs expiration cleanup of TTL-expired memories.
This is called after each successful capture to lazily clean up expired memories without requiring a separate scheduled job.
Sourcefn generate_urn(&self, memory: &Memory) -> String
fn generate_urn(&self, memory: &Memory) -> String
Generates a URN for the memory.
Sourcepub fn validate(&self, request: &CaptureRequest) -> Result<ValidationResult>
pub fn validate(&self, request: &CaptureRequest) -> Result<ValidationResult>
Validates a capture request without storing.
§Errors
Returns an error if validation fails.
§Examples
use subcog::services::CaptureService;
use subcog::models::{CaptureRequest, Namespace, Domain};
let service = CaptureService::default();
// Valid request
let request = CaptureRequest {
content: "Valid content".to_string(),
namespace: Namespace::Learnings,
domain: Domain::default(),
tags: vec![],
source: None,
skip_security_check: false,
ttl_seconds: None,
scope: None,
..Default::default()
};
let result = service.validate(&request)?;
assert!(result.is_valid);
// Empty content is invalid
let empty_request = CaptureRequest {
content: "".to_string(),
namespace: Namespace::Learnings,
domain: Domain::default(),
tags: vec![],
source: None,
skip_security_check: false,
ttl_seconds: None,
scope: None,
..Default::default()
};
let result = service.validate(&empty_request)?;
assert!(!result.is_valid);Captures a memory with authorization check (CRIT-006).
This method requires super::auth::Permission::Write to be present in the auth context.
Use this for MCP/HTTP endpoints where authorization is required.
When the request contains a group_id, this method also validates that the
auth context has write permission to that group.
§Arguments
request- The capture requestauth- Authorization context with permissions
§Errors
Returns Error::Unauthorized if write permission is not granted.
Returns Error::Unauthorized if group write permission is required but not granted.
Returns other errors as per capture.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for CaptureService
impl !RefUnwindSafe for CaptureService
impl Send for CaptureService
impl Sync for CaptureService
impl Unpin for CaptureService
impl !UnwindSafe for CaptureService
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> FutureExt for T
impl<T> FutureExt for T
§fn with_context(self, otel_cx: Context) -> WithContext<Self>
fn with_context(self, otel_cx: Context) -> WithContext<Self>
§fn with_current_context(self) -> WithContext<Self>
fn with_current_context(self) -> WithContext<Self>
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request§impl<L> LayerExt<L> for L
impl<L> LayerExt<L> for L
§fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>where
L: Layer<S>,
Layered].