mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-25 04:16:22 +02:00
255 lines
7.2 KiB
Go
255 lines
7.2 KiB
Go
// Package session defines domain entities for cognitive state persistence.
|
|
package session
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// HypothesisStatus represents the lifecycle state of a hypothesis.
|
|
type HypothesisStatus string
|
|
|
|
const (
|
|
HypothesisProposed HypothesisStatus = "PROPOSED"
|
|
HypothesisTesting HypothesisStatus = "TESTING"
|
|
HypothesisConfirmed HypothesisStatus = "CONFIRMED"
|
|
HypothesisRejected HypothesisStatus = "REJECTED"
|
|
)
|
|
|
|
// IsValid checks if the status is a known value.
|
|
func (s HypothesisStatus) IsValid() bool {
|
|
switch s {
|
|
case HypothesisProposed, HypothesisTesting, HypothesisConfirmed, HypothesisRejected:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Goal represents the primary objective of a session.
|
|
type Goal struct {
|
|
ID string `json:"id"`
|
|
Description string `json:"description"`
|
|
Progress float64 `json:"progress"` // 0.0-1.0
|
|
}
|
|
|
|
// Validate checks goal fields.
|
|
func (g *Goal) Validate() error {
|
|
if g.Description == "" {
|
|
return fmt.Errorf("goal description is required")
|
|
}
|
|
if g.Progress < 0.0 || g.Progress > 1.0 {
|
|
return fmt.Errorf("goal progress must be between 0.0 and 1.0, got %f", g.Progress)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Hypothesis represents a testable hypothesis.
|
|
type Hypothesis struct {
|
|
ID string `json:"id"`
|
|
Statement string `json:"statement"`
|
|
Status HypothesisStatus `json:"status"`
|
|
}
|
|
|
|
// Decision represents a recorded decision with rationale.
|
|
type Decision struct {
|
|
ID string `json:"id"`
|
|
Description string `json:"description"`
|
|
Rationale string `json:"rationale"`
|
|
Alternatives []string `json:"alternatives,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// SessionFact represents a fact within a session's cognitive state.
|
|
type SessionFact struct {
|
|
ID string `json:"id"`
|
|
Content string `json:"content"`
|
|
EntityType string `json:"entity_type"`
|
|
Confidence float64 `json:"confidence"`
|
|
ValidAt string `json:"valid_at,omitempty"`
|
|
}
|
|
|
|
// CognitiveStateVector represents the full cognitive state of a session.
|
|
type CognitiveStateVector struct {
|
|
SessionID string `json:"session_id"`
|
|
Version int `json:"version"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
PrimaryGoal *Goal `json:"primary_goal,omitempty"`
|
|
Hypotheses []Hypothesis `json:"hypotheses"`
|
|
Decisions []Decision `json:"decisions"`
|
|
Facts []SessionFact `json:"facts"`
|
|
OpenQuestions []string `json:"open_questions"`
|
|
ConfidenceMap map[string]float64 `json:"confidence_map"`
|
|
}
|
|
|
|
// NewCognitiveStateVector creates a new empty state vector.
|
|
func NewCognitiveStateVector(sessionID string) *CognitiveStateVector {
|
|
return &CognitiveStateVector{
|
|
SessionID: sessionID,
|
|
Version: 1,
|
|
Timestamp: time.Now(),
|
|
Hypotheses: []Hypothesis{},
|
|
Decisions: []Decision{},
|
|
Facts: []SessionFact{},
|
|
OpenQuestions: []string{},
|
|
ConfidenceMap: make(map[string]float64),
|
|
}
|
|
}
|
|
|
|
// SetGoal sets or replaces the primary goal. Progress is clamped to [0, 1].
|
|
func (csv *CognitiveStateVector) SetGoal(description string, progress float64) {
|
|
if progress < 0 {
|
|
progress = 0
|
|
}
|
|
if progress > 1 {
|
|
progress = 1
|
|
}
|
|
csv.PrimaryGoal = &Goal{
|
|
ID: generateID(),
|
|
Description: description,
|
|
Progress: progress,
|
|
}
|
|
}
|
|
|
|
// AddHypothesis adds a new hypothesis in PROPOSED status.
|
|
func (csv *CognitiveStateVector) AddHypothesis(statement string) *Hypothesis {
|
|
h := Hypothesis{
|
|
ID: generateID(),
|
|
Statement: statement,
|
|
Status: HypothesisProposed,
|
|
}
|
|
csv.Hypotheses = append(csv.Hypotheses, h)
|
|
return &csv.Hypotheses[len(csv.Hypotheses)-1]
|
|
}
|
|
|
|
// AddDecision records a decision with rationale and alternatives.
|
|
func (csv *CognitiveStateVector) AddDecision(description, rationale string, alternatives []string) *Decision {
|
|
d := Decision{
|
|
ID: generateID(),
|
|
Description: description,
|
|
Rationale: rationale,
|
|
Alternatives: alternatives,
|
|
Timestamp: time.Now(),
|
|
}
|
|
csv.Decisions = append(csv.Decisions, d)
|
|
return &csv.Decisions[len(csv.Decisions)-1]
|
|
}
|
|
|
|
// AddFact adds a fact to the session state.
|
|
func (csv *CognitiveStateVector) AddFact(content, entityType string, confidence float64) *SessionFact {
|
|
f := SessionFact{
|
|
ID: generateID(),
|
|
Content: content,
|
|
EntityType: entityType,
|
|
Confidence: confidence,
|
|
ValidAt: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
csv.Facts = append(csv.Facts, f)
|
|
return &csv.Facts[len(csv.Facts)-1]
|
|
}
|
|
|
|
// BumpVersion increments the version counter.
|
|
func (csv *CognitiveStateVector) BumpVersion() {
|
|
csv.Version++
|
|
csv.Timestamp = time.Now()
|
|
}
|
|
|
|
// Checksum computes a SHA-256 hex digest of the serialized state.
|
|
func (csv *CognitiveStateVector) Checksum() string {
|
|
data, _ := json.Marshal(csv)
|
|
h := sha256.Sum256(data)
|
|
return hex.EncodeToString(h[:])
|
|
}
|
|
|
|
// ToCompactString renders the state as a compact text block for prompt injection.
|
|
// maxTokens controls approximate truncation (1 token ≈ 4 chars).
|
|
func (csv *CognitiveStateVector) ToCompactString(maxTokens int) string {
|
|
maxChars := maxTokens * 4
|
|
var sb strings.Builder
|
|
|
|
if csv.PrimaryGoal != nil {
|
|
fmt.Fprintf(&sb, "GOAL: %s (%.0f%%)\n", csv.PrimaryGoal.Description, csv.PrimaryGoal.Progress*100)
|
|
}
|
|
|
|
if len(csv.Hypotheses) > 0 {
|
|
sb.WriteString("HYPOTHESES:\n")
|
|
for _, h := range csv.Hypotheses {
|
|
fmt.Fprintf(&sb, " - [%s] %s\n", strings.ToLower(string(h.Status)), h.Statement)
|
|
if sb.Len() > maxChars {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(csv.Facts) > 0 {
|
|
sb.WriteString("FACTS:\n")
|
|
for _, f := range csv.Facts {
|
|
fmt.Fprintf(&sb, " - [%s] %s\n", f.EntityType, f.Content)
|
|
if sb.Len() > maxChars {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(csv.Decisions) > 0 {
|
|
sb.WriteString("DECISIONS:\n")
|
|
for _, d := range csv.Decisions {
|
|
fmt.Fprintf(&sb, " - %s\n", d.Description)
|
|
if sb.Len() > maxChars {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(csv.OpenQuestions) > 0 {
|
|
sb.WriteString("OPEN QUESTIONS:\n")
|
|
for _, q := range csv.OpenQuestions {
|
|
fmt.Fprintf(&sb, " - %s\n", q)
|
|
if sb.Len() > maxChars {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
result := sb.String()
|
|
if len(result) > maxChars {
|
|
result = result[:maxChars]
|
|
}
|
|
return result
|
|
}
|
|
|
|
// SessionInfo holds metadata about a persisted session.
|
|
type SessionInfo struct {
|
|
SessionID string `json:"session_id"`
|
|
Version int `json:"version"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// AuditEntry records a state change operation.
|
|
type AuditEntry struct {
|
|
SessionID string `json:"session_id"`
|
|
Action string `json:"action"`
|
|
Version int `json:"version"`
|
|
Timestamp string `json:"timestamp"`
|
|
Details string `json:"details"`
|
|
}
|
|
|
|
// StateStore defines the interface for session state persistence.
|
|
type StateStore interface {
|
|
Save(ctx context.Context, state *CognitiveStateVector, checksum string) error
|
|
Load(ctx context.Context, sessionID string, version *int) (*CognitiveStateVector, string, error)
|
|
ListSessions(ctx context.Context) ([]SessionInfo, error)
|
|
DeleteSession(ctx context.Context, sessionID string) (int, error)
|
|
GetAuditLog(ctx context.Context, sessionID string, limit int) ([]AuditEntry, error)
|
|
}
|
|
|
|
func generateID() string {
|
|
b := make([]byte, 16)
|
|
_, _ = rand.Read(b)
|
|
return hex.EncodeToString(b)
|
|
}
|