- 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
235 lines
5.1 KiB
Go
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
|
|
}
|