From 01d96841736838fa78ad8d4f061fc623b285b872 Mon Sep 17 00:00:00 2001 From: Gdetrane Date: Thu, 7 May 2026 16:08:48 +0200 Subject: [PATCH 1/4] Fix CI workflow and lint compatibility. Repair the CockroachDB startup step in CI, switch golangci-lint to the v2 CLI path that matches the repo config, and clean up the newly surfaced lint violations so pull request checks can run cleanly again. Co-authored-by: Cursor --- .github/workflows/ci.yml | 18 ++++-- cmd/memoryd/main.go | 68 ++++++++++++----------- internal/importer/cursor_agent_jsonl.go | 4 +- internal/server/http.go | 4 +- internal/store/cockroach/helpers.go | 15 ----- internal/store/cockroach/introspection.go | 8 +-- internal/store/cockroach/memories.go | 10 ++-- internal/store/cockroach/migrate.go | 4 +- internal/store/cockroach/recall.go | 12 ++-- 9 files changed, 73 insertions(+), 70 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3e859a..67787e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,9 +34,9 @@ jobs: fi - name: Run golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - version: latest + run: | + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest + "$(go env GOPATH)/bin/golangci-lint" run - name: Run unit tests run: go test -race ./... @@ -73,11 +73,19 @@ jobs: - name: Start CockroachDB run: | - ./cockroach-v26.1.3.linux-amd64/cockroach start-single-node --insecure --store=type=mem,size=0.25GiB --listen-addr=127.0.0.1:26257 --http-addr=127.0.0.1:8080 --background + ./cockroach-v26.1.3.linux-amd64/cockroach start-single-node \ + --insecure \ + --store=type=mem,size=0.25GiB \ + --listen-addr=127.0.0.1:26257 \ + --http-addr=127.0.0.1:8080 \ + --background - name: Wait for CockroachDB run: | - ./cockroach-v26.1.3.linux-amd64/cockroach sql --insecure --host=127.0.0.1:26257 -e 'select 1' + ./cockroach-v26.1.3.linux-amd64/cockroach sql \ + --insecure \ + --host=127.0.0.1:26257 \ + -e 'select 1' - name: Run integration and migration upgrade tests env: diff --git a/cmd/memoryd/main.go b/cmd/memoryd/main.go index 2548379..2d5f024 100644 --- a/cmd/memoryd/main.go +++ b/cmd/memoryd/main.go @@ -400,36 +400,40 @@ func parseOptionalUUIDFlag(value string) (*uuid.UUID, error) { } func printUsage(w *os.File) { - fmt.Fprintln(w, "agent-memory") - fmt.Fprintln(w, "") - fmt.Fprintln(w, "Usage:") - fmt.Fprintln(w, " memoryd serve") - fmt.Fprintln(w, " memoryd worker [--once]") - fmt.Fprintln(w, " memoryd import --format= --file= --tenant-id= --agent-id= [--thread-id=] [--source-id=]") - fmt.Fprintln(w, " memoryd doctor") - fmt.Fprintln(w, " memoryd migrate") - fmt.Fprintln(w, " memoryd version") - fmt.Fprintln(w, "") - fmt.Fprintln(w, "Environment:") - fmt.Fprintln(w, " MEMORY_DATABASE_URL CockroachDB connection string") - fmt.Fprintln(w, " MEMORY_HTTP_ADDR HTTP listen address (default :8080)") - fmt.Fprintln(w, " MEMORY_HTTP_AUTH_MODE disabled|api_key (default disabled)") - fmt.Fprintln(w, " MEMORY_HTTP_AUTH_HEADER auth header name (default Authorization)") - fmt.Fprintln(w, " MEMORY_HTTP_AUTH_SCHEME auth scheme prefix (default Bearer)") - fmt.Fprintln(w, " MEMORY_HTTP_AUTH_PRINCIPALS_JSON JSON array of principals when api_key auth is enabled") - fmt.Fprintln(w, " MEMORY_HTTP_RATE_LIMIT_RPS requests per second per principal, 0 disables rate limiting") - fmt.Fprintln(w, " MEMORY_HTTP_RATE_LIMIT_BURST token bucket burst size when rate limiting is enabled") - fmt.Fprintln(w, " MEMORY_LOG_LEVEL debug|info|warn|error (default info)") - fmt.Fprintln(w, " MEMORY_LOG_FORMAT text|json|logfmt (default text)") - fmt.Fprintln(w, " MEMORY_LOG_REPORT_CALLER true|false (default false)") - fmt.Fprintln(w, " MEMORY_LOG_TIMESTAMPS true|false (default true)") - fmt.Fprintln(w, " MEMORY_SHUTDOWN_TIMEOUT graceful shutdown timeout (default 10s)") - fmt.Fprintln(w, " MEMORY_HEALTH_TIMEOUT readiness timeout (default 3s)") - fmt.Fprintln(w, " MEMORY_WORKER_POLL_INTERVAL worker poll interval (default 10s)") - fmt.Fprintln(w, " MEMORY_EMBEDDER_PROVIDER deterministic|openai (default deterministic)") - fmt.Fprintln(w, " MEMORY_EMBEDDER_MODEL required when provider=openai") - fmt.Fprintln(w, " MEMORY_EMBEDDER_DIMENSIONS embedding vector dimensions (default 1536)") - fmt.Fprintln(w, " MEMORY_EMBED_MAX_BATCH max texts per provider embedding call (default 32)") - fmt.Fprintln(w, " MEMORY_OPENAI_API_KEY required when provider=openai") - fmt.Fprintln(w, " MEMORY_OPENAI_BASE_URL optional OpenAI-compatible base URL") + writeUsageLine(w, "agent-memory") + writeUsageLine(w, "") + writeUsageLine(w, "Usage:") + writeUsageLine(w, " memoryd serve") + writeUsageLine(w, " memoryd worker [--once]") + writeUsageLine(w, " memoryd import --format= --file= --tenant-id= --agent-id= [--thread-id=] [--source-id=]") + writeUsageLine(w, " memoryd doctor") + writeUsageLine(w, " memoryd migrate") + writeUsageLine(w, " memoryd version") + writeUsageLine(w, "") + writeUsageLine(w, "Environment:") + writeUsageLine(w, " MEMORY_DATABASE_URL CockroachDB connection string") + writeUsageLine(w, " MEMORY_HTTP_ADDR HTTP listen address (default :8080)") + writeUsageLine(w, " MEMORY_HTTP_AUTH_MODE disabled|api_key (default disabled)") + writeUsageLine(w, " MEMORY_HTTP_AUTH_HEADER auth header name (default Authorization)") + writeUsageLine(w, " MEMORY_HTTP_AUTH_SCHEME auth scheme prefix (default Bearer)") + writeUsageLine(w, " MEMORY_HTTP_AUTH_PRINCIPALS_JSON JSON array of principals when api_key auth is enabled") + writeUsageLine(w, " MEMORY_HTTP_RATE_LIMIT_RPS requests per second per principal, 0 disables rate limiting") + writeUsageLine(w, " MEMORY_HTTP_RATE_LIMIT_BURST token bucket burst size when rate limiting is enabled") + writeUsageLine(w, " MEMORY_LOG_LEVEL debug|info|warn|error (default info)") + writeUsageLine(w, " MEMORY_LOG_FORMAT text|json|logfmt (default text)") + writeUsageLine(w, " MEMORY_LOG_REPORT_CALLER true|false (default false)") + writeUsageLine(w, " MEMORY_LOG_TIMESTAMPS true|false (default true)") + writeUsageLine(w, " MEMORY_SHUTDOWN_TIMEOUT graceful shutdown timeout (default 10s)") + writeUsageLine(w, " MEMORY_HEALTH_TIMEOUT readiness timeout (default 3s)") + writeUsageLine(w, " MEMORY_WORKER_POLL_INTERVAL worker poll interval (default 10s)") + writeUsageLine(w, " MEMORY_EMBEDDER_PROVIDER deterministic|openai (default deterministic)") + writeUsageLine(w, " MEMORY_EMBEDDER_MODEL required when provider=openai") + writeUsageLine(w, " MEMORY_EMBEDDER_DIMENSIONS embedding vector dimensions (default 1536)") + writeUsageLine(w, " MEMORY_EMBED_MAX_BATCH max texts per provider embedding call (default 32)") + writeUsageLine(w, " MEMORY_OPENAI_API_KEY required when provider=openai") + writeUsageLine(w, " MEMORY_OPENAI_BASE_URL optional OpenAI-compatible base URL") +} + +func writeUsageLine(w *os.File, line string) { + _, _ = fmt.Fprintln(w, line) } diff --git a/internal/importer/cursor_agent_jsonl.go b/internal/importer/cursor_agent_jsonl.go index 9bc239d..9a32730 100644 --- a/internal/importer/cursor_agent_jsonl.go +++ b/internal/importer/cursor_agent_jsonl.go @@ -33,7 +33,9 @@ func (CursorAgentJSONLImporter) ImportFile(ctx context.Context, req FileImportRe if err != nil { return nil, fmt.Errorf("open transcript file: %w", err) } - defer file.Close() + defer func() { + _ = file.Close() + }() scanner := bufio.NewScanner(file) scanner.Buffer(make([]byte, 0, 64*1024), 10*1024*1024) diff --git a/internal/server/http.go b/internal/server/http.go index 29cb601..7f7354d 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -615,7 +615,9 @@ func hasSupportedV1ContentType(raw string) bool { func decodeJSONBody(w http.ResponseWriter, r *http.Request, dst any) error { r.Body = http.MaxBytesReader(w, r.Body, maxJSONBodyBytes) - defer r.Body.Close() + defer func() { + _ = r.Body.Close() + }() decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() diff --git a/internal/store/cockroach/helpers.go b/internal/store/cockroach/helpers.go index 97542ec..36e07c5 100644 --- a/internal/store/cockroach/helpers.go +++ b/internal/store/cockroach/helpers.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "strings" - "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" @@ -142,20 +141,6 @@ func scanEpisode(scanner rowScanner) (memory.Episode, error) { return episode, nil } -func cursorFromEpisode(episode memory.Episode) memory.CheckpointCursor { - return memory.CheckpointCursor{ - CreatedAt: episode.CreatedAt, - ID: episode.ID, - } -} - -func optionalTime(value *time.Time) any { - if value == nil { - return nil - } - return *value -} - func scanMemory(scanner rowScanner) (memory.Memory, error) { var ( record memory.Memory diff --git a/internal/store/cockroach/introspection.go b/internal/store/cockroach/introspection.go index b975715..3804244 100644 --- a/internal/store/cockroach/introspection.go +++ b/internal/store/cockroach/introspection.go @@ -99,11 +99,11 @@ func (s *Store) ListProjectionCheckpoints(ctx context.Context, req memory.ListPr args = append(args, req.TenantID) if strings.TrimSpace(req.ProjectionName) != "" { argN++ - builder.WriteString(fmt.Sprintf(" AND projection_name = $%d", argN)) + fmt.Fprintf(&builder, " AND projection_name = $%d", argN) args = append(args, strings.TrimSpace(req.ProjectionName)) } argN++ - builder.WriteString(fmt.Sprintf(" ORDER BY projection_name, shard_id LIMIT $%d", argN)) + fmt.Fprintf(&builder, " ORDER BY projection_name, shard_id LIMIT $%d", argN) args = append(args, limit) rows, err := s.pool.Query(ctx, builder.String(), args...) @@ -159,11 +159,11 @@ func (s *Store) ListConsolidationRuns(ctx context.Context, req memory.ListConsol args = append(args, req.TenantID) if strings.TrimSpace(req.ProjectionName) != "" { argN++ - builder.WriteString(fmt.Sprintf(" AND projection_name = $%d", argN)) + fmt.Fprintf(&builder, " AND projection_name = $%d", argN) args = append(args, strings.TrimSpace(req.ProjectionName)) } argN++ - builder.WriteString(fmt.Sprintf(" ORDER BY created_at DESC LIMIT $%d", argN)) + fmt.Fprintf(&builder, " ORDER BY created_at DESC LIMIT $%d", argN) args = append(args, limit) rows, err := s.pool.Query(ctx, builder.String(), args...) diff --git a/internal/store/cockroach/memories.go b/internal/store/cockroach/memories.go index 8ecc22c..b09e5aa 100644 --- a/internal/store/cockroach/memories.go +++ b/internal/store/cockroach/memories.go @@ -352,27 +352,27 @@ func (s *Store) QueryMemories(ctx context.Context, req memory.QueryMemoriesReque if req.AgentID != nil { argN++ - builder.WriteString(fmt.Sprintf(" AND agent_id = $%d", argN)) + fmt.Fprintf(&builder, " AND agent_id = $%d", argN) args = append(args, *req.AgentID) } if req.ThreadID != nil { argN++ - builder.WriteString(fmt.Sprintf(" AND thread_id = $%d", argN)) + fmt.Fprintf(&builder, " AND thread_id = $%d", argN) args = append(args, *req.ThreadID) } if strings.TrimSpace(req.Kind) != "" { argN++ - builder.WriteString(fmt.Sprintf(" AND kind = $%d", argN)) + fmt.Fprintf(&builder, " AND kind = $%d", argN) args = append(args, strings.TrimSpace(req.Kind)) } if strings.TrimSpace(req.Status) != "" { argN++ - builder.WriteString(fmt.Sprintf(" AND status = $%d", argN)) + fmt.Fprintf(&builder, " AND status = $%d", argN) args = append(args, strings.TrimSpace(req.Status)) } argN++ - builder.WriteString(fmt.Sprintf(" ORDER BY created_at, id LIMIT $%d", argN)) + fmt.Fprintf(&builder, " ORDER BY created_at, id LIMIT $%d", argN) args = append(args, limit) rows, err := s.pool.Query(ctx, builder.String(), args...) diff --git a/internal/store/cockroach/migrate.go b/internal/store/cockroach/migrate.go index ff2823b..0d82598 100644 --- a/internal/store/cockroach/migrate.go +++ b/internal/store/cockroach/migrate.go @@ -24,7 +24,9 @@ func (s *Store) Migrate(ctx context.Context) error { } sqlDB := stdlib.OpenDBFromPool(s.pool) - defer sqlDB.Close() + defer func() { + _ = sqlDB.Close() + }() goose.SetBaseFS(migrationFS) goose.SetLogger(discardLogger{}) diff --git a/internal/store/cockroach/recall.go b/internal/store/cockroach/recall.go index 5b54597..65bfabb 100644 --- a/internal/store/cockroach/recall.go +++ b/internal/store/cockroach/recall.go @@ -27,7 +27,7 @@ func (s *Store) SearchMemories(ctx context.Context, req memory.RecallRequest, qu var ( builder strings.Builder args []any - argN = 1 + argN int ) builder.WriteString(` SELECT m.id, m.tenant_id, m.agent_id, m.thread_id, m.kind, m.status, m.content, m.summary, m.attributes, @@ -41,27 +41,27 @@ func (s *Store) SearchMemories(ctx context.Context, req memory.RecallRequest, qu if req.AgentID != nil { argN++ - builder.WriteString(fmt.Sprintf(" AND me.agent_id = $%d", argN)) + fmt.Fprintf(&builder, " AND me.agent_id = $%d", argN) args = append(args, *req.AgentID) } if req.ThreadID != nil { argN++ - builder.WriteString(fmt.Sprintf(" AND m.thread_id = $%d", argN)) + fmt.Fprintf(&builder, " AND m.thread_id = $%d", argN) args = append(args, *req.ThreadID) } if strings.TrimSpace(req.Kind) != "" { argN++ - builder.WriteString(fmt.Sprintf(" AND m.kind = $%d", argN)) + fmt.Fprintf(&builder, " AND m.kind = $%d", argN) args = append(args, strings.TrimSpace(req.Kind)) } if strings.TrimSpace(req.Status) != "" { argN++ - builder.WriteString(fmt.Sprintf(" AND m.status = $%d", argN)) + fmt.Fprintf(&builder, " AND m.status = $%d", argN) args = append(args, strings.TrimSpace(req.Status)) } argN++ - builder.WriteString(fmt.Sprintf(" ORDER BY me.embedding <=> $1, m.created_at, m.id LIMIT $%d", argN)) + fmt.Fprintf(&builder, " ORDER BY me.embedding <=> $1, m.created_at, m.id LIMIT $%d", argN) args = append(args, limit) rows, err := s.pool.Query(ctx, builder.String(), args...) From 555adccfde5b9491af1660a415cde1c99a73e125 Mon Sep 17 00:00:00 2001 From: Gdetrane Date: Thu, 7 May 2026 16:16:23 +0200 Subject: [PATCH 2/4] Finish CI repair and remove obsolete license options note. Pin GitHub Actions to a patched Go toolchain, simplify the Cockroach startup commands so integration jobs start reliably, and drop the old license-options file and README reference now that Apache-2.0 is the settled license. Co-authored-by: Cursor --- .github/workflows/ci.yml | 18 ++++-------------- .github/workflows/release.yml | 2 +- LICENSE-OPTIONS.md | 10 ---------- README.md | 2 +- 4 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 LICENSE-OPTIONS.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67787e5..f983fb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version: '1.25.9' cache: true - name: Download modules @@ -61,7 +61,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version: '1.25.9' cache: true - name: Download modules @@ -72,20 +72,10 @@ jobs: curl -fsSL https://binaries.cockroachdb.com/cockroach-v26.1.3.linux-amd64.tgz | tar -xz - name: Start CockroachDB - run: | - ./cockroach-v26.1.3.linux-amd64/cockroach start-single-node \ - --insecure \ - --store=type=mem,size=0.25GiB \ - --listen-addr=127.0.0.1:26257 \ - --http-addr=127.0.0.1:8080 \ - --background + run: ./cockroach-v26.1.3.linux-amd64/cockroach start-single-node --insecure --store=type=mem,size=0.25GiB --listen-addr=127.0.0.1:26257 --http-addr=127.0.0.1:8080 --background - name: Wait for CockroachDB - run: | - ./cockroach-v26.1.3.linux-amd64/cockroach sql \ - --insecure \ - --host=127.0.0.1:26257 \ - -e 'select 1' + run: ./cockroach-v26.1.3.linux-amd64/cockroach sql --insecure --host=127.0.0.1:26257 -e 'select 1' - name: Run integration and migration upgrade tests env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 554e363..4cab60f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: go.mod + go-version: '1.25.9' cache: true - name: Verify go.mod and go.sum are tidy diff --git a/LICENSE-OPTIONS.md b/LICENSE-OPTIONS.md deleted file mode 100644 index 0de1e34..0000000 --- a/LICENSE-OPTIONS.md +++ /dev/null @@ -1,10 +0,0 @@ -# License Options - -This repository now uses `Apache-2.0`. - -Final decision: - -- `Apache-2.0`: permissive, business-friendly, explicit patent grant, easiest adoption path. -- `AGPL-3.0` was considered because it would have enforced stronger reciprocity for hosted modified versions. - -`Apache-2.0` was chosen because it is the best fit for broad adoption, clean downstream reuse, and low-friction integration into other open source and commercial systems. diff --git a/README.md b/README.md index 768c4db..9475b52 100644 --- a/README.md +++ b/README.md @@ -506,4 +506,4 @@ The MCP layer uses camelCase tool arguments, consistent with your other MCP serv - Release scope and process notes live in `docs/release.md`. - Deployment guidance lives in `docs/operations/deploy.md`. - Backup/export/restore guidance lives in `docs/operations/backup-restore.md`. -- The repository is licensed under `Apache-2.0`. Historical license notes live in `LICENSE-OPTIONS.md`. +- The repository is licensed under `Apache-2.0`. From e93ea80f49197bd6b0d4be52d60bc880018b2d4f Mon Sep 17 00:00:00 2001 From: Gdetrane Date: Thu, 7 May 2026 16:21:18 +0200 Subject: [PATCH 3/4] Increase Cockroach CI store size for GitHub runners. The current CockroachDB build rejects the 0.25GiB in-memory store size on GitHub-hosted runners because the minimum allowed size is higher. Raise the CI and release workflow store size so the integration job can start the database successfully. Co-authored-by: Cursor --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f983fb1..117a473 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: curl -fsSL https://binaries.cockroachdb.com/cockroach-v26.1.3.linux-amd64.tgz | tar -xz - name: Start CockroachDB - run: ./cockroach-v26.1.3.linux-amd64/cockroach start-single-node --insecure --store=type=mem,size=0.25GiB --listen-addr=127.0.0.1:26257 --http-addr=127.0.0.1:8080 --background + run: ./cockroach-v26.1.3.linux-amd64/cockroach start-single-node --insecure --store=type=mem,size=0.75GiB --listen-addr=127.0.0.1:26257 --http-addr=127.0.0.1:8080 --background - name: Wait for CockroachDB run: ./cockroach-v26.1.3.linux-amd64/cockroach sql --insecure --host=127.0.0.1:26257 -e 'select 1' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4cab60f..5c88768 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: run: | ./cockroach-v26.1.3.linux-amd64/cockroach start-single-node \ --insecure \ - --store=type=mem,size=0.25GiB \ + --store=type=mem,size=0.75GiB \ --listen-addr=127.0.0.1:26257 \ --http-addr=127.0.0.1:8080 \ --background From 829a0e48577ff235f770f8272b1796279cbce63d Mon Sep 17 00:00:00 2001 From: Gdetrane Date: Thu, 7 May 2026 16:25:40 +0200 Subject: [PATCH 4/4] Serialize integration packages in CI. The integration job reuses one Cockroach database across packages, so running `go test ./...` in parallel lets multiple packages race the migration step. Run integration packages with `-p 1` in CI and release workflows so the shared test database stays stable. Co-authored-by: Cursor --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 117a473..34c7697 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,4 +80,4 @@ jobs: - name: Run integration and migration upgrade tests env: COCKROACH_TEST_DATABASE_URL: postgresql://root@127.0.0.1:26257/defaultdb?sslmode=disable - run: go test -count=1 -tags=integration ./... + run: go test -count=1 -p 1 -tags=integration ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5c88768..6100f30 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: - name: Run integration and migration upgrade tests env: COCKROACH_TEST_DATABASE_URL: postgresql://root@127.0.0.1:26257/defaultdb?sslmode=disable - run: go test -count=1 -tags=integration ./... + run: go test -count=1 -p 1 -tags=integration ./... - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6