Skip to main content

ServiceContainer

Struct ServiceContainer 

Source
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():

  1. Lock the Mutex<DomainIndexManager>
  2. Check if index exists for requested DomainScope
  3. If missing, create SQLite database at scope-specific path
  4. Return reference to the index

§Thread Safety

  • Mutex guards the manager, not individual indices
  • Each index has its own internal locking via SqliteBackend
  • Callers should minimize lock hold time

§Path Resolution

ScopePath
Project<user-data>/index.db
User<user-data>/index.db
OrgConfigured via OrgIndexConfig

§Error Handling

  • Missing repo returns Error::OperationFailed
  • SQLite initialization errors propagate as Error::OperationFailed
  • Index creation is idempotent (safe to call multiple times)

Fields§

§capture: CaptureService

Capture service.

§sync: SyncService

Sync 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: PathBuf

User 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

Source

pub fn for_repo( repo_path: impl Into<PathBuf>, org_config: Option<OrgIndexConfig>, ) -> Result<Self>

Creates a new service container for a repository.

§Arguments
  • repo_path - Path to or within a git repository
  • org_config - Optional organization index configuration
§Errors

Returns an error if the repository cannot be found or backends fail to initialize.

Source

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.

Source

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
PlatformPath
macOS~/Library/Application Support/subcog/
Linux~/.local/share/subcog/
WindowsC:\Users\<User>\AppData\Local\subcog\
§Errors

Returns an error if the user data directory cannot be created or storage backends fail to initialize.

Source

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.

Source

pub const fn is_user_scope(&self) -> bool

Returns whether this container is using user scope (no git repository).

Source

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 (SQLite FTS5) 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.

Source

fn project_scope_filter(&self) -> Option<SearchFilter>

Source

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.

Source

pub const fn capture(&self) -> &CaptureService

Returns the capture service.

Source

pub const fn sync(&self) -> &SyncService

Returns the sync service.

Source

pub const fn repo_path(&self) -> Option<&PathBuf>

Returns the repository path.

Source

pub fn embedder(&self) -> Option<Arc<dyn Embedder>>

Returns a reference to the embedder if available.

Source

pub fn vector(&self) -> Option<Arc<dyn VectorBackend + Send + Sync>>

Returns a reference to the vector backend if available.

Source

pub fn index(&self) -> Result<SqliteBackend>

Creates an index backend for the project scope.

§Errors

Returns an error if the index cannot be initialized.

Source

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 configuration
  • backends - Available storage backends
  • entity_extraction - Optional callback for entity extraction (Graph RAG)
Source

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:

  1. Extracts entities from the memory content using EntityExtractorService
  2. Stores entities and relationships in the GraphService
  3. Records mentions linking memories to entities
§Arguments
  • config - The capture configuration (checked for auto_extract_entities flag)
  • paths - Path manager for locating the graph database
  • llm - Optional LLM provider for intelligent extraction
§Returns

Some(callback) if auto-extraction is enabled and graph backend initializes, None otherwise (graceful degradation).

Source

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.

Source

pub fn deduplication_with_config( &self, config: DeduplicationConfig, ) -> Result<DeduplicationService<FastEmbedEmbedder, UsearchBackend>>

Creates a deduplication service with custom configuration.

§Arguments
  • config - Custom deduplication configuration
§Errors

Returns an error if the recall service cannot be initialized.

Source

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.

Source

pub fn get_index_path(&self, scope: DomainScope) -> Result<PathBuf>

Gets the index path for a domain scope.

§Errors

Returns an error if the path cannot be determined.

Source

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.

Source

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.

Source

pub fn reindex_all(&self) -> Result<HashMap<DomainScope, usize>>

Reindexes all domain scopes.

§Returns

A map of scope to count of indexed memories.

§Errors

Returns an error if any scope fails to reindex.

Source

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))?;
Source

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.

Source

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.

Source

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())
Source

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§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> FutureExt for T

§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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

§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
§

impl<L> LayerExt<L> for L

§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in [Layered].
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more