Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to Distill are documented here.

## [Unreleased]

### Added

- **PostgreSQL/Supabase memory backend** — Persistent memory storage using Postgres instead of ephemeral SQLite. New `--memory-backend postgres` and `--memory-dsn` flags on `api` and `mcp` commands. Implements the same `Store` interface as SQLiteStore. ([#40](https://github.com/Siddhant-K-code/distill/pull/40))

---

## [v0.4.0] - 2026-02-24

### Added
Expand Down
2 changes: 1 addition & 1 deletion FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ LLMs are non-deterministic. The same input can produce different compressed outp

### What is Context Memory?

Persistent memory that accumulates knowledge across agent sessions. Store context once, recall it later by semantic similarity + recency. Memories are deduplicated on write and compressed over time through hierarchical decay (full text → summary → keywords → evicted). Enable with `--memory` on the `api` or `mcp` commands.
Persistent memory that accumulates knowledge across agent sessions. Store context once, recall it later by semantic similarity + recency. Memories are deduplicated on write and compressed over time through hierarchical decay (full text → summary → keywords → evicted). Enable with `--memory` on the `api` or `mcp` commands. Supports SQLite (default, local) and PostgreSQL/Supabase (`--memory-backend postgres`) for persistent storage.

### What are Sessions?

Expand Down
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,16 @@ See [mcp/README.md](mcp/README.md) for more configuration options.

Persistent memory that accumulates knowledge across agent sessions. Memories are deduplicated on write, ranked by relevance + recency on recall, and compressed over time through hierarchical decay.

Enable with the `--memory` flag on `api` or `mcp` commands.
Enable with the `--memory` flag on `api` or `mcp` commands. Supports SQLite (default, local) and PostgreSQL (persistent, recommended for production).

```bash
# SQLite (default - local file, good for development)
distill api --memory

# PostgreSQL (persistent - recommended for production/Supabase)
distill api --memory --memory-backend postgres \
--memory-dsn 'postgres://user:pass@host:5432/db?sslmode=require'
```

### CLI

Expand Down Expand Up @@ -273,8 +282,13 @@ Accessing a memory resets its decay clock. Configure ages via `distill.yaml`:

```yaml
memory:
# SQLite (default)
db_path: distill-memory.db
dedup_threshold: 0.15

# Or PostgreSQL/Supabase
# backend: postgres
# dsn: postgres://user:pass@host:5432/db?sslmode=require
```

## Session Management
Expand Down Expand Up @@ -431,7 +445,9 @@ auth:
- ${DISTILL_API_KEY}

memory:
db_path: distill-memory.db
db_path: distill-memory.db # SQLite (default)
# backend: postgres # Use 'postgres' for Supabase/PostgreSQL
# dsn: ${DATABASE_URL} # Postgres connection string
dedup_threshold: 0.15

session:
Expand All @@ -448,6 +464,7 @@ Environment variables can be referenced using `${VAR}` or `${VAR:-default}` synt
OPENAI_API_KEY # For text → embedding conversion (see note below)
PINECONE_API_KEY # For Pinecone backend
QDRANT_URL # For Qdrant backend (default: localhost:6334)
DATABASE_URL # For Postgres memory backend (Supabase connection string)
DISTILL_API_KEYS # Optional: protect your self-hosted instance (see below)
```

Expand Down
18 changes: 14 additions & 4 deletions cmd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func init() {
apiCmd.Flags().String("embedding-model", "text-embedding-3-small", "OpenAI embedding model")
apiCmd.Flags().String("api-keys", "", "Comma-separated list of valid API keys (or use DISTILL_API_KEYS)")
apiCmd.Flags().Bool("memory", false, "Enable persistent memory store")
apiCmd.Flags().String("memory-backend", "sqlite", "Memory store backend: sqlite or postgres")
apiCmd.Flags().String("memory-dsn", "", "Memory store DSN (file path for sqlite, connection string for postgres)")
apiCmd.Flags().Bool("session", false, "Enable session management")
apiCmd.Flags().String("session-db", "distill-sessions.db", "SQLite database path for session store")

Expand Down Expand Up @@ -184,15 +186,23 @@ func runAPI(cmd *cobra.Command, args []string) error {
// Setup memory store (opt-in)
enableMemory, _ := cmd.Flags().GetBool("memory")
if enableMemory {
memDBPath := viper.GetString("memory.db_path")
if memDBPath == "" {
memDBPath = "distill-memory.db"
memBackend, _ := cmd.Flags().GetString("memory-backend")
memDSN, _ := cmd.Flags().GetString("memory-dsn")
// Fall back to config file values
if memDSN == "" {
memDSN = viper.GetString("memory.dsn")
}
if memDSN == "" {
memDSN = viper.GetString("memory.db_path")
}
if memBackend == "sqlite" && memDSN == "" {
memDSN = "distill-memory.db"
}
memThreshold := viper.GetFloat64("memory.dedup_threshold")
if memThreshold == 0 {
memThreshold = 0.15
}
memStore, err := memoryStoreFromConfig(memDBPath, memThreshold)
memStore, err := memoryStoreFromConfig(memBackend, memDSN, memThreshold)
if err != nil {
return fmt.Errorf("failed to create memory store: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/api_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

// MemoryAPI handles memory-related HTTP endpoints.
type MemoryAPI struct {
store *memory.SQLiteStore
store memory.Store
embedder retriever.EmbeddingProvider
}

Expand Down
16 changes: 10 additions & 6 deletions cmd/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ func init() {

// Memory store
mcpCmd.Flags().Bool("memory", false, "Enable persistent memory store")
mcpCmd.Flags().String("memory-db", "distill-memory.db", "SQLite database path for memory store")
mcpCmd.Flags().String("memory-backend", "sqlite", "Memory store backend: sqlite or postgres")
mcpCmd.Flags().String("memory-dsn", "", "Memory store DSN (file path for sqlite, connection string for postgres")
mcpCmd.Flags().String("memory-db", "distill-memory.db", "SQLite database path for memory store (deprecated, use --memory-dsn)")
mcpCmd.Flags().Bool("session", false, "Enable session management")
mcpCmd.Flags().String("session-db", "distill-sessions.db", "SQLite database path for session store")

Expand All @@ -108,7 +110,7 @@ type MCPServer struct {
broker *contextlab.Broker
embedder retriever.EmbeddingProvider
cfg contextlab.BrokerConfig
memStore *memory.SQLiteStore
memStore memory.Store
sessStore *session.SQLiteStore
}

Expand Down Expand Up @@ -159,10 +161,12 @@ func runMCP(cmd *cobra.Command, args []string) error {
// Create memory store (opt-in)
enableMemory, _ := cmd.Flags().GetBool("memory")
if enableMemory {
memDBPath, _ := cmd.Flags().GetString("memory-db")
memCfg := memory.DefaultConfig()
memCfg.DedupThreshold = threshold
memStore, err := memory.NewSQLiteStore(memDBPath, memCfg)
memBackend, _ := cmd.Flags().GetString("memory-backend")
memDSN, _ := cmd.Flags().GetString("memory-dsn")
if memDSN == "" {
memDSN, _ = cmd.Flags().GetString("memory-db")
}
memStore, err := memoryStoreFromConfig(memBackend, memDSN, threshold)
if err != nil {
return fmt.Errorf("failed to create memory store: %w", err)
}
Expand Down
21 changes: 16 additions & 5 deletions cmd/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,22 @@ func runMemoryStats(cmd *cobra.Command, args []string) error {

// memoryStoreFromConfig creates a memory store from the API server config.
// Used by the API server and MCP server.
func memoryStoreFromConfig(dbPath string, threshold float64) (*memory.SQLiteStore, error) {
if dbPath == "" {
dbPath = "distill-memory.db"
}
// backend: "sqlite" (default) or "postgres"
// dsn: database path (sqlite) or connection string (postgres)
func memoryStoreFromConfig(backend, dsn string, threshold float64) (memory.Store, error) {
cfg := memory.DefaultConfig()
cfg.DedupThreshold = threshold
return memory.NewSQLiteStore(dbPath, cfg)

switch backend {
case "postgres":
if dsn == "" {
return nil, fmt.Errorf("--memory-dsn is required for postgres backend")
}
return memory.NewPostgresStore(dsn, cfg)
default:
if dsn == "" {
dsn = "distill-memory.db"
}
return memory.NewSQLiteStore(dsn, cfg)
}
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.8.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand Down Expand Up @@ -72,6 +76,7 @@ require (
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.33.0 // indirect
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
Expand Down Expand Up @@ -119,6 +127,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
Expand Down Expand Up @@ -165,6 +174,8 @@ golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
Expand Down
Loading