Skip to content
Merged
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
34 changes: 14 additions & 20 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,11 @@ name: pr-full-ci
# job 3 (sequential, needs 1+2): docker build smoke-test (no push)
#
# ⚠️ SECRETS REQUIRED — must be provisioned by Siri/BMO before enabling as required check:
# ENGRAM_OPENAI_API_KEYOpenAI embeddings (text-embedding-3-small, dim 1536)
# ENGRAM_VOYAGE_API_KEYVoyage embeddings (voyage-3.5, dim 1024)
# pigo CANNOT add this; must be set by Siri/BMO in
# GitHub → Settings → Secrets and variables → Actions
# ENGRAM_API_KEY — bearer token for engram server auth (harness uses this)
# harness has a hardcoded default fallback, but CI should set its own
#
# NOTE: eval/harness/harness.py currently defaults to Voyage embedder.
# We override to OpenAI via env vars below (deterministic, 1536-dim gate).
# ENGRAM_VOYAGE_API_KEY is NOT needed when ENGRAM_EMBEDDER_PROVIDER=openai.

on:
pull_request:
Expand Down Expand Up @@ -49,12 +45,11 @@ jobs:
# Engram server: connect to service-container Qdrant (no auth needed)
ENGRAM_QDRANT_URL: localhost:6334
QDRANT_URL: http://localhost:6333 # harness.py uses this form
# Embedder: standardized on OpenAI for determinism
ENGRAM_OPENAI_API_KEY: ${{ secrets.ENGRAM_OPENAI_API_KEY }}
ENGRAM_OPENAI_BASE_URL: https://api.openai.com/v1
ENGRAM_EMBEDDER_PROVIDER: openai
ENGRAM_EMBEDDING_MODEL: text-embedding-3-small
ENGRAM_EMBEDDING_DIMENSION: "1536"
# Embedder: Voyage (matches prod)
ENGRAM_VOYAGE_API_KEY: ${{ secrets.ENGRAM_VOYAGE_API_KEY }}
ENGRAM_EMBEDDER_PROVIDER: voyage
ENGRAM_EMBEDDING_MODEL: voyage-3.5
ENGRAM_EMBEDDING_DIMENSION: "1024"
# Auth: set server + integration client to same token
ENGRAM_API_KEY: ${{ secrets.ENGRAM_API_KEY }}

Expand Down Expand Up @@ -87,7 +82,7 @@ jobs:
- name: Integration tests
run: ./integration_test.sh
# integration_test.sh spins up the binary via coproc, runs MCP-over-stdio tests
# Hard-fails (exit 1) if ENGRAM_OPENAI_API_KEY is not set
# Hard-fails (exit 1) if ENGRAM_VOYAGE_API_KEY is not set

# ── Job 2: Eval gate 26/26 ───────────────────────────────────────────────────
eval-gate:
Expand All @@ -105,12 +100,11 @@ jobs:
env:
ENGRAM_QDRANT_URL: localhost:6334
QDRANT_URL: http://localhost:6333
# ⚠️ Override harness default (Voyage) → OpenAI for deterministic gate
ENGRAM_OPENAI_API_KEY: ${{ secrets.ENGRAM_OPENAI_API_KEY }}
ENGRAM_OPENAI_BASE_URL: https://api.openai.com/v1
ENGRAM_EMBEDDER_PROVIDER: openai
ENGRAM_EMBEDDING_MODEL: text-embedding-3-small
ENGRAM_EMBEDDING_DIMENSION: "1536"
# Embedder: Voyage (matches prod)
ENGRAM_VOYAGE_API_KEY: ${{ secrets.ENGRAM_VOYAGE_API_KEY }}
ENGRAM_EMBEDDER_PROVIDER: voyage
ENGRAM_EMBEDDING_MODEL: voyage-3.5
ENGRAM_EMBEDDING_DIMENSION: "1024"
ENGRAM_API_KEY: ${{ secrets.ENGRAM_API_KEY }}
ENGRAM_URL: http://localhost:8080

Expand Down Expand Up @@ -212,7 +206,7 @@ jobs:
cache-to: type=gha,mode=max

# ── Branch protection note (configured by Siri/BMO, not YAML) ─────────────────
# After ENGRAM_OPENAI_API_KEY is confirmed provisioned (§4.5 verification):
# After ENGRAM_VOYAGE_API_KEY is confirmed provisioned (§4.5 verification):
# Settings → Branches → main → Require status checks:
# ✅ "Integration Tests (race + Qdrant)"
# ✅ "Eval Gate (26/26)"
Expand All @@ -221,5 +215,5 @@ jobs:
# ✅ Include administrators
# ❌ Allow force pushes
#
# ⚠️ DO NOT make checks required before ENGRAM_OPENAI_API_KEY is provisioned —
# ⚠️ DO NOT make checks required before ENGRAM_VOYAGE_API_KEY is provisioned —
# otherwise every PR blocks permanently (integration test exits 1 without key).
18 changes: 18 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# golangci-lint v2 configuration for engram.
#
# We fix the codebase against golangci-lint v2.x. The CI workflow (see
# .github/workflows/push.yml) runs golangci-lint v2.12.2 via
# golangci-lint-action@v8, matching this config's `version: "2"` schema and
# go.mod's go1.25. The former v1.62 pin was incompatible with a v2 config and
# go1.25 (config-load / go-version errors) and has been removed.
#
# No linter is blanket-disabled to fake a pass. The four linters below are the
# ones that flagged real issues on master; all violations were fixed in code.
version: "2"

linters:
enable:
- errcheck # unchecked error returns
- govet # suspicious constructs
- staticcheck # bugs, deprecations, simplifications
- unused # dead code
2 changes: 1 addition & 1 deletion cmd/backfill_reflection_tag/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func main() {
fmt.Fprintf(os.Stderr, "connect qdrant: %v\n", err)
os.Exit(1)
}
defer store.Close()
defer func() { _ = store.Close() }()

res, err := run(context.Background(), store, opts)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/backfill_valid_until/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func main() {
fmt.Fprintf(os.Stderr, "connect qdrant: %v\n", err)
os.Exit(1)
}
defer store.Close()
defer func() { _ = store.Close() }()

res, err := run(context.Background(), store, opts)
if err != nil {
Expand Down
32 changes: 16 additions & 16 deletions cmd/engram/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func serve(cfg *config.Config) error {
return fmt.Errorf("init tracing: %w", err)
}
if tp != nil {
defer tp.Shutdown(context.Background())
defer func() { _ = tp.Shutdown(context.Background()) }()
}

fmt.Fprintf(os.Stderr, "Starting Engram server (transport: %s)\n", cfg.Transport)
Expand All @@ -127,7 +127,7 @@ func serve(cfg *config.Config) error {
storeMap[col] = s
}
store := qdrant.NewMultiStore(storeMap, collection.CollectionUser)
defer store.Close()
defer func() { _ = store.Close() }()

// Ensure all physical collections exist.
ctx := context.Background()
Expand Down Expand Up @@ -238,7 +238,7 @@ func dreamCheck(cfg *config.Config) error {
}
return dream.PrintGateResult(result)
}
defer store.Close()
defer func() { _ = store.Close() }()

result, err := dream.CheckGates(store)
if err != nil {
Expand All @@ -253,7 +253,7 @@ func dreamRun(cfg *config.Config) error {
return fmt.Errorf("init tracing: %w", err)
}
if tp != nil {
defer tp.Shutdown(context.Background())
defer func() { _ = tp.Shutdown(context.Background()) }()
}

// Parse flags from os.Args[2:].
Expand All @@ -280,7 +280,7 @@ func dreamRun(cfg *config.Config) error {
if err != nil {
return fmt.Errorf("connect qdrant: %w", err)
}
defer store.Close()
defer func() { _ = store.Close() }()

ctx := context.Background()
if err := store.EnsureCollection(ctx); err != nil {
Expand Down Expand Up @@ -341,7 +341,7 @@ func reflectionCheck(cfg *config.Config) error {
if err != nil {
return fmt.Errorf("connect qdrant: %w", err)
}
defer store.Close()
defer func() { _ = store.Close() }()

eng := reflection.NewEngine(store, nil, reflection.DefaultConfig())
ctx := context.Background()
Expand All @@ -363,7 +363,7 @@ func reflectionRun(cfg *config.Config) error {
return fmt.Errorf("init tracing: %w", err)
}
if tp != nil {
defer tp.Shutdown(context.Background())
defer func() { _ = tp.Shutdown(context.Background()) }()
}

dryRun := false
Expand All @@ -390,7 +390,7 @@ func reflectionRun(cfg *config.Config) error {
if err != nil {
return fmt.Errorf("connect qdrant: %w", err)
}
defer store.Close()
defer func() { _ = store.Close() }()

ctx := context.Background()
if err := store.EnsureCollection(ctx); err != nil {
Expand Down Expand Up @@ -468,7 +468,7 @@ func migrateReflected(cfg *config.Config) error {
if err != nil {
return fmt.Errorf("connect qdrant: %w", err)
}
defer store.Close()
defer func() { _ = store.Close() }()

ctx := context.Background()
if err := store.EnsureCollection(ctx); err != nil {
Expand Down Expand Up @@ -599,7 +599,7 @@ func migrateCollections(cfg *config.Config) error {
if err != nil {
return fmt.Errorf("connect legacy store: %w", err)
}
defer legacyStore.Close()
defer func() { _ = legacyStore.Close() }()

// Target stores — one per new collection.
targetStoreMap := map[string]*qdrant.Store{}
Expand All @@ -609,7 +609,7 @@ func migrateCollections(cfg *config.Config) error {
if err != nil {
return fmt.Errorf("connect target store %s: %w", col, err)
}
defer s.Close()
defer func() { _ = s.Close() }()
targetStoreMap[col] = s
}

Expand Down Expand Up @@ -763,10 +763,10 @@ func dropLegacy(cfg *config.Config) error {
return fmt.Errorf("connect store %s: %w", col, err)
}
if err := s.DropCollection(ctx); err != nil {
s.Close()
_ = s.Close()
return fmt.Errorf("drop collection %s: %w", col, err)
}
s.Close()
_ = s.Close()
fmt.Fprintf(os.Stderr, "drop-legacy: deleted collection %q\n", col)
}
return nil
Expand Down Expand Up @@ -840,7 +840,7 @@ func migrateExtraCollections(cfg *config.Config) error {
if err != nil {
return fmt.Errorf("connect target store %s: %w", targetName, err)
}
defer targetStore.Close()
defer func() { _ = targetStore.Close() }()
if !dryRun {
if err := targetStore.EnsureCollection(ctx); err != nil {
return fmt.Errorf("ensure target collection %s: %w", targetName, err)
Expand Down Expand Up @@ -888,7 +888,7 @@ func migrateExtraCollections(cfg *config.Config) error {
Offset: offset,
})
if err != nil {
srcStore.Close()
_ = srcStore.Close()
return fmt.Errorf("scroll %s: %w", srcName, err)
}
if len(batch) == 0 {
Expand Down Expand Up @@ -949,7 +949,7 @@ func migrateExtraCollections(cfg *config.Config) error {
}
offset = next
}
srcStore.Close()
_ = srcStore.Close()
}

result := map[string]any{
Expand Down
2 changes: 1 addition & 1 deletion cmd/migrate/w17_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func run() error {
if err != nil {
return fmt.Errorf("connect qdrant: %w", err)
}
defer store.Close()
defer func() { _ = store.Close() }()

ctx := context.Background()
if err := store.EnsureCollection(ctx); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/otel/exporter_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (w *rotatingWriter) Write(p []byte) (int, error) {

func (w *rotatingWriter) rotate(date string) error {
if w.file != nil {
w.file.Close()
_ = w.file.Close()
}
path := filepath.Join(w.dir, fmt.Sprintf("engram-traces-%s.jsonl", date))
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
Expand Down
8 changes: 4 additions & 4 deletions internal/otel/exporter_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestFileExporter_WritesJSONL(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer exp.Shutdown(context.Background())
defer func() { _ = exp.Shutdown(context.Background()) }()

stubs := tracetest.SpanStubs{
{Name: "span-1", SpanKind: trace.SpanKindInternal},
Expand All @@ -39,7 +39,7 @@ func TestFileExporter_WritesJSONL(t *testing.T) {
if err != nil {
t.Fatalf("open trace file: %v", err)
}
defer f.Close()
defer func() { _ = f.Close() }()

var lineCount int
scanner := bufio.NewScanner(f)
Expand Down Expand Up @@ -74,7 +74,7 @@ func TestFileExporter_FileNaming(t *testing.T) {
if err := exp.ExportSpans(context.Background(), stubs.Snapshots()); err != nil {
t.Fatal(err)
}
exp.Shutdown(context.Background())
_ = exp.Shutdown(context.Background())

today := time.Now().Format("2006-01-02")
expected := "engram-traces-" + today + ".jsonl"
Expand All @@ -94,7 +94,7 @@ func TestFileExporter_EmptyBatch(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer exp.Shutdown(context.Background())
defer func() { _ = exp.Shutdown(context.Background()) }()

if err := exp.ExportSpans(context.Background(), nil); err != nil {
t.Fatalf("ExportSpans with nil should not error: %v", err)
Expand Down
6 changes: 3 additions & 3 deletions internal/otel/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestNewTracerProvider_Disabled(t *testing.T) {
}
if tp != nil {
t.Error("expected nil TracerProvider when disabled")
tp.Shutdown(context.Background())
_ = tp.Shutdown(context.Background())
}

_, isNoop := otel.GetTracerProvider().(noop.TracerProvider)
Expand All @@ -33,7 +33,7 @@ func TestNewTracerProvider_ExporterNone(t *testing.T) {
}
if tp != nil {
t.Error("expected nil TracerProvider for exporter=none")
tp.Shutdown(context.Background())
_ = tp.Shutdown(context.Background())
}

_, isNoop := otel.GetTracerProvider().(noop.TracerProvider)
Expand All @@ -58,7 +58,7 @@ func TestNewTracerProvider_File(t *testing.T) {
if tp == nil {
t.Fatal("expected non-nil TracerProvider for file exporter")
}
defer tp.Shutdown(context.Background())
defer func() { _ = tp.Shutdown(context.Background()) }()

_, isNoop := otel.GetTracerProvider().(noop.TracerProvider)
if isNoop {
Expand Down
14 changes: 7 additions & 7 deletions pkg/dream/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,20 +486,20 @@ func (e *Engine) generateInsight(ctx context.Context, tag string, group []memory

// Build compact event summaries for the prompt.
var sb strings.Builder
sb.WriteString(fmt.Sprintf(
fmt.Fprintf(&sb,
"You are a memory consolidation AI. Below are %d related memory events tagged %q, "+
"recorded over the past 7 days. Synthesize them into a single concise insight (2-4 sentences). "+
"Focus on patterns, trends, or key conclusions — not just summarizing individual events. "+
"Write in English, third-person style (e.g. \"Siri has been...\", \"Frank tends to...\").\n\n",
len(group), tag,
))
)
sb.WriteString("Events:\n")
for i, m := range group {
content := m.Content
if len(content) > 200 {
content = content[:200] + "..."
}
sb.WriteString(fmt.Sprintf("%d. [%s] %s\n", i+1, time.Unix(int64(m.CreatedAt), 0).Format("2006-01-02"), content))
fmt.Fprintf(&sb, "%d. [%s] %s\n", i+1, time.Unix(int64(m.CreatedAt), 0).Format("2006-01-02"), content)
}
sb.WriteString("\nInsight:")

Expand Down Expand Up @@ -768,7 +768,7 @@ func (e *Engine) skillDiff(ctx context.Context) (string, []string, error) {

// Build Haiku prompt.
var sb strings.Builder
sb.WriteString(fmt.Sprintf(
fmt.Fprintf(&sb,
"You are a skill improvement analyst for an AI agent named Siri. "+
"Below is the current SKILL.md for '%s' and a list of recent insights/directives.\n\n"+
"Task: generate a structured diff proposal for this skill file.\n"+
Expand All @@ -779,13 +779,13 @@ func (e *Engine) skillDiff(ctx context.Context) (string, []string, error) {
"**Reasoning:** <1-2 sentences>\n\n"+
"Only propose changes clearly supported by the insights. If no changes are needed, write \"No changes needed.\"\n\n",
c.name, c.name,
))
)

skillMDSnippet := c.skillMD
if len(skillMDSnippet) > 1500 {
skillMDSnippet = skillMDSnippet[:1500] + "\n...[truncated]"
}
sb.WriteString(fmt.Sprintf("### Current SKILL.md:\n```\n%s\n```\n\n", skillMDSnippet))
fmt.Fprintf(&sb, "### Current SKILL.md:\n```\n%s\n```\n\n", skillMDSnippet)

sb.WriteString("### Recent Insights & Directives:\n")
limit := 8
Expand All @@ -797,7 +797,7 @@ func (e *Engine) skillDiff(ctx context.Context) (string, []string, error) {
if len(content) > 200 {
content = content[:200] + "..."
}
sb.WriteString(fmt.Sprintf("%d. [%s] %s\n", i+1, m.Type, content))
fmt.Fprintf(&sb, "%d. [%s] %s\n", i+1, m.Type, content)
}
sb.WriteString("\nDiff proposal:")

Expand Down
Loading
Loading