pub struct ServiceContainer {
capture: CaptureService,
sync: SyncService,
index_manager: Mutex<DomainIndexManager>,
repo_path: Option<PathBuf>,
user_data_dir: PathBuf,
embedder: Option<Arc<dyn Embedder>>,
vector: Option<Arc<dyn VectorBackend + Send + Sync>>,
}Expand description
Container for initialized services with configured backends.
Unlike the previous singleton design, this can be instantiated per-context with domain-scoped indices.
§DomainIndexManager Complexity
The index_manager field uses DomainIndexManager to provide multi-domain
index support with lazy initialization. Key complexity points:
§Architecture
ServiceContainer
└── Mutex<DomainIndexManager>
├── Project index (<user-data>/index.db) // faceted by project/branch/path
├── User index (<user-data>/index.db) // user-wide
└── Org index (configured path) // optional§Lazy Initialization
Indices are created on-demand via index_for_scope():
- Lock the
Mutex<DomainIndexManager> - Check if index exists for requested
DomainScope - If missing, create
SQLitedatabase at scope-specific path - Return reference to the index
§Thread Safety
Mutexguards the manager, not individual indices- Each index has its own internal locking via
SqliteBackend - Callers should minimize lock hold time
§Path Resolution
| Scope | Path |
|---|---|
| Project | <user-data>/index.db |
| User | <user-data>/index.db |
| Org | Configured via OrgIndexConfig |
§Error Handling
- Missing repo returns
Error::OperationFailed SQLiteinitialization errors propagate asError::OperationFailed- Index creation is idempotent (safe to call multiple times)
Fields§
§capture: CaptureServiceCapture service.
sync: SyncServiceSync service.
index_manager: Mutex<DomainIndexManager>Domain index manager for multi-domain indices.
See struct-level documentation for complexity notes.
repo_path: Option<PathBuf>Repository path (if known).
user_data_dir: PathBufUser data directory (from config, used for graph and other user-scoped data).
embedder: Option<Arc<dyn Embedder>>Shared embedder for both capture and recall.
vector: Option<Arc<dyn VectorBackend + Send + Sync>>Shared vector backend for both capture and recall.
Implementations§
Source§impl ServiceContainer
impl ServiceContainer
Sourcepub fn for_repo(
repo_path: impl Into<PathBuf>,
org_config: Option<OrgIndexConfig>,
) -> Result<Self>
pub fn for_repo( repo_path: impl Into<PathBuf>, org_config: Option<OrgIndexConfig>, ) -> Result<Self>
Sourcepub fn from_current_dir() -> Result<Self>
pub fn from_current_dir() -> Result<Self>
Creates a service container from the current directory.
§Errors
Returns an error if not in a git repository.
Sourcepub fn for_user() -> Result<Self>
pub fn for_user() -> Result<Self>
Creates a service container for user-scoped storage.
Used when operating outside a git repository. Stores memories in the
user’s local data directory using SQLite persistence.
§Storage Paths
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/subcog/ |
| Linux | ~/.local/share/subcog/ |
| Windows | C:\Users\<User>\AppData\Local\subcog\ |
§Errors
Returns an error if the user data directory cannot be created or storage backends fail to initialize.
Sourcepub fn from_current_dir_or_user() -> Result<Self>
pub fn from_current_dir_or_user() -> Result<Self>
Creates a service container from the current directory, falling back to user scope.
This is the recommended factory method for CLI and MCP entry points:
- If in a git repository → uses project scope (user-level index + project facets)
- If NOT in a git repository → uses user scope (user-level index)
§Examples
// Works in any directory
let container = ServiceContainer::from_current_dir_or_user()?;
// In git repo: subcog://project/{namespace}/{id}
// Outside git: subcog://user/{namespace}/{id}
let result = container.capture().capture(request)?;§Errors
Returns an error only if both project and user scope fail to initialize.
Sourcepub const fn is_user_scope(&self) -> bool
pub const fn is_user_scope(&self) -> bool
Returns whether this container is using user scope (no git repository).
Sourcepub fn recall_for_scope(&self, scope: DomainScope) -> Result<RecallService>
pub fn recall_for_scope(&self, scope: DomainScope) -> Result<RecallService>
Creates a recall service for a specific domain scope.
The recall service is configured with:
- Index backend (
SQLiteFTS5) for text search - Embedder for generating query embeddings (if available)
- Vector backend for similarity search (if available)
§Errors
Returns an error if the index cannot be initialized.
fn project_scope_filter(&self) -> Option<SearchFilter>
Sourcepub fn recall(&self) -> Result<RecallService>
pub fn recall(&self) -> Result<RecallService>
Creates a recall service for the appropriate scope.
Uses user scope for user-scoped containers, project scope otherwise.
§Errors
Returns an error if the index cannot be initialized.
Sourcepub const fn capture(&self) -> &CaptureService
pub const fn capture(&self) -> &CaptureService
Returns the capture service.
Sourcepub const fn sync(&self) -> &SyncService
pub const fn sync(&self) -> &SyncService
Returns the sync service.
Sourcepub fn embedder(&self) -> Option<Arc<dyn Embedder>>
pub fn embedder(&self) -> Option<Arc<dyn Embedder>>
Returns a reference to the embedder if available.
Sourcepub fn vector(&self) -> Option<Arc<dyn VectorBackend + Send + Sync>>
pub fn vector(&self) -> Option<Arc<dyn VectorBackend + Send + Sync>>
Returns a reference to the vector backend if available.
Sourcepub fn index(&self) -> Result<SqliteBackend>
pub fn index(&self) -> Result<SqliteBackend>
Creates an index backend for the project scope.
§Errors
Returns an error if the index cannot be initialized.
Sourcefn build_capture_service(
config: Config,
backends: &BackendSet,
entity_extraction: Option<EntityExtractionCallback>,
) -> CaptureService
fn build_capture_service( config: Config, backends: &BackendSet, entity_extraction: Option<EntityExtractionCallback>, ) -> CaptureService
Builds a CaptureService from available backends.
Applies graceful degradation: uses whatever backends are available.
§Arguments
config- The capture configurationbackends- Available storage backendsentity_extraction- Optional callback for entity extraction (Graph RAG)
Sourcefn create_entity_extraction_callback(
config: &Config,
paths: &PathManager,
llm: Option<Arc<dyn LlmProvider>>,
) -> Option<EntityExtractionCallback>
fn create_entity_extraction_callback( config: &Config, paths: &PathManager, llm: Option<Arc<dyn LlmProvider>>, ) -> Option<EntityExtractionCallback>
Creates an entity extraction callback if auto-extraction is enabled.
The callback:
- Extracts entities from the memory content using
EntityExtractorService - Stores entities and relationships in the
GraphService - Records mentions linking memories to entities
§Arguments
config- The capture configuration (checked forauto_extract_entitiesflag)paths- Path manager for locating the graph databasellm- Optional LLM provider for intelligent extraction
§Returns
Some(callback) if auto-extraction is enabled and graph backend initializes,
None otherwise (graceful degradation).
Sourcepub fn deduplication(
&self,
) -> Result<DeduplicationService<FastEmbedEmbedder, UsearchBackend>>
pub fn deduplication( &self, ) -> Result<DeduplicationService<FastEmbedEmbedder, UsearchBackend>>
Creates a deduplication service without embedding support.
This variant supports:
- Exact match (SHA256 hash comparison)
- Recent capture (LRU cache with TTL)
For full semantic similarity support, create a DeduplicationService
directly with an embedder and vector backend.
§Errors
Returns an error if the recall service cannot be initialized.
Sourcepub fn deduplication_with_config(
&self,
config: DeduplicationConfig,
) -> Result<DeduplicationService<FastEmbedEmbedder, UsearchBackend>>
pub fn deduplication_with_config( &self, config: DeduplicationConfig, ) -> Result<DeduplicationService<FastEmbedEmbedder, UsearchBackend>>
Sourcepub fn data_subject(&self) -> Result<DataSubjectService>
pub fn data_subject(&self) -> Result<DataSubjectService>
Creates a data subject service for GDPR operations.
Provides:
export_user_data()- Export all user data (GDPR Article 20)delete_user_data()- Delete all user data (GDPR Article 17)
§Errors
Returns an error if the index backend cannot be initialized.
Sourcepub fn get_index_path(&self, scope: DomainScope) -> Result<PathBuf>
pub fn get_index_path(&self, scope: DomainScope) -> Result<PathBuf>
Sourcepub fn reindex_scope(&self, scope: DomainScope) -> Result<usize>
pub fn reindex_scope(&self, scope: DomainScope) -> Result<usize>
Rebuilds the FTS index from SQLite data for a specific scope.
Since SQLite is the authoritative storage, this function reads all memories
from the SQLite database and rebuilds the FTS5 full-text search index.
§Arguments
scope- The domain scope to reindex
§Returns
The number of memories indexed.
§Errors
Returns an error if reading or indexing fails.
Sourcepub fn reindex(&self) -> Result<usize>
pub fn reindex(&self) -> Result<usize>
Reindexes memories for the project scope (default).
§Errors
Returns an error if notes cannot be read or indexing fails.
Sourcepub fn reindex_all(&self) -> Result<HashMap<DomainScope, usize>>
pub fn reindex_all(&self) -> Result<HashMap<DomainScope, usize>>
Sourcepub fn graph(&self) -> Result<GraphService<SqliteGraphBackend>>
pub fn graph(&self) -> Result<GraphService<SqliteGraphBackend>>
Creates a graph service for knowledge graph operations.
The graph service stores entities and relationships in a dedicated
SQLite database (graph.db) in the user data directory.
§Errors
Returns an error if the graph backend cannot be initialized.
§Example
let container = ServiceContainer::from_current_dir_or_user()?;
let graph = container.graph()?;
let entity = graph.store_entity(Entity::new(EntityType::Technology, "Rust", domain))?;Sourcepub fn entity_extractor(&self) -> EntityExtractorService
pub fn entity_extractor(&self) -> EntityExtractorService
Creates an entity extractor service for extracting entities from text.
The extractor uses pattern-based fallback when no LLM is provided.
For LLM-powered extraction, use Self::entity_extractor_with_llm.
§Returns
An EntityExtractorService configured for the appropriate domain.
Sourcepub fn entity_extractor_with_llm(
&self,
llm: Arc<dyn LlmProvider>,
) -> EntityExtractorService
pub fn entity_extractor_with_llm( &self, llm: Arc<dyn LlmProvider>, ) -> EntityExtractorService
Creates an entity extractor service with LLM support.
The extractor uses the provided LLM for intelligent entity extraction. Falls back to pattern-based extraction if LLM calls fail.
§Arguments
llm- The LLM provider to use for extraction.
§Returns
An EntityExtractorService configured with LLM support.
Sourcefn current_domain(&self) -> Domain
fn current_domain(&self) -> Domain
Returns the current domain based on scope.
- If in a git repository: returns project-scoped domain (
Domain::new()) - If NOT in a git repository: returns user-scoped domain (
Domain::for_user())
Sourcepub fn webhook_service(&self) -> Result<Option<WebhookService>>
pub fn webhook_service(&self) -> Result<Option<WebhookService>>
Creates a webhook service for event notifications.
The webhook service subscribes to memory events and delivers them to
configured webhook endpoints. Configuration is loaded from
~/.config/subcog/webhooks.yaml.
Returns Ok(None) if no webhooks are configured.
§Errors
Returns an error if the configuration is invalid or the audit database cannot be created.
§Example
let container = ServiceContainer::from_current_dir_or_user()?;
if let Some(webhook_service) = container.webhook_service()? {
// Start webhook dispatcher as background task
let _handle = webhook_service.start();
}Auto Trait Implementations§
impl !Freeze for ServiceContainer
impl !RefUnwindSafe for ServiceContainer
impl Send for ServiceContainer
impl Sync for ServiceContainer
impl Unpin for ServiceContainer
impl !UnwindSafe for ServiceContainer
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].