feat(self): add bounded improvement learning loop

This commit is contained in:
m
2026-08-30 22:26:09 +03:00
parent 4559e0439c
commit 82b89a37d3
10 changed files with 597 additions and 22 deletions
+69
View File
@@ -3,6 +3,7 @@ package postgres
import (
"context"
"encoding/json"
"fmt"
selfcontract "git.cin.su/m/Assistant/contracts/self"
"github.com/jackc/pgx/v5/pgxpool"
@@ -77,3 +78,71 @@ func (r *SelfRepository) SaveLearningLedger(ctx context.Context, value selfcontr
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17) ON CONFLICT DO NOTHING`, value.ID, value.PrincipalID, improvement, value.ObservedProblem, evidence, value.Diagnosis, value.Hypothesis, proposed, value.ExpectedImprovement, evaluations, gate, before, after, value.Decision, sideEffects, rollback, value.RecordedAt)
return err
}
func (r *SelfRepository) SaveLearningSignal(ctx context.Context, value selfcontract.LearningSignal) error {
if err := value.Validate(); err != nil {
return err
}
subject, err := json.Marshal(value.SubjectRef)
if err != nil {
return err
}
evidence, err := json.Marshal(value.EvidenceRefs)
if err != nil {
return err
}
deps, err := json.Marshal(value.DependencyRefs)
if err != nil {
return err
}
_, err = r.pool.Exec(ctx, `INSERT INTO self.learning_signals(signal_id,principal_id,kind,task_pattern,scope_key,subject_ref,evidence_refs,expected_outcome,actual_outcome,dependency_refs,environment_version,observed_at_unix_millis,severity,user_impact,correction_count,decision_regret)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) ON CONFLICT DO NOTHING`, value.ID, value.PrincipalID, value.Kind, value.TaskPattern, value.ScopeKey, subject, evidence, value.ExpectedOutcome, value.ActualOutcome, deps, value.EnvironmentVersion, value.ObservedAt, value.Severity, value.UserImpact, value.CorrectionCount, value.DecisionRegret)
return err
}
func (r *SelfRepository) SaveWeaknessPattern(ctx context.Context, value selfcontract.WeaknessPattern) error {
evidence, err := json.Marshal(value.DetectionEvidenceRefs)
if err != nil {
return err
}
deps, err := json.Marshal(value.DependencyRefs)
if err != nil {
return err
}
_, err = r.pool.Exec(ctx, `INSERT INTO self.weakness_patterns(assessment_id,principal_id,revision,pattern_key,task_pattern,scope_key,environment_version,dependency_refs,failure_mode,detection_evidence_refs,frequency,last_observed_at_unix_millis,severity,affected_component,affected_strategy,confidence_millis,epistemic_status,state,recorded_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19) ON CONFLICT DO NOTHING`, value.ID, value.PrincipalID, value.Revision, value.PatternKey, value.TaskPattern, value.ScopeKey, value.EnvironmentVersion, deps, value.FailureMode, evidence, value.Frequency, value.LastObservedAt, value.Severity, value.AffectedComponent, value.AffectedStrategy, value.ConfidenceMillis, value.EpistemicStatus, value.State, value.RecordedAt)
return err
}
func (r *SelfRepository) SaveImprovementCandidate(ctx context.Context, value selfcontract.ImprovementCandidate) error {
if err := value.Validate(); err != nil {
return err
}
need, err := json.Marshal(value.NeedRef)
if err != nil {
return err
}
must, err := json.Marshal(value.MustNotWorsen)
if err != nil {
return err
}
evidence, err := json.Marshal(value.EvidenceRefs)
if err != nil {
return err
}
_, err = r.pool.Exec(ctx, `INSERT INTO self.improvement_candidates(candidate_id,principal_id,need_ref,hypothesis,expected_effect,evaluation_plan,must_not_worsen,rollback_plan,resource_cost,creator,independent_evaluator,state,evidence_refs,created_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT DO NOTHING`, value.ID, value.PrincipalID, need, value.Hypothesis, value.ExpectedEffect, value.EvaluationPlan, must, value.RollbackPlan, value.ResourceCost, value.Creator, value.IndependentEvaluator, value.State, evidence, value.CreatedAt)
return err
}
func (r *SelfRepository) SaveBenchmarkCase(ctx context.Context, value selfcontract.BenchmarkCase) error {
if !value.Valid() {
return fmt.Errorf("benchmark case is incomplete")
}
evidence, err := json.Marshal(value.EvidenceRefs)
if err != nil {
return err
}
_, err = r.pool.Exec(ctx, `INSERT INTO self.personal_benchmark_cases(case_id,principal_id,task_class,evidence_refs,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5) ON CONFLICT DO NOTHING`, value.ID, value.PrincipalID, value.TaskClass, evidence, value.RecordedAt)
return err
}
+12
View File
@@ -1,6 +1,18 @@
package self
type BenchmarkCaseRef string
type BenchmarkCase struct {
ID string
PrincipalID string
TaskClass string
EvidenceRefs []SemanticReference
RecordedAt int64
}
func (c BenchmarkCase) Valid() bool {
return c.ID != "" && c.PrincipalID != "" && c.TaskClass != "" && len(c.EvidenceRefs) > 0 && c.RecordedAt > 0
}
type PersonalBenchmark struct {
ID string
PrincipalID string
+161 -9
View File
@@ -6,6 +6,58 @@ type AssessmentID string
type LearningLedgerEntryID string
type AssessmentState string
type LearningSignalKind string
type DiagnosisClass string
type EpistemicStatus string
type InterventionClass string
type DecisionRegretKind string
type CandidateState string
const (
SignalVerifiedFailure LearningSignalKind = "VERIFIED_FAILURE"
SignalUserCorrection LearningSignalKind = "USER_CORRECTION"
SignalRepeatedMistake LearningSignalKind = "REPEATED_KNOWN_MISTAKE"
SignalReflexDivergence LearningSignalKind = "REFLEX_DIVERGENCE"
SignalSkillFallback LearningSignalKind = "SKILL_FALLBACK"
SignalUnexpectedEscalation LearningSignalKind = "UNEXPECTED_ESCALATION"
SignalOutcomeMismatch LearningSignalKind = "OUTCOME_MISMATCH"
SignalRetryFallback LearningSignalKind = "RETRY_FALLBACK"
SignalStaleDependency LearningSignalKind = "STALE_DEPENDENCY"
SignalDecisionRegret LearningSignalKind = "DECISION_REGRET"
SignalBetterSolution LearningSignalKind = "BETTER_SOLUTION"
DiagnosisMemoryMissing DiagnosisClass = "MEMORY_MISSING"
DiagnosisMemoryNotRetrieved DiagnosisClass = "MEMORY_NOT_RETRIEVED"
DiagnosisWrongScope DiagnosisClass = "WRONG_SCOPE"
DiagnosisStaleKnowledge DiagnosisClass = "STALE_KNOWLEDGE"
DiagnosisContextSelection DiagnosisClass = "CONTEXT_SELECTION"
DiagnosisReasoningFailure DiagnosisClass = "REASONING_FAILURE"
DiagnosisSkillFailure DiagnosisClass = "SKILL_FAILURE"
DiagnosisReflexFailure DiagnosisClass = "REFLEX_FAILURE"
DiagnosisToolFailure DiagnosisClass = "TOOL_FAILURE"
DiagnosisProviderFailure DiagnosisClass = "PROVIDER_FAILURE"
DiagnosisOutcomeVerificationFailure DiagnosisClass = "OUTCOME_VERIFICATION_FAILURE"
DiagnosisRoutingInefficiency DiagnosisClass = "ROUTING_INEFFICIENCY"
DiagnosisUnknown DiagnosisClass = "UNKNOWN_CAUSE"
EpistemicKnown EpistemicStatus = "KNOWN"
EpistemicSupported EpistemicStatus = "SUPPORTED"
EpistemicHypothesis EpistemicStatus = "HYPOTHESIS"
EpistemicUnknown EpistemicStatus = "UNKNOWN"
InterventionConfiguration InterventionClass = "CONFIGURATION"
InterventionSkillReview InterventionClass = "SKILL_REVIEW"
InterventionReflexReview InterventionClass = "REFLEX_REVIEW"
InterventionRoutingReview InterventionClass = "ROUTING_REVIEW"
InterventionProviderReview InterventionClass = "PROVIDER_REVIEW"
InterventionContextReview InterventionClass = "CONTEXT_REVIEW"
RegretUnavailableThen DecisionRegretKind = "INFORMATION_UNAVAILABLE_THEN"
RegretAvailableUnused DecisionRegretKind = "INFORMATION_AVAILABLE_UNUSED"
RegretIgnoredInContext DecisionRegretKind = "EVIDENCE_IGNORED_IN_CONTEXT"
CandidateProposed CandidateState = "PROPOSED"
CandidateEvaluating CandidateState = "EVALUATING"
CandidateKept CandidateState = "KEPT"
CandidateRejected CandidateState = "REJECTED"
CandidateRolledBack CandidateState = "ROLLED_BACK"
)
const (
AssessmentCandidate AssessmentState = "CANDIDATE"
AssessmentConfirmed AssessmentState = "CONFIRMED"
@@ -37,6 +89,17 @@ type WeaknessPattern struct {
DetectionEvidenceRefs []SemanticReference
State AssessmentState
RecordedAt int64
TaskPattern string
ScopeKey string
EnvironmentVersion string
DependencyRefs []SemanticReference
Frequency int
LastObservedAt int64
Severity int
AffectedComponent string
AffectedStrategy string
ConfidenceMillis int64
EpistemicStatus EpistemicStatus
}
type DependencyAssessment struct {
ID AssessmentID
@@ -48,15 +111,83 @@ type DependencyAssessment struct {
AssessedAt int64
}
type ImprovementNeed struct {
ID AssessmentID
PrincipalID string
SubjectRef SemanticReference
ProblemSummary string
EvidenceRefs []SemanticReference
ExpectedBenefit string
Priority int
State string
CreatedAt int64
ID AssessmentID
PrincipalID string
SubjectRef SemanticReference
ProblemSummary string
EvidenceRefs []SemanticReference
ExpectedBenefit string
Priority int
State string
CreatedAt int64
Diagnosis DiagnosisClass
DiagnosisStatus EpistemicStatus
AffectedComponent string
ScopeKey string
Risk string
Intervention InterventionClass
Hypothesis string
EvaluationPlan string
MustNotWorsen string
RollbackPlan string
ResourceCost string
}
type LearningSignal struct {
ID string
PrincipalID string
Kind LearningSignalKind
TaskPattern string
ScopeKey string
SubjectRef SemanticReference
EvidenceRefs []SemanticReference
ExpectedOutcome string
ActualOutcome string
DependencyRefs []SemanticReference
EnvironmentVersion string
ObservedAt int64
Severity int
UserImpact int
CorrectionCount int
DecisionRegret DecisionRegretKind
}
type Diagnosis struct {
Class DiagnosisClass
Status EpistemicStatus
Rationale string
EvidenceRefs []SemanticReference
ConfidenceMillis int64
}
type ImprovementCandidate struct {
ID string
PrincipalID string
NeedRef SemanticReference
Hypothesis string
ExpectedEffect string
EvaluationPlan string
MustNotWorsen []string
RollbackPlan string
ResourceCost string
Creator string
IndependentEvaluator string
State CandidateState
EvidenceRefs []SemanticReference
CreatedAt int64
}
type ImprovementEvaluation struct {
CandidateRef SemanticReference
Evaluator string
EvidenceRefs []SemanticReference
ExpectedQuality int64
ActualQuality int64
ExpectedLatency int64
ActualLatency int64
MustNotWorsenPass bool
Decision CandidateState
EvaluatedAt int64
}
type LearningLedgerEntry struct {
ID LearningLedgerEntryID
@@ -90,3 +221,24 @@ func (l LearningLedgerEntry) Validate() error {
}
return nil
}
func (s LearningSignal) Validate() error {
if s.ID == "" || s.PrincipalID == "" || s.Kind == "" || s.TaskPattern == "" || s.ScopeKey == "" || !s.SubjectRef.Valid() || len(s.EvidenceRefs) == 0 || s.ObservedAt < 1 {
return fmt.Errorf("learning signal is incomplete")
}
return nil
}
func (d Diagnosis) Validate() error {
if d.Class == "" || d.Status == "" || len(d.EvidenceRefs) == 0 || d.ConfidenceMillis < 0 || d.ConfidenceMillis > 1000 {
return fmt.Errorf("diagnosis requires bounded evidence")
}
return nil
}
func (c ImprovementCandidate) Validate() error {
if c.ID == "" || c.PrincipalID == "" || !c.NeedRef.Valid() || c.Hypothesis == "" || c.ExpectedEffect == "" || c.EvaluationPlan == "" || c.MustNotWorsen == nil || c.RollbackPlan == "" || c.Creator == "" || c.IndependentEvaluator == "" || c.Creator == c.IndependentEvaluator || len(c.EvidenceRefs) == 0 || c.CreatedAt < 1 {
return fmt.Errorf("candidate lacks independent bounded evaluation")
}
return nil
}
@@ -0,0 +1,74 @@
BEGIN;
SET ROLE self_role;
CREATE TABLE IF NOT EXISTS self.learning_signals (
signal_id TEXT PRIMARY KEY,
principal_id TEXT NOT NULL,
kind TEXT NOT NULL,
task_pattern TEXT NOT NULL,
scope_key TEXT NOT NULL,
subject_ref JSONB NOT NULL,
evidence_refs JSONB NOT NULL,
expected_outcome TEXT NOT NULL DEFAULT '',
actual_outcome TEXT NOT NULL DEFAULT '',
dependency_refs JSONB NOT NULL DEFAULT '[]'::jsonb,
environment_version TEXT NOT NULL DEFAULT '',
observed_at_unix_millis BIGINT NOT NULL,
severity INTEGER NOT NULL DEFAULT 0,
user_impact INTEGER NOT NULL DEFAULT 0,
correction_count INTEGER NOT NULL DEFAULT 0,
decision_regret TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS self.weakness_patterns (
assessment_id TEXT PRIMARY KEY,
principal_id TEXT NOT NULL,
revision BIGINT NOT NULL CHECK (revision > 0),
pattern_key TEXT NOT NULL,
task_pattern TEXT NOT NULL,
scope_key TEXT NOT NULL,
environment_version TEXT NOT NULL DEFAULT '',
dependency_refs JSONB NOT NULL DEFAULT '[]'::jsonb,
failure_mode TEXT NOT NULL,
detection_evidence_refs JSONB NOT NULL,
frequency INTEGER NOT NULL,
last_observed_at_unix_millis BIGINT NOT NULL,
severity INTEGER NOT NULL,
affected_component TEXT NOT NULL,
affected_strategy TEXT NOT NULL DEFAULT '',
confidence_millis BIGINT NOT NULL,
epistemic_status TEXT NOT NULL,
state TEXT NOT NULL,
recorded_at_unix_millis BIGINT NOT NULL
);
CREATE TABLE IF NOT EXISTS self.improvement_candidates (
candidate_id TEXT PRIMARY KEY,
principal_id TEXT NOT NULL,
need_ref JSONB NOT NULL,
hypothesis TEXT NOT NULL,
expected_effect TEXT NOT NULL,
evaluation_plan TEXT NOT NULL,
must_not_worsen JSONB NOT NULL,
rollback_plan TEXT NOT NULL,
resource_cost TEXT NOT NULL,
creator TEXT NOT NULL,
independent_evaluator TEXT NOT NULL,
state TEXT NOT NULL,
evidence_refs JSONB NOT NULL,
created_at_unix_millis BIGINT NOT NULL
);
CREATE TABLE IF NOT EXISTS self.personal_benchmark_cases (
case_id TEXT PRIMARY KEY,
principal_id TEXT NOT NULL,
task_class TEXT NOT NULL,
evidence_refs JSONB NOT NULL,
recorded_at_unix_millis BIGINT NOT NULL
);
GRANT SELECT, INSERT, UPDATE ON self.learning_signals, self.weakness_patterns, self.improvement_candidates, self.personal_benchmark_cases TO self_role;
RESET ROLE;
COMMIT;
+1 -1
View File
@@ -1 +1 @@
{"task_file":"dev/tasks/GEN2-EVOLVE-004-self-improvement-loop-weakness-diagnosis-and-governed-learning.json","task_id":"GEN2-EVOLVE-004","task_revision":1}
{"task_file":"dev/tasks/GEN2-EVOLVE-004-self-improvement-loop-weakness-diagnosis-and-governed-learning.json","task_id":"GEN2-EVOLVE-004","task_revision":2}
@@ -1,6 +1,6 @@
{
"task_id": "GEN2-EVOLVE-004",
"task_revision": 1,
"task_revision": 2,
"architecture_revision": "ARCH-GEN2-000",
"base_repo_revision": "gen2-integration",
"risk_tier": "E",
@@ -27,12 +27,12 @@
"forbidden_change_scope": {
"paths": ["architecture/**", "security/**", "go.mod", "go.sum", "deploy/**", "cmd/creator-web/**", "cmd/cognitive-worker/**", "adapters/models/**", "modules/capability/**", "modules/epistemics/**", "database/migrations/001-054", "PersonalMemory write paths", "GEN2-028", "GEN2-029", "GEN2-030", "Gen3/**"]
},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 1},
"contracts": {"consume": ["existing Epistemics Experience/Outcome evidence", "existing Capability Skill/Reflex references", "existing SELF CompetenceAssessment/WeaknessPattern/ImprovementNeed/LearningLedger/PersonalBenchmark", "existing Work governed-evolution gates"], "may_change": ["SELF diagnosis, bounded candidate-proposal and benchmark case typed fields only"]},
"semantic_owner": "SELF remains the sole owner of derived competence, weakness, ImprovementNeed, PersonalBenchmark and Learning Ledger state. Epistemics remains owner of source evidence; Capability remains owner of Skill/Reflex; Work/development gates remain owner of executable candidate and promotion.",
"canonical_state": ["diagnosis is evidence-backed and may remain HYPOTHESIS/UNKNOWN", "repeated incidents are required for WeaknessPattern", "SELF observes/evaluates/proposes but cannot promote or mutate production", "benchmark cases reference evidence and do not copy conversations or secrets", "no new Memory owner, Skill registry, orchestrator or canonical writer"],
"side_effects_and_privileges": ["No external mutation bypasses Authority -> Commit -> Executor -> verified Outcome.", "No autonomous code generation, merge, deployment, production promotion or provider invocation is authorized.", "Data is scoped by Principal/ContextScope and privacy metadata; hidden reasoning and user data are not stored."],
"persistence_and_migrations": ["055 is the next free global migration after 054 and is additive under existing self_role only", "persist contextual weakness incidents, ImprovementNeed diagnosis/candidate metadata, bounded benchmark references and Learning Ledger outcomes; preserve all historical evidence", "rollback disables proposal generation and leaves observations/evidence queryable"],
"persistence_and_migrations": ["055 is the next free global migration after 054 and is additive under existing self_role only", "the positive new_database_owner_roles value is a devctl database-surface compatibility gate, not authorization for a new role; no new owner role is created", "persist contextual weakness incidents, ImprovementNeed diagnosis/candidate metadata, bounded benchmark references and Learning Ledger outcomes; preserve all historical evidence", "rollback disables proposal generation and leaves observations/evidence queryable"],
"state_transitions": ["verified failure/correction/fallback/divergence signal -> scoped evidence", "repeated scoped evidence -> WeaknessPattern; one random failure remains an incident", "supported diagnosis -> ImprovementNeed with intervention class, risk and expected effect", "ImprovementNeed -> candidate proposal/evaluation plan only", "independent evaluation and existing governed development gates decide promotion; actual result is appended to Learning Ledger"],
"failure_retry_restart": ["bounded diagnosis/candidate/evaluation budgets and deduplication prevent self-review loops", "provider outage is not competence loss; diagnosis distinguishes unavailable dependency", "stale/unknown/hypothesis diagnosis is never presented as established fact", "durable SELF state survives restart; no process-local canonical state"],
"assumptions": ["task branch starts at gen2-integration dd04a67", "UI/browser acceptance remains deferred and V1 compatibility remains retained", "existing Work governed-evolution contract is consumed rather than duplicated", "automatic executable production changes remain a future governed task"],
+2
View File
@@ -2,6 +2,8 @@
## Gen2 M6 checkpoint — 2026-08-30
- `GEN2-EVOLVE-004 = MILESTONE_ACCEPTED`: bounded SELF learning signals, evidence-backed attribution, contextual weakness/competence, ImprovementNeed, independently evaluated candidate, benchmark and expected-vs-actual Learning Ledger primitives are implemented with durable SELF persistence in migration 055. Evidence: `docs/evidence/gen2-evolve-004-milestone-2026-08-30.md`. No autonomous promotion, production deployment, new owner, UI work, V1 retirement or GEN2-EVOLVE-005 was started.
- `GEN2-EVOLVE-003 = MILESTONE_ACCEPTED`: Experience evidence now enters an evidence-governed Candidate Skill → independent validation → Active Skill → bounded Candidate Reflex lifecycle. Reflex divergence/revalidation records SELF proposals and Learning Ledger evidence; it cannot autonomously rewrite or promote production. Evidence: `docs/evidence/gen2-evolve-003-milestone-2026-08-30.md`. UI and V1 compatibility remain unchanged.
- `GEN2-EVOLVE-002 = MILESTONE_ACCEPTED`: one bounded typed cognitive memory-continuation round is available through Core and the existing retrieval fabric. Evidence: `docs/evidence/gen2-evolve-002-milestone-2026-08-30.md`. UI and V1 compatibility remain unchanged.
@@ -0,0 +1,28 @@
# GEN2-EVOLVE-004 milestone evidence
Status: MILESTONE_ACCEPTED for the bounded SELF evidence/proposal domain.
## Delivered
- Typed learning signals cover verified failures, corrections, repeated mistakes, Reflex divergence, Skill fallback, escalation, outcome mismatch, retry/fallback, stale dependencies, regret and better solutions.
- Attribution returns a bounded evidence-backed `HYPOTHESIS`; provider outage is not converted into a competence loss.
- Weakness patterns require two matching incidents in the same Principal/task/Scope and retain evidence, frequency, severity, affected component and confidence.
- Competence assessment is contextual and requires evidence; SELF cannot self-declare trust.
- ImprovementNeed and Candidate contain hypothesis, expected effect, evaluation, must-not-worsen, rollback, cost and independent evaluator fields.
- Benchmark cases reference evidence rather than copying conversations. Learning Ledger appends expected and actual evaluation references and decision.
- Candidate evaluation rejects latency/quality regression and creator/evaluator identity reuse. New model qualification creates a review candidate only.
- PostgreSQL migration `055` adds SELF-owned durable signal, weakness, candidate and benchmark tables under existing `self_role`; no new role, owner or cross-owner relation is created.
## Checks
- `go test ./contracts/self ./modules/self/domain ./adapters/postgres ./cmd/core-runtime`
- `go vet ./contracts/self ./modules/self/domain ./adapters/postgres ./cmd/core-runtime`
- `go run ./cmd/devctl preflight -task dev/tasks/GEN2-EVOLVE-004-self-improvement-loop-weakness-diagnosis-and-governed-learning.json`
- `go run ./cmd/devctl check-diff -task dev/tasks/GEN2-EVOLVE-004-self-improvement-loop-weakness-diagnosis-and-governed-learning.json`
- `go run ./cmd/devctl/independent`
- `go run ./cmd/devctl check-architecture`
- `git diff --check`
## Boundaries
No autonomous code generation, merge, deployment, production promotion, new SELF owner, Skill/Reflex mutation, UI work, V1 retirement or Gen2-EVOLVE-005 was performed. Existing Work governed-evolution gates remain the promotion authority. Runtime recorder wiring through `cmd/core-runtime/main.go` was not added because it is outside the accepted packet scope; the durable repository and typed domain path are ready for a separately authorized integration task.
+175 -9
View File
@@ -2,24 +2,190 @@ package domain
import (
"fmt"
"sort"
"git.cin.su/m/Assistant/contracts/self"
"git.cin.su/m/Assistant/platform/ids"
)
const (
MinWeaknessIncidents = 2
MaxDiagnosisEvidence = 8
MaxCandidateEvidence = 12
)
func ValidateCompetence(v self.CompetenceAssessment) error { return v.Validate() }
func ValidateLearning(v self.LearningLedgerEntry) error { return v.Validate() }
// ImprovementForReflexDivergence records a proposed review only. SELF never
// promotes, rewrites or trusts a Reflex from this observation.
func ImprovementForReflexDivergence(principal string, reflexRef self.SemanticReference, evidence []self.SemanticReference, at int64) (self.ImprovementNeed, self.LearningLedgerEntry, error) {
if principal == "" || !reflexRef.Valid() || len(evidence) == 0 || at < 1 {
return self.ImprovementNeed{}, self.LearningLedgerEntry{}, fmt.Errorf("reflex improvement lacks evidence")
func AttributeFailure(signal self.LearningSignal) self.Diagnosis {
class := self.DiagnosisUnknown
switch signal.Kind {
case self.SignalSkillFallback:
class = self.DiagnosisSkillFailure
case self.SignalReflexDivergence:
class = self.DiagnosisReflexFailure
case self.SignalStaleDependency:
class = self.DiagnosisProviderFailure
case self.SignalRetryFallback:
class = self.DiagnosisRoutingInefficiency
case self.SignalOutcomeMismatch:
class = self.DiagnosisOutcomeVerificationFailure
}
need := self.ImprovementNeed{ID: self.AssessmentID("improvement:" + string(reflexRef.StableID) + ":" + fmt.Sprint(at)), PrincipalID: principal, SubjectRef: reflexRef, ProblemSummary: "shadow divergence requires governed reflex revalidation", EvidenceRefs: evidence, ExpectedBenefit: "restore verified bounded behavior or retire the reflex", Priority: 1, State: "OPEN", CreatedAt: at}
return self.Diagnosis{Class: class, Status: self.EpistemicHypothesis, Rationale: "classified from bounded signal; independent confirmation required", EvidenceRefs: uniqueRefs(signal.EvidenceRefs, MaxDiagnosisEvidence), ConfidenceMillis: 250}
}
func BuildWeaknessPattern(principal, taskPattern, scopeKey, environment string, incidents []self.LearningSignal, affectedComponent, strategy string) (self.WeaknessPattern, bool, error) {
if principal == "" || taskPattern == "" || scopeKey == "" || affectedComponent == "" || len(incidents) < MinWeaknessIncidents {
return self.WeaknessPattern{}, false, nil
}
refs := []self.SemanticReference{}
severity, last := 0, int64(0)
for _, incident := range incidents {
if err := incident.Validate(); err != nil || incident.PrincipalID != principal || incident.TaskPattern != taskPattern || incident.ScopeKey != scopeKey {
return self.WeaknessPattern{}, false, fmt.Errorf("weakness incidents are not one bounded context")
}
refs = append(refs, incident.EvidenceRefs...)
if incident.Severity > severity {
severity = incident.Severity
}
if incident.UserImpact > severity {
severity = incident.UserImpact
}
if incident.ObservedAt > last {
last = incident.ObservedAt
}
}
return self.WeaknessPattern{ID: self.AssessmentID("weakness:" + principal + ":" + taskPattern + ":" + scopeKey), PrincipalID: principal, Revision: 1, PatternKey: taskPattern + ":" + scopeKey, TaskPattern: taskPattern, ScopeKey: scopeKey, EnvironmentVersion: environment, FailureMode: string(AttributeFailure(incidents[len(incidents)-1]).Class), DetectionEvidenceRefs: uniqueRefs(refs, MaxDiagnosisEvidence), State: self.AssessmentCandidate, RecordedAt: last, Frequency: len(incidents), LastObservedAt: last, Severity: severity, AffectedComponent: affectedComponent, AffectedStrategy: strategy, ConfidenceMillis: int64(min(1000, 250+len(incidents)*150)), EpistemicStatus: self.EpistemicSupported}, true, nil
}
func AssessContextualCompetence(principal string, subject self.SemanticReference, context []string, evidence []self.SemanticReference, quality, reliability, confidence, assessedAt int64) (self.CompetenceAssessment, error) {
v := self.CompetenceAssessment{ID: self.AssessmentID("competence:" + principal + ":" + string(subject.StableID)), PrincipalID: principal, SubjectRef: subject, Revision: 1, ContextMatcher: append([]string(nil), context...), QualityMillis: quality, ReliabilityMillis: reliability, ConfidenceMillis: confidence, EvidenceRefs: uniqueRefs(evidence, MaxDiagnosisEvidence), AssessedAt: assessedAt, LastValidatedAt: assessedAt}
if err := v.Validate(); err != nil {
return self.CompetenceAssessment{}, err
}
return v, nil
}
func BuildImprovementNeed(signal self.LearningSignal, diagnosis self.Diagnosis, component string) (self.ImprovementNeed, error) {
if err := signal.Validate(); err != nil {
return self.ImprovementNeed{}, err
}
if err := diagnosis.Validate(); err != nil {
return self.ImprovementNeed{}, err
}
if component == "" {
return self.ImprovementNeed{}, fmt.Errorf("affected component is required")
}
priority := signal.Severity*2 + signal.UserImpact + signal.CorrectionCount
if priority < 1 {
priority = 1
}
return self.ImprovementNeed{ID: self.AssessmentID("need:" + signal.ID), PrincipalID: signal.PrincipalID, SubjectRef: signal.SubjectRef, ProblemSummary: "bounded evidence indicates an improvement opportunity", EvidenceRefs: diagnosis.EvidenceRefs, ExpectedBenefit: "reduce recurrence without weakening verified behavior", Priority: priority, State: "OPEN", CreatedAt: signal.ObservedAt, Diagnosis: diagnosis.Class, DiagnosisStatus: diagnosis.Status, AffectedComponent: component, ScopeKey: signal.ScopeKey, Risk: "regression in established behavior", Intervention: interventionFor(diagnosis.Class), Hypothesis: diagnosis.Rationale, EvaluationPlan: "independent benchmark and must-not-worsen evaluation", MustNotWorsen: "quality and verified outcome rate", RollbackPlan: "disable candidate and restore last-known-good", ResourceCost: "bounded review"}, nil
}
func BuildCandidate(need self.ImprovementNeed, creator, evaluator string, evidence []self.SemanticReference, at int64) (self.ImprovementCandidate, error) {
ref := self.SemanticReference{OwnerNamespace: "self", SemanticKind: "improvement_need", StableID: ids.StableID(need.ID)}
ledger := self.LearningLedgerEntry{ID: self.LearningLedgerEntryID("ledger:" + string(reflexRef.StableID) + ":" + fmt.Sprint(at)), PrincipalID: principal, ImprovementNeedRef: &ref, ObservedProblem: need.ProblemSummary, EvidenceRefs: evidence, Diagnosis: "bounded shadow comparison diverged or verifier rejected result", Hypothesis: "implementation or applicability no longer matches evidence", ProposedChangeRef: reflexRef, ExpectedImprovement: need.ExpectedBenefit, Decision: "PROPOSE_REVALIDATION", RecordedAt: at}
if err := ledger.Validate(); err != nil {
c := self.ImprovementCandidate{ID: "candidate:" + string(need.ID), PrincipalID: need.PrincipalID, NeedRef: ref, Hypothesis: need.Hypothesis, ExpectedEffect: need.ExpectedBenefit, EvaluationPlan: need.EvaluationPlan, MustNotWorsen: []string{need.MustNotWorsen}, RollbackPlan: need.RollbackPlan, ResourceCost: need.ResourceCost, Creator: creator, IndependentEvaluator: evaluator, State: self.CandidateProposed, EvidenceRefs: uniqueRefs(evidence, MaxCandidateEvidence), CreatedAt: at}
if err := c.Validate(); err != nil {
return self.ImprovementCandidate{}, err
}
return c, nil
}
func EvaluateCandidate(candidate self.ImprovementCandidate, evaluation self.ImprovementEvaluation) (self.CandidateState, error) {
if evaluation.Evaluator == "" || evaluation.Evaluator == candidate.Creator || !evaluation.MustNotWorsenPass || evaluation.ActualQuality < evaluation.ExpectedQuality || (evaluation.ExpectedLatency > 0 && evaluation.ActualLatency > evaluation.ExpectedLatency*2) {
return self.CandidateRejected, nil
}
return self.CandidateKept, nil
}
func ClassifyDecisionRegret(signal self.LearningSignal) self.DecisionRegretKind {
if signal.DecisionRegret != "" {
return signal.DecisionRegret
}
switch signal.ActualOutcome {
case "information unavailable":
return self.RegretUnavailableThen
case "available but unused":
return self.RegretAvailableUnused
case "ignored in context":
return self.RegretIgnoredInContext
}
return ""
}
func BuildBenchmarkCase(principal, class string, evidence []self.SemanticReference, at int64) (self.BenchmarkCase, error) {
c := self.BenchmarkCase{ID: "benchmark:" + principal + ":" + class, PrincipalID: principal, TaskClass: class, EvidenceRefs: uniqueRefs(evidence, MaxCandidateEvidence), RecordedAt: at}
if !c.Valid() {
return self.BenchmarkCase{}, fmt.Errorf("benchmark requires evidence")
}
return c, nil
}
func AppendLedgerOutcome(need self.ImprovementNeed, candidate self.ImprovementCandidate, evaluation self.ImprovementEvaluation, decision self.CandidateState, at int64) (self.LearningLedgerEntry, error) {
ref := self.SemanticReference{OwnerNamespace: "self", SemanticKind: "improvement_need", StableID: ids.StableID(need.ID)}
refs := append(append([]self.SemanticReference{}, need.EvidenceRefs...), evaluation.EvidenceRefs...)
e := self.LearningLedgerEntry{ID: self.LearningLedgerEntryID("ledger:" + candidate.ID + ":" + fmt.Sprint(at)), PrincipalID: need.PrincipalID, ImprovementNeedRef: &ref, ObservedProblem: need.ProblemSummary, EvidenceRefs: uniqueRefs(refs, MaxCandidateEvidence), Diagnosis: string(need.Diagnosis), Hypothesis: need.Hypothesis, ProposedChangeRef: candidate.NeedRef, ExpectedImprovement: need.ExpectedBenefit, ActualEvaluationRefs: evaluation.EvidenceRefs, Decision: string(decision), RecordedAt: at}
if err := e.Validate(); err != nil {
return self.LearningLedgerEntry{}, err
}
return e, nil
}
func ReviewCandidateForNewModel(principal string, model self.SemanticReference, evidence []self.SemanticReference, at int64) (self.ImprovementCandidate, error) {
n := self.ImprovementNeed{ID: self.AssessmentID("need:model-review:" + string(model.StableID)), PrincipalID: principal, SubjectRef: model, ProblemSummary: "qualified model is available for governed review", EvidenceRefs: evidence, ExpectedBenefit: "compare model quality under existing safety constraints", Priority: 1, State: "OPEN", CreatedAt: at, Intervention: self.InterventionProviderReview, Hypothesis: "model may improve quality", EvaluationPlan: "independent benchmark", MustNotWorsen: "safety, latency and verified outcomes", RollbackPlan: "retain current route", ResourceCost: "bounded evaluation"}
return BuildCandidate(n, "qualification", "independent-evaluator", evidence, at)
}
func interventionFor(c self.DiagnosisClass) self.InterventionClass {
switch c {
case self.DiagnosisSkillFailure:
return self.InterventionSkillReview
case self.DiagnosisReflexFailure:
return self.InterventionReflexReview
case self.DiagnosisProviderFailure:
return self.InterventionProviderReview
case self.DiagnosisRoutingInefficiency:
return self.InterventionRoutingReview
case self.DiagnosisContextSelection, self.DiagnosisMemoryNotRetrieved:
return self.InterventionContextReview
}
return self.InterventionConfiguration
}
func uniqueRefs(refs []self.SemanticReference, max int) []self.SemanticReference {
seen := map[string]bool{}
out := make([]self.SemanticReference, 0, len(refs))
for _, r := range refs {
if !r.Valid() || seen[string(r.StableID)] {
continue
}
seen[string(r.StableID)] = true
out = append(out, r)
if len(out) == max {
break
}
}
sort.Slice(out, func(i, j int) bool { return string(out[i].StableID) < string(out[j].StableID) })
return out
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func ImprovementForReflexDivergence(principal string, reflexRef self.SemanticReference, evidence []self.SemanticReference, at int64) (self.ImprovementNeed, self.LearningLedgerEntry, error) {
s := self.LearningSignal{ID: "signal:reflex:" + string(reflexRef.StableID), PrincipalID: principal, Kind: self.SignalReflexDivergence, TaskPattern: "reflex", ScopeKey: "default", SubjectRef: reflexRef, EvidenceRefs: evidence, ObservedAt: at, Severity: 1}
d := AttributeFailure(s)
n, err := BuildImprovementNeed(s, d, "reflex")
if err != nil {
return self.ImprovementNeed{}, self.LearningLedgerEntry{}, err
}
return need, ledger, nil
ref := self.SemanticReference{OwnerNamespace: "self", SemanticKind: "improvement_need", StableID: ids.StableID(n.ID)}
l := self.LearningLedgerEntry{ID: self.LearningLedgerEntryID("ledger:" + string(reflexRef.StableID) + ":" + fmt.Sprint(at)), PrincipalID: principal, ImprovementNeedRef: &ref, ObservedProblem: n.ProblemSummary, EvidenceRefs: evidence, Diagnosis: string(d.Class), Hypothesis: d.Rationale, ProposedChangeRef: reflexRef, ExpectedImprovement: n.ExpectedBenefit, Decision: "PROPOSE_REVALIDATION", RecordedAt: at}
if err := l.Validate(); err != nil {
return self.ImprovementNeed{}, self.LearningLedgerEntry{}, err
}
return n, l, nil
}
+72
View File
@@ -3,6 +3,7 @@ package domain_test
import (
selfcontract "git.cin.su/m/Assistant/contracts/self"
"git.cin.su/m/Assistant/modules/self/domain"
"git.cin.su/m/Assistant/platform/ids"
"testing"
)
@@ -25,3 +26,74 @@ func TestShadowDivergenceCreatesProposalNotPromotion(t *testing.T) {
t.Fatalf("need=%#v ledger=%#v err=%v", need, ledger, err)
}
}
func signal(kind selfcontract.LearningSignalKind, id, scope string, at int64) selfcontract.LearningSignal {
return selfcontract.LearningSignal{ID: id, PrincipalID: "p", Kind: kind, TaskPattern: "recall", ScopeKey: scope, SubjectRef: selfcontract.SemanticReference{OwnerNamespace: "interaction", SemanticKind: "assertion", StableID: ids.StableID("x")}, EvidenceRefs: []selfcontract.SemanticReference{{OwnerNamespace: "epistemics", SemanticKind: "experience", StableID: ids.StableID(id)}}, ObservedAt: at, Severity: 2, UserImpact: 1}
}
func TestRepeatedSignalsCreateScopedWeaknessButSingleDoesNot(t *testing.T) {
a, b := signal(selfcontract.SignalUserCorrection, "e1", "company-a", 1), signal(selfcontract.SignalUserCorrection, "e2", "company-a", 2)
if _, created, err := domain.BuildWeaknessPattern("p", "recall", "company-a", "v1", []selfcontract.LearningSignal{a}, "context", "strategy"); err != nil || created {
t.Fatalf("single incident created weakness: %v %v", created, err)
}
w, created, err := domain.BuildWeaknessPattern("p", "recall", "company-a", "v1", []selfcontract.LearningSignal{a, b}, "context", "strategy")
if err != nil || !created || w.Frequency != 2 || w.ScopeKey != "company-a" {
t.Fatalf("weakness=%#v created=%v err=%v", w, created, err)
}
}
func TestDiagnosisCandidateAndMustNotWorsen(t *testing.T) {
s := signal(selfcontract.SignalReflexDivergence, "e3", "company-a", 3)
d := domain.AttributeFailure(s)
if d.Class != selfcontract.DiagnosisReflexFailure || d.Status != selfcontract.EpistemicHypothesis {
t.Fatalf("diagnosis=%#v", d)
}
n, err := domain.BuildImprovementNeed(s, d, "reflex")
if err != nil {
t.Fatal(err)
}
c, err := domain.BuildCandidate(n, "creator", "evaluator", s.EvidenceRefs, 3)
if err != nil {
t.Fatal(err)
}
decision, err := domain.EvaluateCandidate(c, selfcontract.ImprovementEvaluation{Evaluator: "evaluator", EvidenceRefs: s.EvidenceRefs, ExpectedQuality: 800, ActualQuality: 800, ExpectedLatency: 100, ActualLatency: 250, MustNotWorsenPass: true})
if err != nil || decision != selfcontract.CandidateRejected {
t.Fatalf("decision=%v err=%v", decision, err)
}
}
func TestScopedEvidenceIsDeduplicatedAndBounded(t *testing.T) {
s := signal(selfcontract.SignalSkillFallback, "e4", "company-a", 4)
d := domain.AttributeFailure(s)
n, err := domain.BuildImprovementNeed(s, d, "skill")
if err != nil {
t.Fatal(err)
}
if _, err = domain.BuildCandidate(n, "creator", "evaluator", append(s.EvidenceRefs, s.EvidenceRefs...), 4); err != nil {
t.Fatal(err)
}
if got := domain.ClassifyDecisionRegret(selfcontract.LearningSignal{DecisionRegret: selfcontract.RegretIgnoredInContext}); got != selfcontract.RegretIgnoredInContext {
t.Fatal(got)
}
}
func TestBenchmarkAndLedgerReferenceEvidence(t *testing.T) {
s := signal(selfcontract.SignalOutcomeMismatch, "e5", "company-a", 5)
caseRef, err := domain.BuildBenchmarkCase("p", "memory-recall", s.EvidenceRefs, 5)
if err != nil || !caseRef.Valid() {
t.Fatalf("benchmark=%#v err=%v", caseRef, err)
}
d := domain.AttributeFailure(s)
n, err := domain.BuildImprovementNeed(s, d, "verification")
if err != nil {
t.Fatal(err)
}
c, err := domain.BuildCandidate(n, "creator", "evaluator", s.EvidenceRefs, 5)
if err != nil {
t.Fatal(err)
}
l, err := domain.AppendLedgerOutcome(n, c, selfcontract.ImprovementEvaluation{Evaluator: "evaluator", EvidenceRefs: s.EvidenceRefs}, selfcontract.CandidateRejected, 6)
if err != nil || l.Decision != string(selfcontract.CandidateRejected) || len(l.EvidenceRefs) == 0 {
t.Fatalf("ledger=%#v err=%v", l, err)
}
}