refactor(cortex): update bulletin, drift, prioritize, rl tasks
This commit is contained in:
parent
7efb45854d
commit
c79d6e1e26
5 changed files with 163 additions and 32 deletions
|
|
@ -27,6 +27,9 @@ type BulletinStore interface {
|
||||||
|
|
||||||
// GetLastBulletin retrieves the most recent bulletin
|
// GetLastBulletin retrieves the most recent bulletin
|
||||||
GetLastBulletin(ctx context.Context, agentID string) (*DailyBulletin, error)
|
GetLastBulletin(ctx context.Context, agentID string) (*DailyBulletin, error)
|
||||||
|
|
||||||
|
// ListActiveAgents returns all agent IDs with recent activity
|
||||||
|
ListActiveAgents(ctx context.Context, since time.Time) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LLMClient provides LLM generation capabilities.
|
// LLMClient provides LLM generation capabilities.
|
||||||
|
|
@ -97,17 +100,42 @@ func (t *BulletinTask) Interval() time.Duration {
|
||||||
return t.cfg.GenerateInterval
|
return t.cfg.GenerateInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute generates the daily bulletin.
|
// Execute generates the daily bulletin for all active agents.
|
||||||
func (t *BulletinTask) Execute(ctx context.Context) error {
|
func (t *BulletinTask) Execute(ctx context.Context) error {
|
||||||
logger.InfoCF("cortex", "Generating daily bulletin", map[string]interface{}{"task": "bulletin"})
|
logger.InfoCF("cortex", "Generating daily bulletin", map[string]interface{}{"task": "bulletin"})
|
||||||
|
|
||||||
// For now, process a single agent (in production, iterate over all agents)
|
// Get all active agents
|
||||||
agentID := "default" // TODO: Get from context or iterate
|
since := time.Now().Add(-t.cfg.LookbackWindow)
|
||||||
|
agents, err := t.store.ListActiveAgents(ctx, since)
|
||||||
|
if err != nil {
|
||||||
|
logger.WarnCF("cortex", "Failed to list active agents, falling back to default",
|
||||||
|
map[string]interface{}{"error": err.Error()})
|
||||||
|
agents = []string{"default"}
|
||||||
|
}
|
||||||
|
if len(agents) == 0 {
|
||||||
|
logger.DebugCF("cortex", "No active agents found, skipping bulletin generation", nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process bulletin for each agent
|
||||||
|
for _, agentID := range agents {
|
||||||
|
if err := t.processAgentBulletin(ctx, agentID); err != nil {
|
||||||
|
logger.WarnCF("cortex", "Failed to generate bulletin for agent",
|
||||||
|
map[string]interface{}{"agent_id": agentID, "error": err.Error()})
|
||||||
|
// Continue with other agents - don't let one failure stop the batch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processAgentBulletin generates and stores a bulletin for a single agent.
|
||||||
|
func (t *BulletinTask) processAgentBulletin(ctx context.Context, agentID string) error {
|
||||||
// Check if we need to generate
|
// Check if we need to generate
|
||||||
last, err := t.store.GetLastBulletin(ctx, agentID)
|
last, err := t.store.GetLastBulletin(ctx, agentID)
|
||||||
if err == nil && last != nil && time.Since(last.GeneratedAt) < t.cfg.GenerateInterval {
|
if err == nil && last != nil && time.Since(last.GeneratedAt) < t.cfg.GenerateInterval {
|
||||||
logger.DebugCF("cortex", "Bulletin still fresh, skipping", map[string]interface{}{"last_generated": last.GeneratedAt})
|
logger.DebugCF("cortex", "Bulletin still fresh, skipping",
|
||||||
|
map[string]interface{}{"agent_id": agentID, "last_generated": last.GeneratedAt})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,8 +151,9 @@ func (t *BulletinTask) Execute(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.DebugCF("cortex", "Bulletin generated successfully", map[string]interface{}{
|
logger.DebugCF("cortex", "Bulletin generated successfully", map[string]interface{}{
|
||||||
"task": "bulletin",
|
"task": "bulletin",
|
||||||
"tokens": bulletin.Tokens,
|
"agent_id": agentID,
|
||||||
|
"tokens": bulletin.Tokens,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,9 @@ type DriftStore interface {
|
||||||
|
|
||||||
// GetDriftStatus retrieves the current drift status for a domain
|
// GetDriftStatus retrieves the current drift status for a domain
|
||||||
GetDriftStatus(ctx context.Context, agentID string, domain string) (*DomainDrift, error)
|
GetDriftStatus(ctx context.Context, agentID string, domain string) (*DomainDrift, error)
|
||||||
|
|
||||||
|
// ListActiveAgents returns all agent IDs with recent activity
|
||||||
|
ListActiveAgents(ctx context.Context, since time.Time) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DomainActivity holds raw activity counts.
|
// DomainActivity holds raw activity counts.
|
||||||
|
|
@ -129,20 +132,55 @@ func (t *DriftTask) Interval() time.Duration {
|
||||||
return t.cfg.CheckInterval
|
return t.cfg.CheckInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute performs drift detection across all domains.
|
// Execute performs drift detection across all agents and their domains.
|
||||||
func (t *DriftTask) Execute(ctx context.Context) error {
|
func (t *DriftTask) Execute(ctx context.Context) error {
|
||||||
logger.InfoCF("cortex", "Running drift detection", map[string]interface{}{"task": "drift"})
|
logger.InfoCF("cortex", "Running drift detection", map[string]interface{}{"task": "drift"})
|
||||||
|
|
||||||
agentID := "default" // TODO: Iterate over all agents
|
// Get all active agents
|
||||||
|
since := time.Now().Add(-t.cfg.ActivityWindow)
|
||||||
|
agents, err := t.store.ListActiveAgents(ctx, since)
|
||||||
|
if err != nil {
|
||||||
|
logger.WarnCF("cortex", "Failed to list active agents, falling back to default",
|
||||||
|
map[string]interface{}{"error": err.Error()})
|
||||||
|
agents = []string{"default"}
|
||||||
|
}
|
||||||
|
if len(agents) == 0 {
|
||||||
|
logger.DebugCF("cortex", "No active agents found, skipping drift detection", nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Get all domains
|
// Process drift detection for each agent
|
||||||
|
var totalDriftDetected int
|
||||||
|
for _, agentID := range agents {
|
||||||
|
count, err := t.processAgentDrift(ctx, agentID)
|
||||||
|
if err != nil {
|
||||||
|
logger.WarnCF("cortex", "Failed to process drift for agent",
|
||||||
|
map[string]interface{}{"agent_id": agentID, "error": err.Error()})
|
||||||
|
// Continue with other agents
|
||||||
|
}
|
||||||
|
totalDriftDetected += count
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.DebugCF("cortex", "Drift detection complete", map[string]interface{}{
|
||||||
|
"task": "drift",
|
||||||
|
"agents_checked": len(agents),
|
||||||
|
"drift_detected": totalDriftDetected,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processAgentDrift performs drift detection for a single agent.
|
||||||
|
func (t *DriftTask) processAgentDrift(ctx context.Context, agentID string) (int, error) {
|
||||||
|
// Get all domains for this agent
|
||||||
domains, err := t.store.GetDomains(ctx, agentID)
|
domains, err := t.store.GetDomains(ctx, agentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get domains: %w", err)
|
return 0, fmt.Errorf("get domains for agent %s: %w", agentID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.DebugCF("cortex", "Checking drift for domains", map[string]interface{}{
|
logger.DebugCF("cortex", "Checking drift for domains", map[string]interface{}{
|
||||||
"task": "drift",
|
"task": "drift",
|
||||||
|
"agent_id": agentID,
|
||||||
"domain_count": len(domains),
|
"domain_count": len(domains),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -151,16 +189,18 @@ func (t *DriftTask) Execute(ctx context.Context) error {
|
||||||
drift, err := t.analyzeDomain(ctx, agentID, domain)
|
drift, err := t.analyzeDomain(ctx, agentID, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.DebugCF("cortex", "Failed to analyze domain", map[string]interface{}{
|
logger.DebugCF("cortex", "Failed to analyze domain", map[string]interface{}{
|
||||||
"error": err,
|
"error": err,
|
||||||
"domain": domain,
|
"agent_id": agentID,
|
||||||
|
"domain": domain,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := t.store.StoreDriftStatus(ctx, drift); err != nil {
|
if err := t.store.StoreDriftStatus(ctx, drift); err != nil {
|
||||||
logger.DebugCF("cortex", "Failed to store drift status", map[string]interface{}{
|
logger.DebugCF("cortex", "Failed to store drift status", map[string]interface{}{
|
||||||
"error": err,
|
"error": err,
|
||||||
"domain": domain,
|
"agent_id": agentID,
|
||||||
|
"domain": domain,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -177,13 +217,14 @@ func (t *DriftTask) Execute(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.DebugCF("cortex", "Drift detection complete", map[string]interface{}{
|
logger.DebugCF("cortex", "Drift detection complete for agent", map[string]interface{}{
|
||||||
"task": "drift",
|
"task": "drift",
|
||||||
|
"agent_id": agentID,
|
||||||
"domains_checked": len(domains),
|
"domains_checked": len(domains),
|
||||||
"drift_detected": driftDetected,
|
"drift_detected": driftDetected,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return driftDetected, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *DriftTask) analyzeDomain(ctx context.Context, agentID, domain string) (*DomainDrift, error) {
|
func (t *DriftTask) analyzeDomain(ctx context.Context, agentID, domain string) (*DomainDrift, error) {
|
||||||
|
|
@ -468,3 +509,8 @@ func (m *MemoryDriftAdapter) GetDriftStatus(ctx context.Context, agentID string,
|
||||||
Score: 0.5,
|
Score: 0.5,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MemoryDriftAdapter) ListActiveAgents(ctx context.Context, since time.Time) ([]string, error) {
|
||||||
|
// TODO: Query memory store for actual active agents
|
||||||
|
return []string{"default"}, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ type PrioritizeStore interface {
|
||||||
|
|
||||||
// UpdateActionableStatus updates the status of an actionable item
|
// UpdateActionableStatus updates the status of an actionable item
|
||||||
UpdateActionableStatus(ctx context.Context, itemID ids.UUID, status ActionableStatus) error
|
UpdateActionableStatus(ctx context.Context, itemID ids.UUID, status ActionableStatus) error
|
||||||
|
|
||||||
|
// ListActiveAgents returns all agent IDs with recent activity
|
||||||
|
ListActiveAgents(ctx context.Context, since time.Time) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionableStatus represents the state of an actionable item.
|
// ActionableStatus represents the state of an actionable item.
|
||||||
|
|
@ -114,28 +117,66 @@ func (t *PrioritizeTask) Interval() time.Duration {
|
||||||
return t.cfg.ProcessInterval
|
return t.cfg.ProcessInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute performs prioritization of memory items.
|
// Execute performs prioritization of memory items across all agents.
|
||||||
func (t *PrioritizeTask) Execute(ctx context.Context) error {
|
func (t *PrioritizeTask) Execute(ctx context.Context) error {
|
||||||
logger.InfoCF("cortex", "Running prioritization scan", map[string]interface{}{"task": "prioritize"})
|
logger.InfoCF("cortex", "Running prioritization scan", map[string]interface{}{"task": "prioritize"})
|
||||||
|
|
||||||
agentID := "default" // TODO: Iterate over all agents
|
// Get all active agents
|
||||||
|
since := time.Now().Add(-t.cfg.LookbackWindow)
|
||||||
|
agents, err := t.store.ListActiveAgents(ctx, since)
|
||||||
|
if err != nil {
|
||||||
|
logger.WarnCF("cortex", "Failed to list active agents, falling back to default",
|
||||||
|
map[string]interface{}{"error": err.Error()})
|
||||||
|
agents = []string{"default"}
|
||||||
|
}
|
||||||
|
if len(agents) == 0 {
|
||||||
|
logger.DebugCF("cortex", "No active agents found, skipping prioritization", nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process prioritization for each agent
|
||||||
|
var totalProcessed, totalExtracted int
|
||||||
|
for _, agentID := range agents {
|
||||||
|
processed, extracted, err := t.processAgentItems(ctx, agentID)
|
||||||
|
if err != nil {
|
||||||
|
logger.WarnCF("cortex", "Failed to process items for agent",
|
||||||
|
map[string]interface{}{"agent_id": agentID, "error": err.Error()})
|
||||||
|
// Continue with other agents
|
||||||
|
}
|
||||||
|
totalProcessed += processed
|
||||||
|
totalExtracted += extracted
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.DebugCF("cortex", "Prioritization complete", map[string]interface{}{
|
||||||
|
"task": "prioritize",
|
||||||
|
"agents_processed": len(agents),
|
||||||
|
"items_processed": totalProcessed,
|
||||||
|
"items_extracted": totalExtracted,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processAgentItems processes prioritization for a single agent.
|
||||||
|
func (t *PrioritizeTask) processAgentItems(ctx context.Context, agentID string) (int, int, error) {
|
||||||
since := time.Now().Add(-t.cfg.LookbackWindow)
|
since := time.Now().Add(-t.cfg.LookbackWindow)
|
||||||
|
|
||||||
// Get unprocessed items
|
// Get unprocessed items
|
||||||
items, err := t.store.GetUnprocessedItems(ctx, agentID, since, t.cfg.MaxItemsPerRun)
|
items, err := t.store.GetUnprocessedItems(ctx, agentID, since, t.cfg.MaxItemsPerRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get unprocessed items: %w", err)
|
return 0, 0, fmt.Errorf("get unprocessed items for agent %s: %w", agentID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
logger.DebugCF("cortex", "No new items to prioritize", map[string]interface{}{"task": "prioritize"})
|
logger.DebugCF("cortex", "No new items to prioritize",
|
||||||
return nil
|
map[string]interface{}{"task": "prioritize", "agent_id": agentID})
|
||||||
|
return 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.DebugCF("cortex", "Processing items for actionability", map[string]interface{}{
|
logger.DebugCF("cortex", "Processing items for actionability", map[string]interface{}{
|
||||||
"task": "prioritize",
|
"task": "prioritize",
|
||||||
"count": len(items),
|
"agent_id": agentID,
|
||||||
|
"count": len(items),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Process each item
|
// Process each item
|
||||||
|
|
@ -147,8 +188,9 @@ func (t *PrioritizeTask) Execute(ctx context.Context) error {
|
||||||
if isActionable {
|
if isActionable {
|
||||||
if err := t.store.StoreActionableItem(ctx, actionable); err != nil {
|
if err := t.store.StoreActionableItem(ctx, actionable); err != nil {
|
||||||
logger.DebugCF("cortex", "Failed to store actionable item", map[string]interface{}{
|
logger.DebugCF("cortex", "Failed to store actionable item", map[string]interface{}{
|
||||||
"error": err,
|
"error": err,
|
||||||
"item_id": item.ID,
|
"item_id": item.ID,
|
||||||
|
"agent_id": agentID,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -159,16 +201,18 @@ func (t *PrioritizeTask) Execute(ctx context.Context) error {
|
||||||
|
|
||||||
// Mark items as processed
|
// Mark items as processed
|
||||||
if err := t.store.MarkAsProcessed(ctx, processed); err != nil {
|
if err := t.store.MarkAsProcessed(ctx, processed); err != nil {
|
||||||
logger.DebugCF("cortex", "Failed to mark items as processed", map[string]interface{}{"error": err})
|
logger.DebugCF("cortex", "Failed to mark items as processed",
|
||||||
|
map[string]interface{}{"error": err, "agent_id": agentID})
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.DebugCF("cortex", "Prioritization complete", map[string]interface{}{
|
logger.DebugCF("cortex", "Prioritization complete for agent", map[string]interface{}{
|
||||||
"task": "prioritize",
|
"task": "prioritize",
|
||||||
|
"agent_id": agentID,
|
||||||
"processed": len(processed),
|
"processed": len(processed),
|
||||||
"extracted": extracted,
|
"extracted": extracted,
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return len(processed), extracted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// analyzeItem determines if a memory item contains an actionable task.
|
// analyzeItem determines if a memory item contains an actionable task.
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,13 @@ import (
|
||||||
// RLStore is the minimal interface for reinforcement learning weight updates.
|
// RLStore is the minimal interface for reinforcement learning weight updates.
|
||||||
// Implemented by the memory delegate via hand-written SQL.
|
// Implemented by the memory delegate via hand-written SQL.
|
||||||
type RLStore interface {
|
type RLStore interface {
|
||||||
GetCompletedTasks(ctx context.Context, since time.Time) ([]TaskRecord, error)
|
GetCompletedTasks(ctx context.Context, agentID string, since time.Time) ([]TaskRecord, error)
|
||||||
GetRetrievedMemories(ctx context.Context, taskID string) ([]RetrievedMemoryRecord, error)
|
GetRetrievedMemories(ctx context.Context, taskID string) ([]RetrievedMemoryRecord, error)
|
||||||
GetTaskBaseline(ctx context.Context, agentID string) (*TaskBaseline, error)
|
GetTaskBaseline(ctx context.Context, agentID string) (*TaskBaseline, error)
|
||||||
UpdateTaskBaseline(ctx context.Context, agentID string, baseline *TaskBaseline) error
|
UpdateTaskBaseline(ctx context.Context, agentID string, baseline *TaskBaseline) error
|
||||||
UpdateMemoryWeight(ctx context.Context, memoryID ids.UUID, weight, credit float64) error
|
UpdateMemoryWeight(ctx context.Context, memoryID ids.UUID, weight, credit float64) error
|
||||||
UpdateMemorySelfReport(ctx context.Context, memoryID ids.UUID, score int) error
|
UpdateMemorySelfReport(ctx context.Context, memoryID ids.UUID, score int) error
|
||||||
|
ListActiveAgents(ctx context.Context, since time.Time) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskRecord represents a completed task with performance metrics.
|
// TaskRecord represents a completed task with performance metrics.
|
||||||
|
|
@ -84,7 +85,7 @@ func (t *RLTask) Execute(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get tasks completed since last run
|
// Get tasks completed since last run
|
||||||
tasks, err := t.store.GetCompletedTasks(ctx, t.lastRun)
|
tasks, err := t.store.GetCompletedTasks(ctx, t.agentID, t.lastRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get completed tasks: %w", err)
|
return fmt.Errorf("failed to get completed tasks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +116,14 @@ func (t *RLTask) Execute(ctx context.Context) error {
|
||||||
|
|
||||||
// Get memory stats for logging
|
// Get memory stats for logging
|
||||||
memories, err := t.store.GetRetrievedMemories(ctx, task.ID)
|
memories, err := t.store.GetRetrievedMemories(ctx, task.ID)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
|
logger.WarnCF("cortex", "Failed to get retrieved memories for RL processing",
|
||||||
|
map[string]interface{}{
|
||||||
|
"task_id": task.ID,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
// Continue processing - don't let memory retrieval failure stop the batch
|
||||||
|
} else {
|
||||||
totalMemoriesUpdated += len(memories)
|
totalMemoriesUpdated += len(memories)
|
||||||
for _, m := range memories {
|
for _, m := range memories {
|
||||||
if m.SelfReportScore != nil {
|
if m.SelfReportScore != nil {
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ func (m *mockRLStore) UpdateTaskBaseline(ctx context.Context, agentID string, ba
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockRLStore) GetCompletedTasks(ctx context.Context, since time.Time) ([]TaskRecord, error) {
|
func (m *mockRLStore) GetCompletedTasks(ctx context.Context, agentID string, since time.Time) ([]TaskRecord, error) {
|
||||||
if m.getCompletedTasksErr != nil {
|
if m.getCompletedTasksErr != nil {
|
||||||
return nil, m.getCompletedTasksErr
|
return nil, m.getCompletedTasksErr
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +83,10 @@ func (m *mockRLStore) UpdateMemorySelfReport(ctx context.Context, memoryID ids.U
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockRLStore) ListActiveAgents(ctx context.Context, since time.Time) ([]string, error) {
|
||||||
|
return []string{"test-agent"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestRLTask_Name(t *testing.T) {
|
func TestRLTask_Name(t *testing.T) {
|
||||||
store := &mockRLStore{}
|
store := &mockRLStore{}
|
||||||
task := NewRLTask(store, "test-agent")
|
task := NewRLTask(store, "test-agent")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue