Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/ai/memory.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 43d56387270ef4da4cabd794d97ca6a49909bc7d |
| Branch | w3-ai-staffing-recommendations |
| Path or archive member | internal/modules/ai/memory.go |
| Original SHA-256 | bfe241262eb381116bfd4ae6b81a4cfa12cbc8694855a45198825b45d2fc5397 |
| Git blob SHA | 74ab31046bb150bcb0bc675dbb86cd4eed699394 |
| Reading-copy SHA-256 | bfe241262eb381116bfd4ae6b81a4cfa12cbc8694855a45198825b45d2fc5397 |
Every recorded occurrence
These 3 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/modules/ai/memory.go | w3-ai-staffing-recommendations | 43d56387270ef4da4cabd794d97ca6a49909bc7d | Open original |
| Janady13/missionmatch / internal/modules/ai/memory.go | ai-intelligence-layer | 5d5ddff70393a197c5f54a45280bb5dd42b24755 | Open original |
| Janady13/missionmatch / internal/modules/ai/memory.go | w3-ai-staffing-recommendations | 43d56387270ef4da4cabd794d97ca6a49909bc7d | Open original |
Recovered source transcript
Download complete source text ↓124 lines · 4.0 KB
package ai
import (
"context"
"errors"
"strconv"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/missionworkforce/missionmatch/internal/platform/inference"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
// Memory is the per tenant episodic and semantic memory, stored in the tenant's own database (ai_memory)
// and therefore physically isolated from every other tenant. Episodic memory records what happened (a
// craft note about a turn); semantic memory holds a recallable fact with an embedding. No demographic
// data may enter memory: every write is eeo guarded. Memory about a candidate carries the candidate id,
// so the right to erasure wipes it (ForgetCandidate), the same way anonymize wipes the resume vector.
type Memory struct {
db *tenant.DB
client *inference.Client
}
// NewMemory builds the per tenant memory over the tenant database and the inference client (for semantic
// recall embeddings).
func NewMemory(db *tenant.DB, client *inference.Client) *Memory {
return &Memory{db: db, client: client}
}
// Store writes one memory. candidateID may be nil for a non candidate craft note. content is eeo guarded.
// A semantic memory is embedded so it can be recalled by similarity.
func (m *Memory) Store(ctx context.Context, candidateID *uuid.UUID, kind, content string) error {
if m.db == nil {
return errors.New("ai: no database")
}
if kind != "episodic" && kind != "semantic" {
return errors.New("ai: memory kind must be episodic or semantic")
}
if err := ForbidEEO(content); err != nil {
return err
}
var vecLit any
if kind == "semantic" && m.client != nil {
emb, err := m.client.Embed(ctx, []string{content})
if err != nil {
return err
}
vecLit = vectorLiteral(emb.Embeddings[0])
}
return m.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
_, err := tx.Exec(ctx,
"INSERT INTO ai_memory (organization_id, candidate_id, kind, content, embedding) "+
"VALUES (NULLIF(current_setting('app.current_org_id', true),'')::uuid, $1, $2, $3, $4::vector)",
candidateID, kind, content, vecLit)
return err
})
}
// ForgetCandidate is the erasure hook: it deletes every memory about a candidate, so anonymize leaves no
// semantic trace in memory. It is called from the right to erasure path alongside the candidate
// anonymize. Idempotent.
func (m *Memory) ForgetCandidate(ctx context.Context, candidateID uuid.UUID) error {
if m.db == nil {
return errors.New("ai: no database")
}
return m.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
_, err := tx.Exec(ctx, "DELETE FROM ai_memory WHERE candidate_id = $1", candidateID)
return err
})
}
// Recall returns the semantically closest memories to a query (RLS scoped, eeo guarded). It is the
// retrieval side of memory augmented generation.
func (m *Memory) Recall(ctx context.Context, query string, limit int) ([]string, error) {
if m.db == nil || m.client == nil {
return nil, errors.New("ai: memory recall needs a database and a client")
}
if err := ForbidEEO(query); err != nil {
return nil, err
}
if limit <= 0 || limit > 50 {
limit = 5
}
emb, err := m.client.Embed(ctx, []string{query})
if err != nil {
return nil, err
}
lit := vectorLiteral(emb.Embeddings[0])
var out []string
err = m.db.RunInTenantTx(ctx, func(ctx context.Context, tx pgx.Tx) error {
rows, err := tx.Query(ctx,
"SELECT content FROM ai_memory WHERE embedding IS NOT NULL ORDER BY embedding <=> $1::vector LIMIT $2",
lit, limit)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var c string
if err := rows.Scan(&c); err != nil {
return err
}
out = append(out, c)
}
return rows.Err()
})
return out, err
}
// vectorLiteral formats a 384 dim vector as a pgvector text literal for the $::vector cast.
func vectorLiteral(v []float32) string {
b := make([]byte, 0, len(v)*8+2)
b = append(b, '[')
for i, x := range v {
if i > 0 {
b = append(b, ',')
}
b = strconv.AppendFloat(b, float64(x), 'f', -1, 32)
}
b = append(b, ']')
return string(b)
}