Portal Community

The Memory Bridge

By default, SK uses its own memory abstraction (ISemanticTextMemory). The bridge replaces SK's default memory implementation with an adapter that delegates to Octopus's ISemanticMemoryStore:

// OctopusSKMemoryAdapter — implements SK's ISemanticTextMemory
// delegating all calls to Octopus ISemanticMemoryStore
public class OctopusSKMemoryAdapter : ISemanticTextMemory
{
    private readonly ISemanticMemoryStore    _octopusStore;
    private readonly IEmbeddingProvider      _embeddingProvider;
    private readonly string                  _collectionPrefix;

    public OctopusSKMemoryAdapter(
        ISemanticMemoryStore    octopusStore,
        IEmbeddingProvider      embeddingProvider,
        IOptions<SemanticKernelPluginConfig> config)
    {
        _octopusStore      = octopusStore;
        _embeddingProvider = embeddingProvider;
        _collectionPrefix  = config.Value.Memory.CollectionPrefix;
    }

    public async Task<string> SaveInformationAsync(
        string collection, string text, string id,
        string? description = null, string? additionalMetadata = null,
        CancellationToken cancellationToken = default)
    {
        var embedding = await _embeddingProvider.EmbedAsync(text, cancellationToken);
        var prefixedCollection = $"{_collectionPrefix}{collection}";

        await _octopusStore.UpsertAsync(prefixedCollection, new SemanticPoint
        {
            Id        = id,
            Embedding = embedding,
            Payload   = new { text, description, additionalMetadata }
        }, cancellationToken);

        return id;
    }

    public async Task<MemoryQueryResult?> GetAsync(
        string collection, string key, bool withEmbedding = false,
        CancellationToken cancellationToken = default)
    {
        var prefixedCollection = $"{_collectionPrefix}{collection}";
        var point = await _octopusStore.GetByIdAsync(prefixedCollection, key, cancellationToken);
        return point is null ? null : MapToSKResult(point);
    }

    public async IAsyncEnumerable<MemoryQueryResult> SearchAsync(
        string collection, string query, int limit = 1, double minRelevanceScore = 0.7,
        bool withEmbeddings = false,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        var queryEmbedding     = await _embeddingProvider.EmbedAsync(query, cancellationToken);
        var prefixedCollection = $"{_collectionPrefix}{collection}";

        var results = await _octopusStore.SearchAsync(
            prefixedCollection, queryEmbedding, limit, (float)minRelevanceScore, cancellationToken);

        foreach (var result in results)
            yield return MapToSKResult(result);
    }
}

Collection Naming with Prefix

SK memory calls use the CollectionPrefix to namespace SK collections within the shared vector store, preventing collisions with Octopus-managed collections:

SK collection nameActual Qdrant/PGVector collection
hr_policiessk_hr_policies
product_docssk_product_docs
tenant_abc_knowledgesk_tenant_abc_knowledge

Bridge Configuration

{
  "SemanticKernelPlugin": {
    "Memory": {
      "BridgeToOctopusStore": true,
      "CollectionPrefix":      "sk_"
    }
  }
}

When BridgeToOctopusStore is false, SK uses its own separate memory store. This is useful when SK needs a completely isolated vector store independent of Octopus semantic memory.

Single embedding model. With the bridge enabled, SK memory and Octopus semantic memory both use the same IEmbeddingProvider. This ensures vectors are compatible for cross-system searches, and avoids running two separate embedding models.