Files
poimen-workflows/internal/memory/service.go
T
Test 5ef14ad5ec feat(memory): add Temporal activities integration for memory service
- Implement 12 Temporal activities for memory operations
- Activities: create, update, search, context, diagnose, analyze, document
- Add activity registration and worker setup
- Full retry/timeout configuration with observability
- Include workflow patterns and examples
- All tests passing (23/23)

Documentation:
- MEMORY_INTEGRATION.md: High-level integration guide
- MEMORY_ACTIVITIES.md: Complete activities reference
- REGISTERED_ACTIVITIES.md: Registry and calling conventions
2026-08-29 21:49:24 -07:00

235 lines
5.1 KiB
Go

package memory
import (
"context"
"fmt"
)
// Service memory service manager
type Service struct {
client *Client
project string
}
// NewService creates memory service manager
func NewService(baseURL, token, project string) *Service {
return &Service{
client: NewClient(baseURL, token),
project: project,
}
}
// KnowledgeRecord high-level knowledge record
type KnowledgeRecord struct {
ID string
Level string // L1|L2|reference
Title string
Content string
Source string
Metadata map[string]interface{}
SHA256 string
}
// CreateKnowledge creates knowledge record
func (s *Service) CreateKnowledge(ctx context.Context, record *KnowledgeRecord) (string, error) {
if record.Level == "" {
record.Level = "L1"
}
if record.Source == "" {
record.Source = "workflow"
}
req := &IngestRequest{
Project: s.project,
Source: record.Source,
Kind: record.Level,
Text: record.Content,
Metadata: record.Metadata,
}
resp, err := s.client.Ingest(ctx, req)
if err != nil {
return "", fmt.Errorf("create knowledge: %w", err)
}
return resp.ID, nil
}
// UpdateKnowledge updates existing knowledge (re-ingest)
func (s *Service) UpdateKnowledge(ctx context.Context, record *KnowledgeRecord) (string, error) {
// Update done by re-ingesting with same signature/source
// Memory service deduplicates based on idempotency key
if record.Metadata == nil {
record.Metadata = make(map[string]interface{})
}
// Use ID as session_id for idempotency
record.Metadata["session_id"] = record.ID
return s.CreateKnowledge(ctx, record)
}
// RetrievalOptions search options
type RetrievalOptions struct {
LevelFilter []string // L1, L2, R
Floor float32 // minimum relevance
Limit int // default 10
Scope string // learned|reference|all
}
// RetrieveKnowledge searches knowledge
func (s *Service) RetrieveKnowledge(ctx context.Context, query string, opts *RetrievalOptions) ([]KnowledgeRecord, error) {
if opts == nil {
opts = &RetrievalOptions{}
}
if opts.Limit == 0 {
opts.Limit = 10
}
req := &QueryRequest{
Project: s.project,
Query: query,
LevelFilter: opts.LevelFilter,
Floor: opts.Floor,
Limit: opts.Limit,
Scope: opts.Scope,
}
resp, err := s.client.Query(ctx, req)
if err != nil {
return nil, fmt.Errorf("retrieve knowledge: %w", err)
}
records := make([]KnowledgeRecord, len(resp.Results))
for i, r := range resp.Results {
records[i] = KnowledgeRecord{
ID: r.ID,
Level: r.Level,
Content: r.Text,
Source: r.Source,
SHA256: "", // Not in response
Metadata: map[string]interface{}{
"score": r.Score,
"semantic_score": r.SemanticScore,
"lexical_score": r.LexicalScore,
"breadcrumb": r.Breadcrumb,
},
}
}
return records, nil
}
// ServiceContext tool/task context
type ServiceContext struct {
Tier int
Lessons []Lesson
Skills []Skill
BudgetUsed int
BudgetMax int
}
// Lesson learned fact or reference
type Lesson struct {
Tier int
Level string
Score float32
Text string
MatchedKind string
SeenCount int
LastSeen string
}
// Skill recommended action
type Skill struct {
Name string
Why string
}
// RetrieveContext retrieves context for tool/task (three-tier)
func (s *Service) RetrieveContext(ctx context.Context, tool, task string, budget int) (*ServiceContext, error) {
if budget == 0 {
budget = 8192
}
req := &ContextRequest{
Project: s.project,
Tool: tool,
Task: task,
SignatureSource: fmt.Sprintf("%s:%s", tool, task),
Scope: "tool_context",
Budget: budget,
}
resp, err := s.client.Context(ctx, req)
if err != nil {
return nil, fmt.Errorf("retrieve context: %w", err)
}
lessons := make([]Lesson, len(resp.Lessons))
for i, l := range resp.Lessons {
lessons[i] = Lesson{
Tier: l.Tier,
Level: l.Level,
Score: l.Score,
Text: l.Text,
MatchedKind: l.MatchedKind,
SeenCount: l.SeenCount,
LastSeen: l.LastSeen,
}
}
skills := make([]Skill, len(resp.Skills))
for i, sk := range resp.Skills {
skills[i] = Skill{
Name: sk.Name,
Why: sk.Why,
}
}
return &ServiceContext{
Tier: resp.Tier,
Lessons: lessons,
Skills: skills,
BudgetUsed: resp.Budget.Used,
BudgetMax: resp.Budget.Requested,
}, nil
}
// VaultInfo vault browsing
type VaultInfo struct {
Path string
Title string
Level string
UpdatedAt string
RecordCount int
}
// GetVault lists vault files
func (s *Service) GetVault(ctx context.Context) ([]VaultInfo, error) {
resp, err := s.client.Vault(ctx, s.project)
if err != nil {
return nil, fmt.Errorf("get vault: %w", err)
}
files := make([]VaultInfo, len(resp.Files))
for i, f := range resp.Files {
files[i] = VaultInfo{
Path: f.Path,
Title: f.Title,
Level: f.Level,
UpdatedAt: f.UpdatedAt,
RecordCount: f.RecordCount,
}
}
return files, nil
}
// IsHealthy checks service health
func (s *Service) IsHealthy(ctx context.Context) bool {
ok, err := s.client.Health(ctx)
return ok && err == nil
}