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
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user