Skip to content

Commit 52327c3

Browse files
authored
Remove SQLite and deploy Postgres by default (#1503)
## Summary Removes SQLite as a supported database backend and makes PostgreSQL the only supported option. Bundles a PostgreSQL instance (with pgvector) in the Helm chart so that `helm install` works out of the box with no external prerequisites. Closes: #1502 ## Changes ### Helm - Bundle PostgreSQL in `helm/kagent/templates/postgresql.yaml` — deployed when `database.postgres.url` and `database.postgres.urlFile` are both empty (the default) - Set `database.postgres.url` to skip the bundled instance and use an external PostgreSQL - Remove `database.type`, `database.sqlite`, SQLite emptyDir volume, and `XDG_CACHE_HOME` from the controller deployment ### CI - Remove `strategy.matrix: database: [sqlite, postgres]` from `test-e2e` — single database, no matrix needed - Remove separate Postgres service container — postgres is deployed inside the Kind cluster by `make helm-install` ## Helm values ```yaml database: postgres: # Leave empty to deploy the bundled postgres. Set to use external PostgreSQL. url: "" urlFile: "" vectorEnabled: true bundled: image: pgvector/pgvector:pg18-trixie storage: 500Mi database: postgres user: postgres password: kagent ``` ## Test plan - [x] Go unit tests pass (`go test -race -skip 'TestE2E.*' ./...`) - [x] Helm unit tests pass (`helm unittest helm/kagent`) - [x] `TestE2EMemoryWithAgent` passes — confirms pgvector and memory table work end-to-end - [x] Full E2E suite passes (`make helm-install push-test-agent push-test-skill` followed by e2e tests via `go test -v github.com/kagent-dev/kagent/go/core/test/e2e -failfast -shuffle=on`) --------- Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
1 parent 978662c commit 52327c3

22 files changed

Lines changed: 462 additions & 435 deletions

.github/workflows/ci.yaml

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,27 +34,9 @@ jobs:
3434
test-e2e:
3535
needs:
3636
- setup
37-
strategy:
38-
fail-fast: false
39-
matrix:
40-
database: [sqlite, postgres]
4137
env:
4238
VERSION: v0.0.1-test
4339
runs-on: ubuntu-latest
44-
services:
45-
postgres:
46-
image: pgvector/pgvector:pg18-trixie
47-
env:
48-
POSTGRES_DB: kagent
49-
POSTGRES_USER: postgres
50-
POSTGRES_PASSWORD: kagent
51-
ports:
52-
- 5432:5432
53-
options: >-
54-
--health-cmd pg_isready
55-
--health-interval 10s
56-
--health-timeout 5s
57-
--health-retries 5
5840
steps:
5941
- name: Checkout repository
6042
uses: actions/checkout@v4
@@ -99,11 +81,6 @@ jobs:
9981
run: |
10082
make create-kind-cluster
10183
echo "Cache key: ${{ needs.setup.outputs.cache-key }}"
102-
if [ "${{ matrix.database }}" = "postgres" ]; then
103-
HOST_IP=$(docker network inspect kind -f '{{range .IPAM.Config}}{{if .Gateway}}{{.Gateway}}{{"\n"}}{{end}}{{end}}' | grep -E '^[0-9]+\.' | head -1)
104-
export KAGENT_HELM_EXTRA_ARGS="$KAGENT_HELM_EXTRA_ARGS --set database.type=postgres --set database.postgres.url=postgres://postgres:kagent@${HOST_IP}:5432/kagent"
105-
echo "Postgres URL: postgres://postgres:kagent@${HOST_IP}:5432/kagent"
106-
fi
10784
make helm-install
10885
make push-test-agent push-test-skill
10986
kubectl wait --for=condition=Ready agents.kagent.dev -n kagent --all --timeout=60s || kubectl get po -n kagent -o wide ||:

DEVELOPMENT.md

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -99,35 +99,21 @@ make kagent-addon-install
9999

100100
This installs the following components into your cluster:
101101

102-
| Addon | Description | Namespace |
103-
|----------------|-----------------------------------------------------|-----------|
104-
| Istio | Service mesh (demo profile) | `istio-system` |
105-
| Grafana | Dashboards and visualization | `kagent` |
106-
| Prometheus | Metrics collection | `kagent` |
107-
| Metrics Server | Kubernetes resource metrics | `kube-system` |
108-
| Postgres | Relational database (for kagent controller storage) | `kagent` |
109-
110-
#### Using Postgres as the Datastore
111-
112-
By default, kagent uses a local SQLite database for data persistence. To use
113-
postgres as the backing store instead, deploy kagent via:
114-
115-
> **Warning:**
116-
> The following example uses hardcoded Postgres credentials (`postgres:kagent`) for local development only.
117-
> **Do not use these credentials in production environments.**
118-
```shell
119-
KAGENT_HELM_EXTRA_ARGS="--set database.type=postgres --set database.postgres.url=postgres://postgres:kagent@postgres.kagent.svc.cluster.local:5432/kagent" \
120-
make helm-install
121-
```
102+
| Addon | Description | Namespace |
103+
|----------------|--------------------------------------|----------------|
104+
| Istio | Service mesh (demo profile) | `istio-system` |
105+
| Grafana | Dashboards and visualization | `kagent` |
106+
| Prometheus | Metrics collection | `kagent` |
107+
| Metrics Server | Kubernetes resource metrics | `kube-system` |
122108

123-
Verify the connection by checking the controller logs:
109+
PostgreSQL (with pgvector) is deployed automatically as part of `make helm-install` via the bundled Helm chart. The optional addons above provide observability components.
110+
111+
Verify the database connection by checking the controller logs:
124112

125113
```shell
126114
kubectl logs -n kagent deployment/kagent-controller | grep -i postgres
127115
```
128116

129-
**To revert to SQLite:** Run `make helm-install` without the `KAGENT_HELM_EXTRA_ARGS` variable.
130-
131117
### Troubleshooting
132118

133119
### buildx localhost access

Makefile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,12 +420,10 @@ kagent-addon-install: use-kind-cluster
420420
# to test the kagent addons - installing istio, grafana, prometheus, metrics-server
421421
istioctl install --set profile=demo -y
422422
kubectl apply --context kind-$(KIND_CLUSTER_NAME) -f contrib/addons/grafana.yaml
423-
kubectl apply --context kind-$(KIND_CLUSTER_NAME) -f contrib/addons/postgres.yaml
424423
kubectl apply --context kind-$(KIND_CLUSTER_NAME) -f contrib/addons/prometheus.yaml
425424
kubectl apply --context kind-$(KIND_CLUSTER_NAME) -f contrib/addons/metrics-server.yaml
426425
# wait for pods to be ready
427426
kubectl wait --context kind-$(KIND_CLUSTER_NAME) --for=condition=Ready pod -l app.kubernetes.io/name=grafana -n kagent --timeout=60s
428-
kubectl wait --context kind-$(KIND_CLUSTER_NAME) --for=condition=Ready pod -l app.kubernetes.io/name=postgres -n kagent --timeout=60s
429427
kubectl wait --context kind-$(KIND_CLUSTER_NAME) --for=condition=Ready pod -l app.kubernetes.io/name=prometheus -n kagent --timeout=60s
430428

431429
.PHONY: open-dev-container

contrib/addons/postgres.yaml

Lines changed: 0 additions & 73 deletions
This file was deleted.

go/.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,3 @@ Dockerfile.cross
2424
*.swp
2525
*.swo
2626
*~
27-
28-
# turso
29-
file::*

go/Dockerfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@ RUN --mount=type=cache,target=/root/go/pkg/mod,rw \
3030
CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -ldflags "$LDFLAGS" -o /app "$BUILD_PACKAGE"
3131

3232
### STAGE 2: final image
33-
# Use distroless/cc-debian12 which includes C/C++ runtime libraries
34-
# This is required for turso-go's purego library loading
33+
# Use distroless as minimal base image to package the manager binary
3534
# Refer to https://github.com/GoogleContainerTools/distroless for more details
36-
FROM gcr.io/distroless/cc-debian12:nonroot
35+
FROM gcr.io/distroless/static:nonroot
3736
ARG TARGETPLATFORM
3837

3938
WORKDIR /

go/core/internal/controller/reconciler/mcp_server_reconciler_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
agenttranslator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent"
1616
"github.com/kagent-dev/kagent/go/core/internal/database"
17+
"github.com/kagent-dev/kagent/go/core/internal/dbtest"
1718
"github.com/kagent-dev/kmcp/api/v1alpha1"
1819
)
1920

@@ -26,6 +27,12 @@ func TestReconcileKagentMCPServer_ErrorPropagation(t *testing.T) {
2627
err := v1alpha1.AddToScheme(scheme)
2728
require.NoError(t, err)
2829

30+
if testing.Short() {
31+
t.Skip("skipping database test in short mode")
32+
}
33+
34+
connStr := dbtest.StartT(context.Background(), t)
35+
2936
testCases := []struct {
3037
name string
3138
mcpServer *v1alpha1.MCPServer
@@ -78,11 +85,10 @@ func TestReconcileKagentMCPServer_ErrorPropagation(t *testing.T) {
7885
WithObjects(tc.mcpServer).
7986
Build()
8087

81-
// Create an in-memory database manager
8288
dbManager, err := database.NewManager(&database.Config{
83-
DatabaseType: database.DatabaseTypeSqlite,
84-
SqliteConfig: &database.SqliteConfig{
85-
DatabasePath: "file::memory:?cache=shared",
89+
PostgresConfig: &database.PostgresConfig{
90+
URL: connStr,
91+
VectorEnabled: true,
8692
},
8793
})
8894
require.NoError(t, err)

go/core/internal/database/client.go

Lines changed: 15 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -512,13 +512,10 @@ func (c *clientImpl) StoreCrewAIMemory(ctx context.Context, memory *dbpkg.CrewAI
512512
func (c *clientImpl) SearchCrewAIMemoryByTask(ctx context.Context, userID, threadID, taskDescription string, limit int) ([]*dbpkg.CrewAIAgentMemory, error) {
513513
var memories []*dbpkg.CrewAIAgentMemory
514514

515-
// Search for task_description within the JSON memory_data field
516-
// Using JSON_EXTRACT or JSON_UNQUOTE for MySQL/PostgreSQL, or simple LIKE for SQLite
517-
// Sort by created_at DESC, then by score ASC (if score exists in JSON)
518515
query := c.db.WithContext(ctx).Where(
519-
"user_id = ? AND thread_id = ? AND (memory_data LIKE ? OR JSON_EXTRACT(memory_data, '$.task_description') LIKE ?)",
516+
"user_id = ? AND thread_id = ? AND (memory_data LIKE ? OR memory_data->>'task_description' LIKE ?)",
520517
userID, threadID, "%"+taskDescription+"%", "%"+taskDescription+"%",
521-
).Order("created_at DESC, JSON_EXTRACT(memory_data, '$.score') ASC")
518+
).Order("created_at DESC, memory_data->>'score' ASC")
522519

523520
// Apply limit
524521
if limit > 0 {
@@ -597,43 +594,17 @@ func (c *clientImpl) StoreAgentMemories(ctx context.Context, memories []*dbpkg.M
597594
func (c *clientImpl) SearchAgentMemory(ctx context.Context, agentName, userID string, embedding pgvector.Vector, limit int) ([]dbpkg.AgentMemorySearchResult, error) {
598595
var results []dbpkg.AgentMemorySearchResult
599596

600-
db := c.db.WithContext(ctx)
601-
if db.Name() == "sqlite" {
602-
// libSQL/Turso syntax: vector_distance_cos(embedding, vector32('JSON_ARRAY'))
603-
// We must use fmt.Sprintf to inline the JSON array because vector32() requires a string literal
604-
// and parameter binding with ? fails with "unexpected token" errors (GORM limitation)
605-
embeddingJSON, err := json.Marshal(embedding.Slice())
606-
if err != nil {
607-
return nil, fmt.Errorf("failed to serialize embedding: %w", err)
608-
}
609-
610-
// Safe formatting because we control the JSON string generation from float slice
611-
query := fmt.Sprintf(`
612-
SELECT id, agent_name, user_id, content, metadata, created_at, expires_at, access_count,
613-
1 - vector_distance_cos(embedding, vector32('%s')) as score
614-
FROM memory
615-
WHERE agent_name = ? AND user_id = ?
616-
ORDER BY vector_distance_cos(embedding, vector32('%s')) ASC
617-
LIMIT ?
618-
`, string(embeddingJSON), string(embeddingJSON))
619-
620-
if err := db.Raw(query, agentName, userID, limit).Scan(&results).Error; err != nil {
621-
return nil, fmt.Errorf("failed to search agent memory (sqlite): %w", err)
622-
}
623-
} else {
624-
// Postgres pgvector syntax: uses <=> operator for cosine distance.
625-
// COALESCE guards against NaN when either vector has zero magnitude.
626-
// pgvector.Vector implements sql.Scanner and driver.Valuer
627-
query := `
628-
SELECT *, COALESCE(1 - (embedding <=> ?), 0) as score
629-
FROM memory
630-
WHERE agent_name = ? AND user_id = ?
631-
ORDER BY embedding <=> ? ASC
632-
LIMIT ?
633-
`
634-
if err := db.Raw(query, embedding, agentName, userID, embedding, limit).Scan(&results).Error; err != nil {
635-
return nil, fmt.Errorf("failed to search agent memory (postgres): %w", err)
636-
}
597+
// pgvector <=> operator for cosine distance.
598+
// COALESCE guards against NaN when either vector has zero magnitude.
599+
query := `
600+
SELECT *, COALESCE(1 - (embedding <=> ?), 0) as score
601+
FROM memory
602+
WHERE agent_name = ? AND user_id = ?
603+
ORDER BY embedding <=> ? ASC
604+
LIMIT ?
605+
`
606+
if err := c.db.WithContext(ctx).Raw(query, embedding, agentName, userID, embedding, limit).Scan(&results).Error; err != nil {
607+
return nil, fmt.Errorf("failed to search agent memory: %w", err)
637608
}
638609

639610
// Increment access count for found memories synchronously.
@@ -642,7 +613,7 @@ func (c *clientImpl) SearchAgentMemory(ctx context.Context, agentName, userID st
642613
for i, m := range results {
643614
ids[i] = m.ID
644615
}
645-
if err := db.Model(&dbpkg.Memory{}).Where("id IN ?", ids).UpdateColumn("access_count", gorm.Expr("access_count + ?", 1)).Error; err != nil {
616+
if err := c.db.WithContext(ctx).Model(&dbpkg.Memory{}).Where("id IN ?", ids).UpdateColumn("access_count", gorm.Expr("access_count + ?", 1)).Error; err != nil {
646617
return nil, fmt.Errorf("failed to increment access count: %w", err)
647618
}
648619
}
@@ -705,15 +676,7 @@ func (c *clientImpl) DeleteAgentMemory(ctx context.Context, agentName, userID st
705676
}
706677

707678
func (c *clientImpl) deleteAgentMemoryByQuery(ctx context.Context, agentName, userID string) error {
708-
var ids []string
709-
if err := c.db.WithContext(ctx).Table("memory").Where("agent_name = ? AND user_id = ?", agentName, userID).Pluck("id", &ids).Error; err != nil {
710-
return fmt.Errorf("failed to list memory ids: %w", err)
711-
}
712-
if len(ids) == 0 {
713-
return nil
714-
}
715-
// DELETE by primary key only to avoid Turso multi-index scan on DELETE which causes a bug
716-
if err := c.db.WithContext(ctx).Exec("DELETE FROM memory WHERE id IN ?", ids).Error; err != nil {
679+
if err := c.db.WithContext(ctx).Where("agent_name = ? AND user_id = ?", agentName, userID).Delete(&dbpkg.Memory{}).Error; err != nil {
717680
return fmt.Errorf("failed to delete agent memory: %w", err)
718681
}
719682
return nil

0 commit comments

Comments
 (0)