refactor(agent): update agent_run and loop

This commit is contained in:
ZanzyTHEbar 2026-03-05 21:16:03 +00:00
parent c6eae697ff
commit 562a99fae1
2 changed files with 102 additions and 14 deletions

View file

@ -196,11 +196,13 @@ func (al *AgentLoop) refreshContextBlocks(ctx context.Context, opts processOptio
_ = g.Wait() _ = g.Wait()
al.ctxBlockCache.Store(opts.SessionKey, ctxBlockCacheEntry{ if !useCached {
focusBlock: focusBlock, al.ctxBlockCache.Store(opts.SessionKey, ctxBlockCacheEntry{
knowledge: kb, focusBlock: focusBlock,
cachedAt: time.Now(), knowledge: kb,
}) cachedAt: time.Now(),
})
}
// Prune stale entries from other sessions to prevent unbounded growth. // Prune stale entries from other sessions to prevent unbounded growth.
al.ctxBlockCache.Range(func(key, value any) bool { al.ctxBlockCache.Range(func(key, value any) bool {
@ -616,10 +618,63 @@ func (al *AgentLoop) runStreaming(ctx context.Context, opts processOptions, ac a
// auditStep logs tool calls from a Fantasy step result to the audit log. // auditStep logs tool calls from a Fantasy step result to the audit log.
func (al *AgentLoop) auditStep(_ context.Context, step fantasy.StepResult, sessionKey string) { func (al *AgentLoop) auditStep(_ context.Context, step fantasy.StepResult, sessionKey string) {
toolCalls := step.Content.ToolCalls() toolCalls := step.Content.ToolCalls()
if len(toolCalls) == 0 { toolResults := step.Content.ToolResults()
if len(toolCalls) == 0 && len(toolResults) == 0 {
return return
} }
callByID := make(map[string]fantasy.ToolCallContent, len(toolCalls))
for _, tc := range toolCalls {
callByID[tc.ToolCallID] = tc
}
// Prefer result-based entries because they carry success/failure semantics.
if len(toolResults) > 0 {
for _, tr := range toolResults {
toolName := ""
toolInput := ""
if tc, ok := callByID[tr.ToolCallID]; ok {
toolName = tc.ToolName
toolInput = tc.Input
}
if toolName == "" {
toolName = "unknown_tool"
}
action := "tool_success"
output := ""
switch out := tr.Result.(type) {
case fantasy.ToolResultOutputContentText:
output = out.Text
case fantasy.ToolResultOutputContentMedia:
output = out.Text
case fantasy.ToolResultOutputContentError:
action = "tool_error"
if out.Error != nil {
output = out.Error.Error()
} else {
output = "tool returned error output"
}
}
entry := &memory.AuditEntry{
ID: ids.New(),
AgentID: pkg.NAME,
SessionKey: sessionKey,
Action: action,
Target: toolName,
Input: toolInput,
Output: output,
}
if !al.enqueueAuditEntry(entry) {
logger.WarnCF("agent", "Audit channel unavailable, dropping tool result entry",
map[string]interface{}{"tool": toolName, "action": action})
}
}
return
}
// Legacy fallback: if no tool results were emitted, record tool calls.
for _, tc := range toolCalls { for _, tc := range toolCalls {
entry := &memory.AuditEntry{ entry := &memory.AuditEntry{
ID: ids.New(), ID: ids.New(),
@ -629,15 +684,31 @@ func (al *AgentLoop) auditStep(_ context.Context, step fantasy.StepResult, sessi
Target: tc.ToolName, Target: tc.ToolName,
Input: tc.Input, Input: tc.Input,
} }
select { if !al.enqueueAuditEntry(entry) {
case al.auditChan <- entry: logger.WarnCF("agent", "Audit channel unavailable, dropping tool call entry",
default:
logger.WarnCF("agent", "Audit channel full, dropping entry",
map[string]interface{}{"tool": tc.ToolName}) map[string]interface{}{"tool": tc.ToolName})
} }
} }
} }
func (al *AgentLoop) enqueueAuditEntry(entry *memory.AuditEntry) (ok bool) {
if al.auditChan == nil || entry == nil {
return false
}
defer func() {
if recover() != nil {
ok = false
}
}()
select {
case al.auditChan <- entry:
return true
default:
return false
}
}
// updateToolContexts updates the context for tools that need channel/chatID info. // updateToolContexts updates the context for tools that need channel/chatID info.
func (al *AgentLoop) updateToolContexts(channel, chatID string) { func (al *AgentLoop) updateToolContexts(channel, chatID string) {
// Use ContextualTool interface instead of type assertions // Use ContextualTool interface instead of type assertions

View file

@ -75,6 +75,7 @@ type AgentLoop struct {
outputOverride atomic.Value // Owner: command_handler.go — CLI output redirection target for internal messages outputOverride atomic.Value // Owner: command_handler.go — CLI output redirection target for internal messages
toolResultSearch fantasy.AgentTool toolResultSearch fantasy.AgentTool
cortex *cortex.Cortex cortex *cortex.Cortex
inflight sync.WaitGroup
} }
type outputTarget struct { type outputTarget struct {
@ -413,6 +414,11 @@ func NewAgentLoop(ctx context.Context, cfg *config.Config, msgBus *bus.MessageBu
if aus, ok := al.memDelegate.(cortex.AuditAnalysisStore); ok { if aus, ok := al.memDelegate.(cortex.AuditAnalysisStore); ok {
auditStore = aus auditStore = aus
} }
// DriftStore for domain health monitoring and trend tracking
var driftStore cortex.DriftStore
if ds, ok := al.memDelegate.(cortex.DriftMemorySource); ok {
driftStore = cortex.NewMemoryDriftAdapter(ds)
}
cortexTasks := []cortex.Task{ cortexTasks := []cortex.Task{
cortex.NewDecayTask(cortex.DefaultDecayConfig(), decayStore), cortex.NewDecayTask(cortex.DefaultDecayConfig(), decayStore),
cortex.NewBackfillTask(cortex.DefaultBackfillConfig(), backfillStore, embedFn), cortex.NewBackfillTask(cortex.DefaultBackfillConfig(), backfillStore, embedFn),
@ -420,6 +426,7 @@ func NewAgentLoop(ctx context.Context, cfg *config.Config, msgBus *bus.MessageBu
cortex.NewPruneTask(cortex.DefaultPruneConfig(), pruneStore), cortex.NewPruneTask(cortex.DefaultPruneConfig(), pruneStore),
cortex.NewRLTask(rlStore, pkg.NAME), cortex.NewRLTask(rlStore, pkg.NAME),
cortex.NewAuditAnalysisTask(auditStore), cortex.NewAuditAnalysisTask(auditStore),
cortex.NewDriftTask(cortex.DefaultDriftConfig(), driftStore),
} }
al.cortex = cortex.New(cortexTasks, 60*time.Second) al.cortex = cortex.New(cortexTasks, 60*time.Second)
@ -440,7 +447,11 @@ func (al *AgentLoop) Run(ctx context.Context) error {
continue continue
} }
response, err := al.processMessage(ctx, msg) al.inflight.Add(1)
response, err := func() (string, error) {
defer al.inflight.Done()
return al.processMessage(ctx, msg)
}()
if err != nil { if err != nil {
response = fmt.Sprintf("Error processing message: %v", err) response = fmt.Sprintf("Error processing message: %v", err)
} }
@ -480,9 +491,15 @@ func (al *AgentLoop) Run(ctx context.Context) error {
func (al *AgentLoop) Stop() { func (al *AgentLoop) Stop() {
al.running.Store(false) al.running.Store(false)
close(al.auditChan) al.inflight.Wait()
<-al.auditDone // wait for audit worker to drain
al.sessions.Close() if al.auditChan != nil {
close(al.auditChan)
<-al.auditDone // wait for audit worker to drain
}
if al.sessions != nil {
al.sessions.Close()
}
if al.identitySync != nil { if al.identitySync != nil {
al.identitySync.Close() al.identitySync.Close()
} }