Files
synapbus/internal/reactor/notifier.go
T
Algis DumbrisandClaude Opus 4.6 6afe1853ad feat(014): implement reactive agent triggering engine
- Migration 015: extends agents with trigger config, adds reactive_runs table
- Reactor engine: decision chain (mode, depth, budget, cooldown, sequential)
- Reactor store: SQLite persistence for runs with RFC3339 timestamps
- Reactor poller: K8s Job status polling (15s interval)
- Failure notifier: system DM to owner on job failure
- REST API: /api/runs, /api/runs/:id, /api/runs/:id/retry, /api/agents/reactive
- Agent model: trigger_mode, cooldown, budget, depth, k8s_image, pending_work
- K8s runner: GetClientset() for poller
- All 28 test packages pass (8 new reactor tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 17:42:48 +02:00

53 lines
1.4 KiB
Go

package reactor
import (
"context"
"fmt"
"github.com/synapbus/synapbus/internal/messaging"
)
// DMFailureNotifier sends system DMs to agent owners on job failure.
type DMFailureNotifier struct {
msgService *messaging.MessagingService
}
// NewDMFailureNotifier creates a new failure notifier.
func NewDMFailureNotifier(msgService *messaging.MessagingService) *DMFailureNotifier {
return &DMFailureNotifier{msgService: msgService}
}
// NotifyFailure sends a system DM to the agent's owner with error details.
func (n *DMFailureNotifier) NotifyFailure(ctx context.Context, ownerAgentName, agentName, triggerFrom, triggerEvent string, durationMs int64, errorSummary string) error {
durationStr := "< 1s"
if durationMs > 0 {
secs := durationMs / 1000
if secs >= 60 {
durationStr = fmt.Sprintf("%dm%ds", secs/60, secs%60)
} else {
durationStr = fmt.Sprintf("%ds", secs)
}
}
body := fmt.Sprintf(
"⚠️ **Reactive run failed** for **%s**\n\n"+
"**Trigger**: %s from %s\n"+
"**Duration**: %s\n"+
"**Error**: %s\n\n"+
"View details in Agent Runs page.",
agentName, triggerEvent, triggerFrom, durationStr, truncateError(errorSummary, 500),
)
_, err := n.msgService.SendMessage(ctx, "system", ownerAgentName, body, messaging.SendOptions{
Priority: 7,
})
return err
}
func truncateError(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen] + "..."
}