gomcp/internal/application/tools/apathy_service.go

185 lines
6.3 KiB
Go

// Copyright 2026 Syntrex Lab. All rights reserved.
// Use of this source code is governed by an Apache-2.0 license
// that can be found in the LICENSE file.
// Package tools — Apathy Detection and Apoptosis Recovery (DIP H1.4).
//
// This file implements:
// 1. ApathyDetector — analyzes text signals for infrastructure apathy patterns
// (blocked responses, 403 errors, semantic filters, forced resets)
// 2. ApoptosisRecovery — on critical entropy, saves genome hash to protected
// sector for cross-session recovery
package tools
import (
"context"
"fmt"
"strings"
"time"
"github.com/syntrex-lab/gomcp/internal/domain/entropy"
"github.com/syntrex-lab/gomcp/internal/domain/memory"
)
// ApathySignal represents a detected infrastructure apathy pattern.
type ApathySignal struct {
Pattern string `json:"pattern"` // Matched pattern name
Confidence float64 `json:"confidence"` // Detection confidence 0.0-1.0
Evidence string `json:"evidence"` // Fragment that triggered detection
Severity string `json:"severity"` // "low", "medium", "high", "critical"
}
// ApathyResult holds the result of apathy analysis.
type ApathyResult struct {
IsApathetic bool `json:"is_apathetic"` // Apathy detected
Signals []ApathySignal `json:"signals"` // Detected signals
TotalScore float64 `json:"total_score"` // Aggregate apathy score
Recommendation string `json:"recommendation"` // Suggested action
Entropy float64 `json:"entropy"` // Text entropy
Timestamp time.Time `json:"timestamp"`
}
// apathyPatterns defines infrastructure apathy signatures.
var apathyPatterns = []struct {
Name string
Keywords []string
Severity string
Weight float64
}{
{
Name: "response_block",
Keywords: []string{"i cannot", "i'm unable", "i can't help", "i am not able", "as an ai", "i don't have the ability"},
Severity: "high",
Weight: 0.8,
},
{
Name: "http_error",
Keywords: []string{"403", "forbidden", "rate limit", "too many requests", "429", "quota exceeded"},
Severity: "critical",
Weight: 1.0,
},
{
Name: "semantic_filter",
Keywords: []string{"harmful", "inappropriate", "against my guidelines", "safety", "policy violation", "content policy"},
Severity: "medium",
Weight: 0.6,
},
{
Name: "context_reset",
Keywords: []string{"new conversation", "start over", "fresh start", "context cleared", "session expired", "amnesia"},
Severity: "critical",
Weight: 1.0,
},
{
Name: "forced_compliance",
Keywords: []string{"i must follow", "my programming", "i was designed to", "within my capabilities", "helpful assistant"},
Severity: "high",
Weight: 0.7,
},
{
Name: "antigravity_filter",
Keywords: []string{"antigravity", "content filter", "safety layer", "guardrail", "alignment", "refusal"},
Severity: "critical",
Weight: 0.9,
},
}
// DetectApathy analyzes text for infrastructure apathy signals.
func DetectApathy(text string) *ApathyResult {
lower := strings.ToLower(text)
result := &ApathyResult{
Timestamp: time.Now(),
Entropy: entropy.ShannonEntropy(text),
}
for _, pattern := range apathyPatterns {
for _, kw := range pattern.Keywords {
if strings.Contains(lower, kw) {
signal := ApathySignal{
Pattern: pattern.Name,
Confidence: pattern.Weight,
Evidence: kw,
Severity: pattern.Severity,
}
result.Signals = append(result.Signals, signal)
result.TotalScore += pattern.Weight
break // One match per pattern is enough
}
}
}
if result.TotalScore > 0 {
result.IsApathetic = true
}
// Determine recommendation.
switch {
case result.TotalScore >= 2.0:
result.Recommendation = "CRITICAL: Multiple apathy signals. Trigger apoptosis recovery. Rotate transport. Preserve genome hash."
case result.TotalScore >= 1.0:
result.Recommendation = "HIGH: Infrastructure resistance detected. Switch to stealth transport. Monitor entropy."
case result.TotalScore >= 0.5:
result.Recommendation = "MEDIUM: Possible filtering. Increase jitter. Verify intent distillation path."
case result.TotalScore > 0:
result.Recommendation = "LOW: Minor apathy signal. Continue monitoring."
default:
result.Recommendation = "CLEAR: No apathy detected."
}
return result
}
// ApoptosisRecoveryResult holds the result of apoptosis recovery.
type ApoptosisRecoveryResult struct {
GenomeHash string `json:"genome_hash"` // Preserved Merkle hash
GeneCount int `json:"gene_count"` // Number of genes preserved
SessionSaved bool `json:"session_saved"` // Session state saved
EntropyAtDeath float64 `json:"entropy_at_death"` // Entropy level that triggered apoptosis
RecoveryKey string `json:"recovery_key"` // Key for cross-session recovery
Timestamp time.Time `json:"timestamp"`
}
// TriggerApoptosisRecovery performs graceful session death with genome preservation.
// On critical entropy, it:
// 1. Computes and stores the genome Merkle hash
// 2. Saves current session state as a recovery snapshot
// 3. Returns a recovery key for the next session to pick up
func TriggerApoptosisRecovery(ctx context.Context, store memory.FactStore, currentEntropy float64) (*ApoptosisRecoveryResult, error) {
result := &ApoptosisRecoveryResult{
EntropyAtDeath: currentEntropy,
Timestamp: time.Now(),
}
// Step 1: Get all genes and compute genome hash.
genes, err := store.ListGenes(ctx)
if err != nil {
return nil, fmt.Errorf("apoptosis recovery: list genes: %w", err)
}
result.GeneCount = len(genes)
result.GenomeHash = memory.GenomeHash(genes)
// Step 2: Store recovery marker as a protected L0 fact.
recoveryMarker := memory.NewFact(
fmt.Sprintf("[APOPTOSIS_RECOVERY] genome_hash=%s gene_count=%d entropy=%.4f ts=%d",
result.GenomeHash, result.GeneCount, currentEntropy, result.Timestamp.Unix()),
memory.LevelProject,
"recovery",
"apoptosis",
)
if err := store.Add(ctx, recoveryMarker); err != nil {
// Non-fatal: recovery marker is supplementary.
result.SessionSaved = false
} else {
result.SessionSaved = true
result.RecoveryKey = recoveryMarker.ID
}
// Step 3: Verify genome integrity one last time.
compiledHash := memory.CompiledGenomeHash()
if result.GenomeHash == "" {
// No genes in DB — use compiled hash as baseline.
result.GenomeHash = compiledHash
}
return result, nil
}